Windows编程系列:进程遍历的几种方法

在应用层下,进程遍历有多种方式,这里介绍几种常用的方式:进程快照、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,请重试该函数,直到成功。
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 tagPROCESSENTRY32W
 2 {
 3     DWORD   dwSize;                     // size
 4     DWORD   cntUsage;                   // obselete(keep 0)
 5     DWORD   th32ProcessID;              // this process
 6     ULONG_PTR th32DefaultHeapID;        // obselete(keep 0)
 7     DWORD   th32ModuleID;               // associated exe
 8     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

 

 

参考资料:

http://www.rohitab.com/discuss/topic/40504-using-ntquerysysteminformation-to-get-process-list/

https://learn.microsoft.com/zh-cn/windows/win32/psapi/enumerating-all-processes

posted @ 2024-03-20 17:41  zhaotianff  阅读(169)  评论(0编辑  收藏  举报