在应用层下,进程遍历有多种方式,这里介绍几种常用的方式:进程快照、NtQuerySystemInformation、EnumProcesses函数、WMI等。
在C#中Process类提供了一个GetProcesses()函数,这个函数内部就是调用的NtQuerySystemInformation进行获取。
进程快照
这种方式比较简单方便。主要涉及以下几个函数
CreateToolhelp32Snapshot(TlHelp32.h)函数,获取进程信息为指定的进程、进程使用的堆、模块、线程建立一个快照。
函数声明如下:
1 HANDLE 2 WINAPI 3 CreateToolhelp32Snapshot( 4 DWORD dwFlags, 5 DWORD th32ProcessID 6 );
参数说明:
dwFlags
值 | 含义 |
---|---|
TH32CS_INHERIT 0x80000000 | 指示快照句柄是可继承的。 |
TH32CS_SNAPALL | 包括系统中的所有进程和线程,以及 th32ProcessID 中指定的进程的堆和模块。 等效于指定使用 OR 操作 (“|”组合的TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPPROCESS和TH32CS_SNAPTHREAD值) 。 |
TH32CS_SNAPHEAPLIST 0x00000001 | 包括快照 th32ProcessID 中指定的进程的所有堆。 若要枚举堆,请参阅 Heap32ListFirst。 |
TH32CS_SNAPMODULE 0x00000008 | 包括快照 th32ProcessID 中指定的进程的所有模块。 若要枚举模块,请参阅 Module32First。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。 64 位 Windows: 在 32 位进程中使用此标志包括 th32ProcessID 中指定的进程的 32 位模块,而在 64 位进程中使用它包括 64 位模块。 若要从 64 位进程包括 th32ProcessID 中指定的进程的 32 位模块,请使用 TH32CS_SNAPMODULE32 标志。 |
TH32CS_SNAPMODULE32 0x00000010 | 从 64 位进程调用时,包括快照中 th32ProcessID 中指定的进程的所有 32 位模块。 此标志可以与 TH32CS_SNAPMODULE 或 TH32CS_SNAPALL结合使用。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。 |
TH32CS_SNAPPROCESS 0x00000002 | 包括系统中快照中的所有进程。 若要枚举进程,请参阅 Process32First。 |
TH32CS_SNAPTHREAD 0x00000004 | 包括快照系统中的所有线程。 若要枚举线程,请参阅 Thread32First。 若要标识属于特定进程的线程,请在枚举线程时将其进程标识符与 THREADENTRY32 结构的 th32OwnerProcessID 成员进行比较。 |
枚举进程的话使用TH32CS_SNAPPROCESS就可以了,也可以枚举其它的内容,可以自行尝试。
th32ProcessID
指定将要快照的进程ID。如果该参数为零,则表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或者
TH32CS_SNAPMODULE后才有效,在其他情况下应忽略该参数,快照所有的进程。
返回值:
成功,返回快照句柄
失败,返回INVALID_HANDLE_VALUE
注意,进程枚举完成后需要CloseHandle关闭快照句柄。
Process32First函数,用于检索系统快照中遇到的第一个进程信息。在调用CreateToolhelp32Snapshot成功后,就可以调用这个函数来获取第一个进程信息。函数声明如下:
1 BOOL 2 WINAPI 3 Process32FirstW( 4 HANDLE hSnapshot, 5 LPPROCESSENTRY32W lppe 6 );
参数说明:
hSnapshot
CreateToolhelp32Snapshot函数返回的快照句柄
lppe
指向PROCESSENTRY32结构的指针。
PROCESSENTRY32结构定义如下:
1 typedef struct tagPROCESSENTRY32W2 {3 DWORD dwSize; // size4 DWORD cntUsage; // obselete(keep 0)5 DWORD th32ProcessID; // this process6 ULONG_PTR th32DefaultHeapID; // obselete(keep 0)7 DWORD th32ModuleID; // associated exe8 DWORD cntThreads; // The number of execution threads started by the process.9 DWORD th32ParentProcessID; // this process's parent process 10 LONG pcPriClassBase; // Base priority of process's threads 11 DWORD dwFlags; // obselete(keep 0) 12 WCHAR szExeFile[MAX_PATH]; // Path 13 } PROCESSENTRY32W;
返回值:
TRUE,进程列表的第一个条目已经复制到缓冲区
FALSE,失败,调用GetLastError()获取错误信息
Process32Next函数,检索系统快照中记录的下一个进程信息。这个函数的声明和Process32First一样,使用方法也一样,这是在第一个进程的基础上,继续向下遍历 。
返回值:
TRUE,进程列表的下一个条目已经复制到缓冲区
FALSE,如果不存在任何进程或者快照不包含进程信息,则GetLastError函数会返回ERROR_NO_MORE_FILES错误值。
遍历流程比较简单:
1、调用CreateToolhelp32Snapshot创建进程快照,设置标志为TH32CS_SNAPPROCESS
2、调用Process32First函数获取第一个进程信息
3、使用一个while循环来调用Process32Next函数获取后面的进程信息
4、当Process32Next返回FALSE,退出循环
5、关闭快照句柄
示例代码如下:
1 int main()2 {3 PROCESSENTRY32 pe32{};4 pe32.dwSize = sizeof(PROCESSENTRY32);5 6 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);7 8 if (INVALID_HANDLE_VALUE == hProcessSnap)9 { 10 return 0; 11 } 12 13 BOOL bRet = Process32First(hProcessSnap, &pe32); 14 15 while (bRet) 16 { 17 //对进程进行操作 18 19 bRet = Process32Next(hProcessSnap, &pe32); 20 } 21 CloseHandle(hProcessSnap); 22 return 0; 23 }
NtQuerySystemInformation
NtQuerySystemInformation用于检索指定的系统信息,它可以检索多种系统信息,它的类型在SYSTEM_INFORMATION_CLASS 枚举中定义。我们这里只演示如何获取进程信息。
函数声明如下:
1 NTSTATUS WINAPI NtQuerySystemInformation( 2 _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, 3 _Inout_ PVOID SystemInformation, 4 _In_ ULONG SystemInformationLength, 5 _Out_opt_ PULONG ReturnLength 6 );
参数说明:
SystemInformationClass
要检索的系统信息的类型。 此参数可以是 SYSTEM_INFORMATION_CLASS 枚举类型的值之一。
这里我们只使用SystemProcessInformation值
SystemInformation
指向接收请求信息的缓冲区的指针,这个类型的大小和结构会因SystemInformationClass参数而异。
也就是说,对应的类型需要对应类型的缓冲区。我们这里SYSTEM_INFORMATION_CLASS使用的是SystemProcessInformation值,
所以这个参数传递的是SYSTEM_PROCESS_INFORMATION类型,定义如下:
这个类型在winternl.h中定义了
1 typedef struct _SYSTEM_PROCESS_INFORMATION {2 ULONG NextEntryOffset;3 ULONG NumberOfThreads;4 BYTE Reserved1[48];5 UNICODE_STRING ImageName;6 KPRIORITY BasePriority;7 HANDLE UniqueProcessId;8 PVOID Reserved2;9 ULONG HandleCount; 10 ULONG SessionId; 11 PVOID Reserved3; 12 SIZE_T PeakVirtualSize; 13 SIZE_T VirtualSize; 14 ULONG Reserved4; 15 SIZE_T PeakWorkingSetSize; 16 SIZE_T WorkingSetSize; 17 PVOID Reserved5; 18 SIZE_T QuotaPagedPoolUsage; 19 PVOID Reserved6; 20 SIZE_T QuotaNonPagedPoolUsage; 21 SIZE_T PagefileUsage; 22 SIZE_T PeakPagefileUsage; 23 SIZE_T PrivatePageCount; 24 LARGE_INTEGER Reserved7[6]; 25 } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
SystemInformationLength
SystemInformation 参数指向的缓冲区的大小(以字节为单位)。
ReturnLength
指向函数写入所请求信息的实际大小的位置的可选指针。 如果该大小小于或等于 SystemInformationLength 参数,该函数会将信息复制到 SystemInformation 缓冲区中;否则,它将返回 NTSTATUS 错误代码,并在 ReturnLength 中返回接收请求的信息所需的缓冲区大小。
说明:在实际使用中,最后一个参数我们可以不去使用,但是前提是分配的空间够大。
返回值
NTSTATUS,需要查看DDK中的Ntstatus.h来查看错误代码,可以用NT_SUCCESS宏来判断是否成功。
按照官方文档的建议,使用NtQuerySystemInformation需要自己从Ntdll.dll中导入这个函数。
遍历流程如下:
1、导入函数
2、调用NtQuerySystemInformation,将SystemInformationClass参数设置为SystemProcessInformation
3、获取第一个进程信息
4、创建一个while循环,通过SYSTEM_PROCESS_INFORMATION的NextEntryOffset值来获取下一个进程信息的偏移量,通过计算得出下一个进程信息
5、当NextEntryOffset为NULL,退出循环,结束遍历
示例代码如下:
1 #include <Windows.h>2 #include <winternl.h>3 #include<strsafe.h>4 #include<iostream>5 6 typedef long(__stdcall* funNtQuerySystemInformation)(UINT, PVOID, ULONG, PULONG);7 8 int main()9 { 10 NTSTATUS status; 11 PVOID buffer; 12 PSYSTEM_PROCESS_INFORMATION pspi; 13 14 //分配足够大的空间 15 buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 16 17 if (!buffer) 18 { 19 //分配内存失败 20 //GetLastError 21 return 0; 22 } 23 24 HMODULE hModule = LoadLibrary(L"ntdll.dll"); 25 26 if (!hModule) 27 { 28 //加载dll失败 29 //GetLastError 30 return 0; 31 } 32 33 funNtQuerySystemInformation NtQuerySystemInformation = (funNtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation"); 34 35 pspi = (PSYSTEM_PROCESS_INFORMATION)buffer; 36 37 if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemProcessInformation, pspi, 1024 * 1024, NULL))) 38 { 39 //查询进程列表失败 40 VirtualFree(buffer, 0, MEM_RELEASE); 41 return 0; 42 } 43 44 while (pspi->NextEntryOffset) // Loop over the list until we reach the last entry. 45 { 46 //在这里添加处理逻辑 47 //...... 48 TCHAR buf[128]{}; 49 StringCchCopy(buf, pspi->ImageName.Length, pspi->ImageName.Buffer); 50 std::wcout << buf << std::endl; 51 52 //计算下一条目的地址 53 pspi = (PSYSTEM_PROCESS_INFORMATION)((LPBYTE)pspi + pspi->NextEntryOffset); 54 } 55 56 //释放分配的空间 57 VirtualFree(buffer, 0, MEM_RELEASE); 58 59 return 0; 60 }
EnumProcesses函数
使用EnumProcesses函数可以枚举全部进程的id,然后通过pid再获取进程详细信息。
EnumProcesses声明如下:
1 BOOL EnumProcesses( 2 [out] DWORD *lpidProcess, 3 [in] DWORD cb, 4 [out] LPDWORD lpcbNeeded 5 );
参数说明:
lpidProcess
指向接收进程标识符列表的数组(pProcessIds )的指针。
cb
pProcessIds 数组的大小(以字节为单位)。
lpcbNeeded
pProcessIds 数组中返回的字节数。
返回值:
非0,成功
0,失败,调用GetLastError来获取详细信息
示例代码如下:
1 #include <iostream>2 #include<Windows.h>3 #include<tchar.h>4 #include<Psapi.h>5 6 int main()7 {8 DWORD processesArray[1024], cbNeeded, processesCount;9 10 if (!EnumProcesses(processesArray, sizeof(processesArray), &cbNeeded)) 11 { 12 return 0; 13 } 14 15 processesCount = cbNeeded / sizeof(DWORD); 16 17 18 for (int i = 0; i < processesCount; i++) 19 { 20 TCHAR szProcessName[MAX_PATH] = L"<unknown>"; 21 22 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 23 FALSE, processesArray[i]); 24 25 if (NULL != hProcess) 26 { 27 HMODULE hMod; 28 29 if (EnumProcessModulesEx(hProcess, &hMod, sizeof(hMod), &cbNeeded, LIST_MODULES_ALL)) 30 { 31 GetModuleBaseName(hProcess, hMod, szProcessName, 32 sizeof(szProcessName) / sizeof(TCHAR)); 33 34 std::wcout << szProcessName << std::endl; 35 36 CloseHandle(hProcess); 37 } 38 39 } 40 } 41 }
使用WMI
WMI的使用可以参考我前面的文章
https://www.cnblogs.com/zhaotianff/p/14764740.html
1 #include <iostream>2 #include <WbemIdl.h>3 #include<Windows.h>4 #include <comdef.h>5 #include<vector>6 7 #pragma comment(lib,"wbemuuid.lib")8 9 int main()10 {11 HRESULT hr;12 std::vector<VARIANT> bb{};13 14 hr = CoInitializeEx(0, COINIT_MULTITHREADED);15 if (FAILED(hr))16 {17 std::cout << "COM初始化失败.错误码 = 0x" << std::hex << hr << std::endl;18 return 0;19 }20 21 hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);22 if (FAILED(hr))23 {24 std::cout << "初始化安全性失败.错误码 = 0x" << std::hex << hr << std::endl;25 }26 27 IWbemLocator* pLoc = NULL;28 hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);29 if (FAILED(hr))30 {31 std::cout << "创建IWbemLocator对象失败.错误码 = 0x" << std::hex << hr << std::endl;32 CoUninitialize();33 return 0;34 }35 36 IWbemServices* pSvc = NULL;37 38 hr = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);39 if (FAILED(hr))40 {41 std::cout << "连接WMI失败.错误码 = 0x" << std::hex << hr << std::endl;42 pLoc->Release();43 CoUninitialize();44 return 0;45 }46 47 IEnumWbemClassObject* pEnumerator = NULL;48 hr = pSvc->ExecQuery(_bstr_t(L"WQL"), _bstr_t(L"Select * from win32_process"),49 WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,50 NULL,51 &pEnumerator);52 53 if (FAILED(hr))54 {55 std::cout << "查询win32_process失败.错误码 = 0x" << std::hex << hr << std::endl;56 pSvc->Release();57 pLoc->Release();58 CoUninitialize();59 return 0;60 }61 62 IWbemClassObject* pclsObj = NULL;63 ULONG uReturn = 0;64 65 while (pEnumerator)66 {67 hr = pEnumerator->Next(WBEM_INFINITE, 1,68 &pclsObj, &uReturn);69 70 if (0 == uReturn)71 {72 break;73 }74 75 if (pclsObj->BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY) == WBEM_S_NO_ERROR)76 {77 BSTR name = NULL;78 VARIANT vtProp;79 VariantInit(&vtProp);80 81 while (pclsObj->Next(0, &name, &vtProp, 0, 0) == WBEM_S_NO_ERROR)82 {83 bb.push_back(vtProp);84 85 //这里暂时只输出字符串86 if (vtProp.vt == VT_NULL || vtProp.vt != VT_BSTR)87 continue;88 89 //std::wcout << name << "\t" << vtProp.bstrVal << std::endl;90 }91 92 SysFreeString(name);93 VariantClear(&vtProp);94 }95 pclsObj->Release();96 }97 98 pEnumerator->Release();99 pSvc->Release(); 100 pLoc->Release(); 101 CoUninitialize(); 102 103 return 0; 104 }
示例代码
https://github.com/zhaotianff/WindowsProgramming/tree/master/Process
https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_EnumProcesses
https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_ZwQuerySystemInformation
https://github.com/zhaotianff/WindowsProgramming/tree/master/Process_WMI
参考资料
Using NtQuerySystemInformation to get process list - Programming - rohitab.com - Forums
枚举所有进程 - Win32 apps | Microsoft Learn