一、进程基本概念
1、进程:一个进程就是一个正在运行的程序,一个程序可以产生多个进程。进程包含下面两个东西
● 进程内核对象:一个内核对象被系统用来管理某个进程,内核对象就是代表这个进程。这个内核对象中,还包含了进程的一些策略,策略信息是系统怎么给进程分配时间、怎么调内核的优先级
● 地址空间:地址空间中包含了可执行代码、动态链接库模块代码、数据、程序动态内存分配获取的内存。
2、进程的特性
进程是静态的,进程中真正执行代码的是线程,所以进程必须有一个主线程primarythread。两种方法退出进程,第一如果进程内所有线程退出,那么进程自动销毁;第二使用函数ExitProcess函数强制退出当前进程
把进程比喻成工厂,把工厂中的人比作线程,工厂给人提供资源(场地、加工设备、原材料),主线程是随进程产生(工厂产生的时候同时产生它的厂长)。厂长工作第一件事就是招人,线程也是一样,产生后,就生成其他线程开始干活了。
二、WINDOW程序执行流程
Windows程序分两种,一个是控制台程序CUI、一个是图形界面程序GUI。如果我们选错了,可以在下图相应的位置修改项目属性。
我们所编写的windows程序中,真正第一个被执行的函数是:
WinMainCRTStartup // GUI_ASCII字符类型C运行时库的入口函数
wWinMainCRTStartup // GUI_UNICODE字符类型C运行时库的入口函数
mainCRTStartup // CUI_ASCII字符类型C运行时库的入口函数
WmainCRTStartup // CUI_UNICODE字符类型C运行时库的入口函数
C运行时库函数,主要完成以下任务:
1. 获取进程命令行指针;
2. 获取进程环境变量指针;
3. 初始化C/C++运行时库的全局变量,如果你包含了头stdlib.h,那么你就可以访问这些变量;
4. 初始化malloc函数的内存堆;
5. 为C++全局类,调用构造函数。
C语言库函数,例如malloc函数最终会调用windowsAPI函数,我们直接调用virtualAlloc windowsAPl函数,效率会更高
程序执行流程
-> Kernerl调用XXXCRTStartup函数
-> XXXCRTStartup函数调用main函数
-> main函数退出
-> exit函数被调用(真正调用的是crt0dat.c中的static void __cdecl doexit(int code int quick, int retcaller);
exit函数主要做以下事情:
1. 调用_onexit函数注册的所有函数;
2. 调用C++销毁函数销毁所有的全局和静态类对象;
3. 如果_CRTDBG_LEAK_CHECK_DF标志被设置,那么调用CrtDumpMemoryLeaks函数,列出泄露内存;
4. 调用ExitProcess函数,系統杀死当前进程
如果我们直接调用ExitProcess,那么前面的内存释放工作都不会做,所以可以会存在一些问题
三、程序运行系统和版本
#include <stdio.h> #include <windows.h> //操作系统版本 #define WINXP 51 #define WINXP2600 512600 #define WIN7 61 #define WIN77600 617600 #define WIN77601 617601 #define WIN8 62 #define WIN89200 629200 #define WIN81 63 #define WIN819600 639600 #define WIN10 100 #define WIN1010240 10010240 #define WIN1010586 10010586 #define WIN1014393 10014393 int main(void) { //定义变量 typedef LONG(__stdcall *fnRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation); fnRtlGetVersion pRtlGetVersion; HMODULE hNtdll; LONG ntStatus; ULONG dwMajorVersion = 0; ULONG dwMinorVersion = 0; ULONG dwBuildNumber = 0; RTL_OSVERSIONINFOW VersionInformation = { 0 }; DWORD OsVersion; do { hNtdll = GetModuleHandle(L"ntdll.dll"); if (hNtdll == NULL)break; pRtlGetVersion = (fnRtlGetVersion)GetProcAddress(hNtdll, "RtlGetVersion"); if (pRtlGetVersion == NULL)break; VersionInformation.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW); ntStatus = pRtlGetVersion(&VersionInformation); if (ntStatus != 0)break; dwMajorVersion = VersionInformation.dwMajorVersion; dwMinorVersion = VersionInformation.dwMinorVersion; dwBuildNumber = VersionInformation.dwBuildNumber; if (dwMajorVersion == 5 && dwMinorVersion == 1 && dwBuildNumber == 2600) OsVersion = WINXP2600; else if (dwMajorVersion == 5 && dwMinorVersion == 1) OsVersion = WINXP; else if (dwMajorVersion == 6 && dwMinorVersion == 1 && dwBuildNumber == 7601) OsVersion = WIN77601; else if (dwMajorVersion == 6 && dwMinorVersion == 1 && dwBuildNumber == 7600) OsVersion = WIN77600; else if (dwMajorVersion == 6 && dwMinorVersion == 1) OsVersion = WIN7; else if (dwMajorVersion == 6 && dwMinorVersion == 2 && dwBuildNumber == 9200) OsVersion = WIN89200; else if (dwMajorVersion == 6 && dwMinorVersion == 2) OsVersion = WIN8; else if (dwMajorVersion == 6 && dwMinorVersion == 3 && dwBuildNumber == 9600) OsVersion = WIN819600; else if (dwMajorVersion == 6 && dwMinorVersion == 3) OsVersion = WIN81; else if (dwMajorVersion == 10 && dwMinorVersion == 0 && dwBuildNumber == 10240) OsVersion = WIN1010240; else if (dwMajorVersion == 10 && dwMinorVersion == 0 && dwBuildNumber == 10586) OsVersion = WIN1010586; else if (dwMajorVersion == 10 && dwMinorVersion == 0 && dwBuildNumber == 14393) OsVersion = WIN1014393; else if (dwMajorVersion == 10 && dwMinorVersion == 0) OsVersion = WIN10; else { return FALSE; } } while (FALSE); printf("%d\n", OsVersion); getchar(); getchar(); return 0; } |
四、进程句柄
1、进程句柄,要和进程内核对象的句柄区分开来,进程句柄也可以叫做模块句柄。每个可执行文件或者DLL文件被装入到某个进程的地址空间,都会有一个唯一的实例句柄,来表示装入后的可执行文件或者DLL,此时把这个可执行文件或者DLL叫做进程地址空间中的一个模块。
进程句柄(HINS)在程序很多地方都被使用,尤其是在装入某一个资源的时候,下面代码是装入图标资源的时候使用进程句柄hInstance:
LoadIcon( HINSTANCE hInstance; PCTSTR pszIcon); |
2、获得进程句柄的两种方法
方法1:WinMain函数的参数
WinMain函数的第一个参数就是当前可执行程序的进程句柄
方法2:使用GetModuleHandle函数
在DLL中调用GetModuleHandle返回的不是DLL模块的地址,而是当前进程的模块地址
这个函数只检查本进程地址空间,不检查别的进程的地址空间
3、有进程句柄获取进程模块的路径和名字
DWORD GetModuleFileName ( HMODULE hInstance, PTSTR pszPath, DWORD cchPath); |
五、进程命令行参数
方法1:入口函数传入参数
方法2:GetCommandLine();
六、环境变量操作
6.1 获取当前环境变量
方法1:使用全局变量
方法2:放在函数里
环境变量被保存放在注册表中
这个注册表键下放的是系统环境变量:HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSer \ Control \ Session Manager \ Environment
这个注册表键下放的是当前登陆用户自己的环境变量:HKEY_CURRENT_USER \ Environment
在你的程序中,如果你改变了环境变量,那么最好你发送一个消息:WM_SETTINGCHANGE。发这个消息时,不要使用某一个窗口的句柄,应该使用:HWND_BROADCAST作为窗口句柄。使用SendMessage或者SendMessageTimeout函数来发送这个消息,IParam参数应该设置成"Environment"。
#include <windows.h> #include <stdio.h> #include <fcntl.h> #include <io.h> int main() { wchar_t buf[1024]; DWORD i = GetEnvironmentVariableW(TEXT("Path"), buf, 1024); _setmode(_fileno(stdout), _O_U16TEXT); wprintf(TEXT("str: %s\n"), buf); //SetEnvironmentVariable(L"asd", L"qwe"); getchar(); return 0; } |
6.2 改变本进程的环境变量
修改系统环境变量要改注册表
6.3 解析系统变量中的%
七、进程目录操作
7.1 GetCurentDirectory获取进程的当前目录
DWORD WINAPI GetCurrentDirectory( ___in DWORD nBufferLength, ___out LPTSTR IpBuffer ); |
nBufferLength: lpBuffer指针指向内存块的大小 (单位TCHAR) ;
lpBuffer: 接收当前路径的内存块。
7.2 SetCurrentDirectory设置进程当前目录
_chdir: 设置当前目录
7.3 代码测试
#include <windows.h> #include <stdio.h> #include <fcntl.h> #include <io.h> int main() { _setmode(_fileno(stdout), _O_U16TEXT); wchar_t* szPath[MAX_PATH]; GetCurrentDirectoryW(MAX_PATH, (LPWSTR)szPath); wprintf(TEXT("%s\n"), szPath); wchar_t* str = TEXT("D:\\ico"); SetCurrentDirectoryW(str); GetCurrentDirectoryW(MAX_PATH, (LPWSTR)szPath); wprintf(TEXT("%s\n"), szPath); getchar(); return 0; } |
7.4 GetFullPathName补齐目录
DWORD WINAPI GetFullPathName( __in LPCTSTR lpFileName, __in DMORD nBufferLength, __out LPTSTR lpBuffer, __out LPTSTR *lpFilePart ); |
lpFileName: 文件名
NBufferlength: 获取全路径的内存大小( TCHAR )
lpBuffer: 内存指针
lpFilePart: 文件名最后一个元素,在lpBuffer中的位置。
上面生成的路径,实际上我磁盘中有D:\\ico,但是没有D:\\ico\\myIco
八、创建进程
8.1 用到的函数
BOOL WINAPI CreateProcess ( __in_opt LPCTSTR lpApplicationName, __inout_opt LPTSTR lpCommandLine , __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, _in_opt LPVOID lpEnvironment, __in_opt LPCTSTR lpCurrentDirectory, __in LPSTARTUPINFO IpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation ); |
简单使用如下,我们关闭控制台,计算机没有被关闭
8.2 参数1:lpApplicationName
被执行的模块的名称,这个模块可以是一个windows应用程序,也可以是其他类型的模块(例如MS-DOS或者OS/2)。如果要运行这些类型的模块,操作系统必须有支持这些模块运行的子系统
可以指定文件的全路径名和文件名,或者相对当前程序运行路径(前面讲了修改程序当前运行路径的函数)的文件名。参数要包含文件的拓展名
8.3 参数2:lpCommandLine
被执行的命令行参数。这个字符串的最大长度可以达到32768个字符,包括null结尾符。如果lpApplicationName是NULL,那么lpCommandLine参数中的可执行文件的名字被限定在MAT_PATH个字符之内。有命令行参数启动进程的方法如下
如果路径有空格,需要用引号括起来
8.4 参数3:lpProcessAttributes
进程的安全描述符
8.5 参数4:lpThreadAttributes
线程的安全描述符,参数3和参数4有专门的语言来编写
8.6 参数5:dwCreationFlags
这里面存放创建时的标志。可以设置进程的优先级,并且规定进程是怎么创建的,详情阅读进程创建标志:
WinBase.h) (进程创建标志 - Win32 apps | Microsoft Learn
我们测试几个
首先建一个这样的项目:
测试CREATE_NEW_CONSOLE标志,如果不加这个标志,不会打开新的控制台
如果加了这个标志,就会重新打开一个控制台打印新进程的输出
进程优先级的标志
8.7 参数6:lpEnvironment
一个指向环境变量内存块的指针。如果这个参数是NULL,那么新进程使用父进程的环境变量。环境变量块保存的是一个NULL结尾的字符串,每个环境变量在改字符串中的形式为:name=value\0
8.8 参数7:lpCurrentDirectory
进程的当前目录
8.9 参数8:lpStartupInfo
启动信息。里面是一个指向STARTUPINFO或者STARTUPINEX结构的指针
8.10 参数9:lpProcessInformation
一个指向PROCESS_INFORMATION结构的指针。
typedef struct PROCESS_INFORMATION {
HANDLE hProcess; // 进程句柄
HANDLE hThread; // 主线程句柄
DWORD dwProcessld; // 进程 ID
DWORD dwThreadld; // 主线程 ID
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
8.11 进程和进程内核对象的关系
进程内核对象的生命周期一定比进程生命周期长,进程没有退出代表进程的进程内核对象一定不会被销毁。当进程退出以后,进程内核对象才能被销毁。进程内核对象的引用计数为零时,销毁进程内核对象。当进程退出的时候,进程内核对象的状态发生变化。我们把进程推出后,代表该进程的内核对象的状态叫激发态
九、结束进程
9.1 进程结束的方式
1、进程的主线程返回(最好的方式)
2、进程中的一个线程调用ExitProcess函数(不推荐)
3、其他进程中的某个线程调用TerminateProcess函数(不推荐)
4、进程中的所有线程执行完毕(这个很少出现)
9.2 方式1
进程的主线程返回(最好的方式)
主线程就是main、winmain函数代表的线程,也就是进程中的第一个线程。无论这个进程还有多少其他线程,只要主线程一退出,那么这个进程就结束了,其他线程自然就被系统杀死了。
#include <Windows.h> #include <stdio.h> DWORD WINAPI ThreadProc(__in LPVOID lpParameter) { wprintf(TEXT("Thread sleeping!\n")); Sleep(INFINITE); // 让这个线程无限沉睡下去 return 0; } int main() { DWORD dwThreadID = 0; CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadID); // 创建一个线程 wprintf(TEXT("The sleeping thread ID is %d\n"), dwThreadID); getchar(); return 0; } |
可以通过工具查看进程当前情况
9.3 方式2
进程中的一个线程调用ExitProcess函数(不推荐)
这个函数只能结束本进程,不能把其他的线程结束掉
9.4 方式3
其他进程中的某个线程调用TerminateProcess函数(不推荐)
只要你有足够的权限,你就可以使用这个TerminateProcess函数,结束系统中的任意进程。同时这个TerminateProcess函数是一个异步函数,下面是退出edge的代码
9.5 进程结束时的动作
1. 所有线程结束
2. 所有用户GDI对象被释放,所有内核对象被关闭。
例如画笔,背景刷,句柄,设备句柄,申请的内存等。
3. 退出码被设置。(退出码就是main或者WinMain的返回值,或者ExitProcess, TerminateProcess函数给出的返回值)
BOOL GetExitCodeProcess(
HANDLE hProcess,
PDWORD * pdwExitCode);
如果进程依然在运行,那么这个函数返回STILL _ACTIVE。
4. 进程内核对象的状态变为有信号状态。
WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMillionseconds);
5. 进程内核对象的计数减一 。
6. 如果在进程中,直接调用ExitProcess或者TerminateProcess函数,那么C++类的销毁函数可能不会被调用!!!!
9.6 项目:自制CMD
#include <Windows.h> #include <stdio.h> int main() { WCHAR ws_Directory[MAX_PATH] ; WCHAR str_Command[MAX_PATH] = { 0 }; STARTUPINFO start_info = { 0 }; PROCESS_INFORMATION info = { 0 }; start_info.dwFillAttribute = sizeof(start_info); // 不区分大小写,比较输入的内容是否是quit,如果是就退出循环 while(CompareStringOrdinal(str_Command, 4, L"quit", 4, TRUE) != CSTR_EQUAL) { GetCurrentDirectoryW(MAX_PATH, (LPWSTR)ws_Directory); wprintf(L"%s : ", ws_Directory); _getws_s(str_Command, MAX_PATH); CreateProcessW(NULL, str_Command, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&start_info, &info); } wprintf(L"press any key to quit\n"); return 0; } |
父进程在创建完子进程后,立即关闭子进程的进程句柄就可以断绝父子关系了
十、用户提权
10.1 简介
用户权限的提升:
UAC (User Account Control, 用户帐户控制) 是微软为提高系统安全而在Windows Vista中引入的新技术。要关闭它,可以进行如下操作:
Windows登录账户后给session一个token (令牌),计算机中每启动一个进程,这个进程就代表你去工作,计算机需要给进程令牌。如果没有以管理员权限运行,给的就是默认的令牌,就是削减过权限的filter token
10.2 查看程序是否有管理员权限
1、获取到的权限枚举
进程权限的检查:
// pElevationType:当前的权限类型: typedef enum { TokenElevationTypeDefault, TokenElevationTypeFull, TokenElevationTypeLimited } TOKEN_ELEVATION_TYPE, *PTOKEN_ELEVATION_TYPE; |
TokenElevationTypeDefault:进程以缺省的用户身份运行,或者没有使用UAC功能。用户没有启动UAC功能,那么程序使用的令牌一定不是Filter Token
TokenElevationTypeFull:进程的权限已经被提升,进程令牌没有使用filter令牌
TokenElevapionTypelimited:进程使用的令牌是Filter令牌。此时通过GetTokenInformation函数,并使用TokenLinkedToken标志,来获取原令牌。
1. 先通过GetTokenInformation函数来确定当前程序的令牌是否位Filter Token
2. 如果是,那么通过GetTokenInformation函数,来获得FilterToken 的原始令牌。
3. 在获得原始令牌后,通过CheckTokenMembership函数确认在原始令牌中有没有管理员的帐号。如果有,那么说明启动当前程序的账户是管理员帐号,否则就不是。CheckTokenMembership确认一个SID是否在一个Token中可用。
4. 如果不是Flter Token,那么直接使用IsUserAnAdmin函数,来确定启动当前程序的帐号
是否为管理员帐号。
#include <Windows.h> #include <stdio.h> #include <shlobj.h> // 作用: 函数用于确认运行当前程序的账号是否为管理员账号 // 参数: pElevationType : 令牌提升类型 // pIsAdmin : 是否是管理员 // 返回: 函数返回TRUE代表函数执行成功 BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE * pElevationType, BOOL * pIsAdmin) { HANDLE hToken = NULL; DWORD dwSize; // 获得当前进程的令牌句柄 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return(FALSE); BOOL bResult = FALSE; // 看看这个令牌的权限提升类型是哪一种: TokenElevationTypeDefault、TokenElevationTypeFull、TokenElevapionTypelimited if (GetTokenInformation(hToken, TokenElevationType, pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) { // 创建一个管理员SID BYTE adminSID[SECURITY_MAX_SID_SIZE]; dwSize = sizeof(adminSID); CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID, &dwSize); if (*pElevationType == TokenElevationTypeLimited) { // 通过Filter Token来获得原始的Token HANDLE hUnfilteredToken = NULL ; // TokenLinkedToken标志,表示要获得Filter Token的原始Token GetTokenInformation(hToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize); // 检查原始Token中,管理员账户adminSID是否被激活,如果被激活,那么说明启动这个程序的账号是管理员账号 // CheckTokenMembership函数结果被保存在pIsAdmin参数中,函数的返回值表示这个函数是否执行成功 if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)) { bResult = TRUE; } // 不要忘记关闭原始令牌 CloseHandle(hUnfilteredToken); } else { // 如果是原始令牌,只要IsUserAnAdmin就可以确定,启动当前程序的账号是否是管理员账号 *pIsAdmin = IsUserAnAdmin(); bResult = TRUE; } } // 不要忘记关闭进程令牌 CloseHandle(hToken); return(bResult); } int main() { TOKEN_ELEVATION_TYPE Type; BOOL IsAdmin; BOOL ret = GetProcessElevation(&Type, &IsAdmin); if (ret) { wprintf(L"%d, Type\n"); if (IsAdmin) wprintf(L"On Admin running!\n"); else wprintf(L"On normal acount running!\n"); } getchar(); return 0; } |
10.3 程序权限调整
#include <Windows.h> #include <stdio.h> #include <processthreadsapi.h> // 作用: 函数将进程权限提升成具有调试权限的进程,这个权限应该是进程所能具有的最大权限 // 这个函数成功的前提,启动这个进程的账号必须是管理员账号 // 参数: fEnable : 为TRUE表示授予当前进程调试权限,为FALSE表示取消当前进程调试权限 // 返回: 函数返回TRUE代表函数调整权限成功 BOOL EnableDebugPrivilege(BOOL fEnable) { // 调试权限可以让该进程能够读取其他进程的信息 BOOL fok = FALSE; HANDLE hToken; // 获取当前进程的令牌 // 这个函数第一个参数是当前进程句柄,第二个参数是进程对获得的令牌有哪些操作权限 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { // 开始激活当前令牌的调试权限 TOKEN_PRIVILEGES tp; // 结构体,表示令牌权限 tp.PrivilegeCount = 1; // 我们启动调试权限,所以给1 // 查找调试权限的LVID,如果第一个参数是NULL,表示获取本地的某个权限的LVID // 函数LookupPrivilegeValue获取本地系统的调试的LVID LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid); // 在tp.Privileges[0].Attributes属性中设置开启调试权限还是关闭调试权限 // Attributes=SE_SE_PRIVILEGE_ENABLED时,激活权限 // Attributes=0时,关闭权限 tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0; // AdjustTokenPrivileges激活或者关闭tp中给定的权限 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL); // 确定是否激活成功 fok = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return(fok); } int main() { if (EnableDebugPrivilege(TRUE)) { wprintf(L"Enable Debug privilege is ok!\n"); } else { wprintf(L"Enable Debug privilege is Failure!\n"); } EnableDebugPrivilege(FALSE); getchar(); return 0; } |
10.4 以管理员权限启动其他程序
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = L"runas";
ShExecInfo.lpFile = L"cmd";
ShExecInfo.lpParameters = L"";
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
十一、获取所有进程和进程中DLL
11.1 获取所有进程
#include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <TlHelp32.h> int main(int argc, char* argv[]) { HANDLE hProcessSnap = NULL; PROCESSENTRY32 pe32 = {0}; //在使用这个结构前,先设置它的大小 pe32.dwSize = sizeof(PROCESSENTRY32); //给系统内所有的进程拍个快照 hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if (hProcessSnap == INVALID_HANDLE_VALUE) { printf_s("CreatToolhelp32Snapshot error!\n"); return -1; } //遍历进程快照,轮流显示每个进程的信息 BOOL bRet = ::Process32First(hProcessSnap,&pe32); while (bRet) { printf_s("进程名称:%ls\n",pe32.szExeFile); //这里得到的应该是宽字符,用%ls或者%S,不然无法正常打印 printf_s("进程ID %u\n\n",pe32.th32ProcessID); bRet = ::Process32Next(hProcessSnap,&pe32); } //不要忘记清除掉snapshot对象 ::CloseHandle(hProcessSnap); return 0; } |
11.2 获取所有DLL
class Dll { public: Dll(std::wstring name, std::wstring path); ~Dll(); std::wstring getDllName(void) const; std::wstring getDllPath(void) const; private: std::wstring dllName; std::wstring dllPath; }; |
bool GetDllFromProcess(std::vector<Dll>& dllVec, const DWORD& processId) { //创建系统快照,并返回进程句柄 HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId); if (handle == INVALID_HANDLE_VALUE) return false; //枚举进程模块 MODULEENTRY32 info; info.dwSize = sizeof(MODULEENTRY32); Module32First(handle, &info); while (Module32Next(handle, &info) != FALSE) { dllVec.emplace_back(Dll(std::wstring(info.szModule), std::wstring(info.szExePath))); } CloseHandle(handle); return true; } |