33-进程枚举 - EnumProcesses
简介
此前进程枚举的一种方法已在使用 CreateToolHelp32Snapshot
的进程注入模块中演示过。此模块将演示使用 EnumProcesses
进行进程枚举的另一种方法。
对于恶意软件作者来说,能够用多种方式在恶意软件中实现一种技术对于在动作上保持不可预测性至关重要。
EnumProcesses
请先查看 Microsoft 关于 EnumProcesses 的文档。请注意此函数将进程 ID (PID) 作为数组返回,不包含相关进程名。问题在于只使用 PID,而没有相关进程名,难以让人识别进程。
解决方案是使用 WinAPI 中的 OpenProcess、GetModuleBaseName 和 EnumProcessModules。
OpenProcess
将用于使用PROCESS_QUERY_INFORMATION
和PROCESS_VM_READ
访问权限打开与 PID 相关的句柄。EnumProcessModules
将用于枚举已打开进程中的所有模块。这是步骤 3 所必需的。GetModuleBaseName
将根据步骤 2 中枚举的进程模块,确定进程的名称。
EnumProcesses 优势
采用 CreateToolhelp32Snapshot
进程枚举方法时,会创建一个快照并执行字符串比较,以确定进程名称是否与预期目标进程匹配。该方法的问题是,当具有不同权限级别的进程有多个实例在运行时,没有办法在字符串比较期间对它们进行区分。例如,有些 svchost.exe
进程以普通用户权限运行,而另一些以提升的权限运行。在字符串比较期间无法确定 svchost.exe
的权限级别。因此,关于它是否有权限的唯一指示符是,如果 OpenProcess
调用失败(假设实施以普通用户权限运行)。
另一方面,使用 EnumProcesses
进程枚举方法会提供进程的 PID 和句柄,目的是获取进程名称。此方法保证成功,因为已存在进程的句柄。
代码演练
本部分将讲解基于 Microsoft 示例 的进程枚举的代码片段。
PrintProcesses 函数
PrintProcesses
是一个自定义函数,用于打印遍历到的进程的进程名称和 PID。只有与实现权限相同的进程才能检索其信息。再次假设实现以普通用户权限运行,则无法检索到提升权限进程的信息。使用 OpenProcess
尝试打开高权限进程的句柄会导致 ERROR_ACCESS_DENIED
错误。
可以使用 OpenProcess
的响应作为指示,以确定是否可以将进程作为目标。无法打开句柄的进程不能作为目标,而成功打开句柄的进程可以作为目标。
BOOL PrintProcesses() {
DWORD adwProcesses[1024 * 2],
dwReturnLen1 = NULL,
dwReturnLen2 = NULL,
dwNmbrOfPids = NULL;
HANDLE hProcess = NULL;
HMODULE hModule = NULL;
WCHAR szProc[MAX_PATH];
// 获取 PID 数组
if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen1)) {
printf("[!] EnumProcesses 失败,错误代码:%d\n", GetLastError());
return FALSE;
}
// 计算数组中元素的数量
dwNmbrOfPids = dwReturnLen1 / sizeof(DWORD);
printf("[i] 检测到的进程数:%d\n", dwNmbrOfPids);
for (int i = 0; i < dwNmbrOfPids; i++) {
// 如果进程不为 NULL
if (adwProcesses[i] != NULL) {
// 打开进程句柄
if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, adwProcesses[i])) != NULL) {
// 如果句柄有效
// 获取进程“hProcess”中模块的句柄
// 模块句柄是“GetModuleBaseName”需要的
if (!EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwReturnLen2)) {
printf("[!] EnumProcessModules 失败 [PID:%d],错误代码:%d\n", adwProcesses[i], GetLastError());
}
else {
// 如果 EnumProcessModules 成功
// 获取“hProcess”的名称并将其保存到“szProc”变量中
if (!GetModuleBaseName(hProcess, hModule, szProc, sizeof(szProc) / sizeof(WCHAR))) {
printf("[!] GetModuleBaseName 失败 [PID:%d],错误代码:%d\n", adwProcesses[i], GetLastError());
}
else {
// 打印进程名称及其 PID
wprintf(L"[%0.3d] 进程 \"%s\" - PID: %d\n", i, szProc, adwProcesses[i]);
}
}
// 关闭进程句柄
CloseHandle(hProcess);
}
}
// 遍历 PID 数组
}
return TRUE;
}
获得远程进程句柄函数
以下代码片段是对之前的 PrintProcesses
函数的更新。GetRemoteProcessHandle
将执行与 PrintProcesses
相同的任务,但它将返回一个指向指定进程的句柄。
更新后的函数使用 wcscmp
来验证目标进程。此外,OpenProcess
的访问控制从 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
更改为 PROCESS_ALL_ACCESS
,以提供对返回的进程对象的更多访问权限。
BOOL GetRemoteProcessHandle(LPCWSTR szProcName, DWORD* pdwPid, HANDLE* phProcess) {
DWORD adwProcesses[1024 * 2],
dwReturnLen1 = NULL,
dwReturnLen2 = NULL,
dwNmbrOfPids = NULL;
HANDLE hProcess = NULL;
HMODULE hModule = NULL;
WCHAR szProc[MAX_PATH];
// 获取 PID 数组
if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen1)) {
printf("[!] EnumProcesses 失败,错误:%d \n", GetLastError());
return FALSE;
}
// 计算数组中的元素数量
dwNmbrOfPids = dwReturnLen1 / sizeof(DWORD);
printf("[i] 检测到的进程数:%d \n", dwNmbrOfPids);
for (int i = 0; i < dwNmbrOfPids; i++) {
// 如果进程不为 NULL
if (adwProcesses[i] != NULL) {
// 打开一个进程句柄
if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, adwProcesses[i])) != NULL) {
// 如果句柄有效
// 获取进程'hProcess'中的模块句柄。
// 模块句柄对于'GetModuleBaseName'是必需的
if (!EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwReturnLen2)) {
printf("[!] EnumProcessModules 失败 [在 Pid:%d ]错误:%d \n", adwProcesses[i], GetLastError());
}
else {
// 如果 EnumProcessModules 成功
// 获取'hProcess'的名称并将其保存在'szProc'变量中
if (!GetModuleBaseName(hProcess, hModule, szProc, sizeof(szProc) / sizeof(WCHAR))) {
printf("[!] GetModuleBaseName 失败 [在 Pid:%d ]错误:%d \n", adwProcesses[i], GetLastError());
}
else {
// 执行比较逻辑
if (wcscmp(szProcName, szProc) == 0) {
wprintf(L"[+] 找到 \"%s\" - Pid:%d \n", szProc, adwProcesses[i]);
// 通过引用返回
*pdwPid = adwProcesses[i];
*phProcess = hProcess;
break;
}
}
}
CloseHandle(hProcess);
}
}
}
// 检查 pdwPid 或 phProcess 是否为 NULL
if (*pdwPid == NULL || *phProcess == NULL)
return FALSE;
else
return TRUE;
}
PrintProcesses - 示例
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/enumprocesses-108501303-c0dfa0d8-5e73-431e-9f5f-3cea0bb217be.png)
获取远程进程句柄 - 示例
![image](https://maldevacademy.s3.amazonaws.com/images/Intermediate/enumprocesses-208500959-341d233b-4852-463e-8108-6d6e4c109416.png)