跳至主要內容

33-进程枚举 - EnumProcesses

Maldevacademy大约 5 分钟安全开发

简介

此前进程枚举的一种方法已在使用 CreateToolHelp32Snapshot 的进程注入模块中演示过。此模块将演示使用 EnumProcesses 进行进程枚举的另一种方法。

对于恶意软件作者来说,能够用多种方式在恶意软件中实现一种技术对于在动作上保持不可预测性至关重要。

EnumProcesses

请先查看 Microsoft 关于 EnumProcessesopen in new window 的文档。请注意此函数将进程 ID (PID) 作为数组返回,不包含相关进程名。问题在于只使用 PID,而没有相关进程名,难以让人识别进程。

解决方案是使用 WinAPI 中的 OpenProcessopen in new windowGetModuleBaseNameopen in new windowEnumProcessModulesopen in new window

  1. OpenProcess 将用于使用 PROCESS_QUERY_INFORMATIONPROCESS_VM_READ 访问权限打开与 PID 相关的句柄。

  2. EnumProcessModules 将用于枚举已打开进程中的所有模块。这是步骤 3 所必需的。

  3. GetModuleBaseName 将根据步骤 2 中枚举的进程模块,确定进程的名称。

EnumProcesses 优势

采用 CreateToolhelp32Snapshot 进程枚举方法时,会创建一个快照并执行字符串比较,以确定进程名称是否与预期目标进程匹配。该方法的问题是,当具有不同权限级别的进程有多个实例在运行时,没有办法在字符串比较期间对它们进行区分。例如,有些 svchost.exe 进程以普通用户权限运行,而另一些以提升的权限运行。在字符串比较期间无法确定 svchost.exe 的权限级别。因此,关于它是否有权限的唯一指示符是,如果 OpenProcess 调用失败(假设实施以普通用户权限运行)。

另一方面,使用 EnumProcesses 进程枚举方法会提供进程的 PID 和句柄,目的是获取进程名称。此方法保证成功,因为已存在进程的句柄。

代码演练

本部分将讲解基于 Microsoft 示例open in new window 的进程枚举的代码片段。

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
image

获取远程进程句柄 - 示例

image
image