Loading... # Windows核心编程-学习笔记2 ## CreateProcess ~~~c++ BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandles, DWORD fdwCreate, PVOID pvEnvironment, PCTSTR pszCurDir, PSTARTUPINFO psiStartInfo, PPROCESS_INFORMATION ppiProcInfo ); ~~~ > 线程调用CreateProcess后,系统做了什么? * 系统创建一个进程内核对象,其初始使用计数为1。该进程内核对象不是进程本身,而是OS用来管理进程的一个**小型数据结构**。 * 系统为新进程创建一个虚拟地址空间,并将可执行文件的代码和数据加载进去 * 系统为新进程的主线程创建一个线程内核对象,其使用计数仍为1,也是小型数据结构。该主线程首先执行应用程序入口点,它由链接器设为C/C++运行库启动代码,并进而调用程序函数WinMain、wWinMain、main或wmain 如果系统成功创建新的进程和主线程,则函数返回TRUE(要注意的是,CreateProcess在进程完全初始化完之前就返回TRUE,那么有可能有一个DLL没找到进程就终止,但是由于已返回TRUE,父进程不会注意到初始化失败的问题) ### pszApplicationName+pszCommandLine **pszCommandLine**:传给新进程的命令行字符串 首先看pszCommandLine,它的类型是PTCSTR,即传入的需要是**非“常量字符串”的地址**,在CreateProcess内部实际上会修改我们传入的命令行字符串,但返回之前会恢复为原来的字符串。因此如果传入的命令行字符串包含在文件映像的只读部分,会引起访问违例,如下 ~~~c++ STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi; CreateProcess(NULL, TEXT("NOTEPAD"), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ~~~ 简单的办法就是加一个临时缓冲区 ~~~c++ STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi; TCHAR szCommandLine[] = TEXT("NOTEPAD") CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ~~~ 解析命令行字符串时看是否有扩展名,没有默认exe。同时函数按照以下顺序搜索可执行文件 * 调用进程.exe文件所在的目录(完整目录) * 调用进程的当前目录 * Windows系统目录,即GetSystemDirectory返回的System32子文件夹 * Windows目录 * PATH环境变量中列出的目录 **pszApplicationName**:新进程要使用的可执行文件的名称,绝大数情况下为NULL,但也可以传入字符串地址,必须包含想要运行可执行文件的名称,且包含扩展名,因为它不会默认有exe,且只会在**指定路径目录或者当前目录**搜索文件 ### psaProcess+psaThread+bInheritHandles 前面提到了要创建新进程,系统必须创建一个进程内核对象和一个线程内核对象。由于都是内核对象,父进程有机会将安全属性关联到这两个对象上: * 传NULL:默认安全描述符 * SECURITY_ATTRIBUTES结构:创建安全权限,使用这个的原因是这两个对象句柄可由父进程将来生成的任何子进程继承 bInheritHandles用来允许新的进程操作父进程能访问的先前的内核对象,这仨感觉没啥用传NULL、NULL、FALSE就好了 ### fdwCreate 该参数标识了影响新进程创建方式的标志,可以多个标志通过按位或操作符来组合使用。可用标志如下: * DEBUG_PROCESS:父进程希望对子进程以及子进程将来生成的所有进程进行调试 * DEBUG_ONLY_THIS_PROCESS:只有关系最近的子进程发生特定事件时,父进程才会得到通知 * CREATE_SUSPENDED:让系统在创建新进程的同时挂起其他主线程。这样父进程可以修改子进程地址空间中的内存,更改子进程的主线程优先级等 * DETACHED_PROCESS:阻止一个基于CUI的进程访问其父进程的控制台窗口,如果一个基于CUI的进程由另一个基于CUI进程创建的,默认情况下新进程输出回附加到现有控制台窗口底部。有这个标志就可以输出发送到新的窗口 * CREATE_NEW_CONSOLE:指示系统为新进程创建一个新的控制台窗口 * CREATE_NO_WINDOW:指示系统不要为应用程序创建任何控制台窗口 * CREATE_NEW_PROCESS_GROUP:修改用户按下ctrl+c时获的通知的进程列表。按下组合键时,假设有多个CUI进程正在运行,系统将通知一个进程组中的所有进程,告诉他们用户打算中断当前操作。在创建一个新的CUI进程时,假如指定这个标志,就会创建一个新的进程组。其中一个进程处于活动状态时,用户按下ctrl+c,系统只向该组中的进程发送通知 * CREATE_DEFAULT_ERROR_MODE:向系统表明新进程不会继承父进程所用的错误模式 * CREATE_SEPARATE_WOW_VDM|CREATE_SHARED_WOW_VDM:16位用,不看了 * CREATE_UNICODE_ENVIRONMENT:告诉系统子进程的环境块应包含Unicode字符(默认ANSI) * CREATE_FORCEDOS:强制系统运行一个嵌入在16位OS/2应用程序中的MS-DOS应用程序 * CREATE_BREAKAWAY_FROM_JOB:允许一个作业中的进程生成一个不和作业关联的进程 * EXTENDED_STARTUPINFO_PRESENT:告诉OS传给psiStartInfo参数的是一个STARTUPINFOEX结构 ### pvEnvironment 指向一个内存块,其中包含新进程要使用的环境字符串,大多情况下为NULL,即继承父进程环境字符串 ### pszCurDir 允许父进程设置子进程的当前驱动器和目录(必须路径中指定驱动器号),默认NULL即相同目录下 ### psiStartInfo 指向一个STARTUPINFO结构或STARTUPINFOEX结构 ~~~c STARTUPINFO si = {sizeof(si)}; CreateProcess(..., &si, ...); ~~~ 上面是默认的,主要成员如下 | 成员 | 窗口、控制台或两者 | 用途 | | ------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | cb | 两者 | 包含该结构体的字节数,sizeof(STARTUPINFO) | | lpReserved | 两者 | 保留,必须初始化为NULL | | lpDesktop | 两者 | 标识一个名称,表明在哪个桌面启动应用程序,不存在则创建,NULL则当前桌面 | | lpTitle | 控制台 | 指定控制台窗口的标题,NULL则可执行文件的名称 | | dwX、dwY | 两者 | 指定应用程序窗口在屏幕上的位置 | | dwXsize、dwYSize | 两者 | 指定应用程序窗口的宽高 | | dwXCountChars、dwYCountChars | 控制台 | 指定子进程控制台的宽高(用字符数来表示) | | dwFillAttribute | 控制台 | 指定子进程的控制台窗口所用的文本和背景色 | | dwFlags | 两者 | 见下 | | wShowWindow | 窗口 | 指定应用程序的主窗口如何显示 | | cbReserved2 | 两者 | 保留,必须初始化为0 | | lpReversed2 | 两者 | 保留,必须初始化为NULL | | hStdInput、hStdOuput、hStdError | 控制台 | 指定到控制台输入缓冲区的句柄和输出缓冲区的句柄。默认hStdInput标识一个键盘缓冲区,hStdOutput和hStdError标识一个控制台窗口的缓冲区 | dwFlags成员包括一组标志,用于修改子进程的创建方式 | 标志 | 含义 | | ----------------------- | --------------------------------------------------- | | STARTF_USESIZE | 使用dwXsize、dwYSize成员 | | STARTF_USESHOWWINDOW | 使用wShowWindow成员 | | STARTF_USEPOSITION | 使用dwX和dwY成员 | | STARTF_USECOUNTCHAR | 使用dwXCountChars、dwYCountChars成员 | | STARTF_USEFILLATTRIBUTE | 使用dwFillAttribute成员 | | STARTF_USESTDHANDLES | 使用hStdInput、hStdOuput、hStdError成员 | | STARTF_RUNFULLSCREEN | 使x86计算机上运行的一个控制台应用程序以全屏模式启动 | | STARTF_FORCEONFEEDBACK | 启动新进程时控制鼠标指针,出现圆圈 | | STARTF_FORCEOFFFEEDBACK | 不会出现圆圈 | ### ppiProcInfo 指向一个PROCESS_INFORMATION结构,CreateProcess函数在返回前会初始化这个结构的成员 ~~~c typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION; ~~~ 前面提到创建进程时,系统创建一个进程内核对象和线程内核对象,并指定一个初始的使用计数1,在CreateProcess返回前会以完全访问权限打开进程对象和线程对象,并将各自的与进程相关联的句柄放入PROCESS_INFORMATION结构的hProcess和hThread成员中。CreateProcess在内部打开这些对象时,每个对象的使用计数变为2。 这意味着系统要释放进程对象,进程必须终止(使用计数递减1),且父进程必须调用**CloseHandle**(再减一为0) 系统空闲进程:ID为0,用来占位,System Idle Process线程数目等于计算机CPU数目 * GetCurrentProcessId/GetCurrentThreadId:获得当前正在运行的进程/线程ID * GetProcessId/GetThreadId:获取指定句柄对应的进程/线程的ID ## 终止进程 ### 主线程的入口点函数返回 确保这个才能确保主线程所有资源被正确清理 * 该线程创建的任何C++对象都将被正确销毁 * OS正确释放线程使用的内存 * 系统将进程的退出代码设为入口点函数的返回值 * 系统递减进程内核对象的使用计数 ### ExitProcess ~~~c VOID ExitProcess(UINT fuExtCode); ~~~ 该函数将终止进程,并将进程的退出代码设置为fuExitCode。C运行时启动代码将显式调用ExitProcess,并将入口点函数的返回值传给它 不建议用这个函数,在某些情况下可能会导致C/C++程序不能正确清理工作 ### TerminateProcess ~~~c BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode); ~~~ 任何线程都可以调用这个函数来终止另一个进程或者他自己的进程。只有在无法通过其他方法强制进程退出才使用这个 ### 当进程中所有线程都终止时 此时OS认为没有理由再保持进程的地址空间,就会终止该进程。进程的退出代码会被设为最后一个终止那个线程的退出代码。 ## 运行权限 手动提升权限,下面是一个使用管理员打开cmd的代码 ~~~c #include <windows.h> #include <shellapi.h> #include <tchar.h> #include <stdio.h> int main() { SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) }; sei.lpVerb = TEXT("runas"); sei.lpFile = TEXT("cmd.exe"); sei.nShow = SW_SHOWNORMAL; if (!ShellExecuteEx(&sei)) { DWORD dwStatus = GetLastError(); if (dwStatus == ERROR_CANCELLED) { printf("NoNoNo!\n"); } } } ~~~ 下面的GetProcessElevation能返回提升类型和一个指出进程是否正在以管理员身份运行的布尔值 ~~~c #include <windows.h> #include <shellapi.h> #include <tchar.h> #include <stdio.h> #include <shlobj_core.h> BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE* pElevationType, BOOL* pIsAdmin) { HANDLE hToken = NULL; DWORD dwSize; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return FALSE; BOOL bResult = FALSE; if (GetTokenInformation(hToken, TokenElevationType, pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) { BYTE adminSID[SECURITY_MAX_SID_SIZE]; dwSize = sizeof(adminSID); CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID, &dwSize); if (*pElevationType == TokenElevationTypeLimited) { HANDLE hUnfilteredToken = NULL; GetTokenInformation(hToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize); if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)) { bResult = TRUE; } CloseHandle(hUnfilteredToken); } else { *pIsAdmin = IsUserAnAdmin(); bResult = TRUE; } } CloseHandle(hToken); return bResult; } int main() { TOKEN_ELEVATION_TYPE elevationType; BOOL isAdmin = FALSE; if (!GetProcessElevation(&elevationType, &isAdmin)) { printf("GetProcessElevation failed, error = %lu\n", GetLastError()); return 1; } printf("ElevationType = %d\n", elevationType); printf("IsAdmin = %s\n", isAdmin ? "TRUE" : "FALSE"); } ~~~ 最后修改:2025 年 11 月 28 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏