如何从 32 位 WOW 进程枚举 64 位进程中的模块
-
25-09-2019 - |
题
我需要在 Windows 的 32 位 WOW 进程中检索 64 位进程的所有模块, 枚举进程模块 将失败,如下所述:
如果从在 WOW64 上运行的 32 位应用程序调用此函数,则它只能枚举 32 位进程的模块。如果进程是64位进程,则该函数失败,最后的错误代码为ERROR_PARTIAL_COPY (299)。
如此至EnumProcessModulesEx和CreateToolhelp32Snapshot。
您对如何实现它有任何想法吗?
谢谢。
解决方案
如果不使用未记录的 API,您就无法做到这一点。一般来说,由于地址空间差异,从 32 位进程读取 64 位进程的内存是行不通的。
EnumProcessModulesEx
, , 其中有 LIST_MODULES_32BIT
和 LIST_MODULES_64BIT
过滤标志,有这样的说法:
该函数主要用于 64 位应用程序。如果该函数由在 WOW64 下运行的 32 位应用程序调用,则忽略 dwFilterFlag 选项,并且该函数提供与 EnumProcessModules 函数相同的结果。
您可以通过使用进程外 64 位 COM 服务器(特别是使用 DLL代理),或者有一个与您通信的单独进程。或者,根据进程相对于目标进程的启动时间,您可以使用 WMI 来获取模块加载事件。请参阅 Win32_ModuleLoadTrace
事件。
流程浏览器, ,一个 32 位 exe,可以向您显示 32 位和 64 位进程的模块,但这确实是雾里看花:32 位 exe 包含其自身的 64 位版本,该版本会写入磁盘并在 64 位计算机上执行。
其他提示
您的请求的解决方案与以下任务有一些交叉点 从 x86 进程读取 x64 进程内存. 。主要是我们要注意的功能 NtWow64QueryInformationProcess64
和 NtWow64ReadVirtualMemory64
存在于 x86 中 ntdll.dll
专为从 x86 进程获取有关 x64 进程的信息而设计。
我们还应该了解操作系统结构之间的一些依赖关系。
PROCESS_BASIC_INFORMATION
包含地址 PEB
. PEB
代表进程环境块。它包含的地址 PEB_LDR_DATA
结构。它又包含第一个的地址 LDR_DATA_TABLE_ENTRY
结构中的 LIST_ENTRY
链。 LDR_DATA_TABLE_ENTRY
包含以下链接 LDR_DATA_TABLE_ENTRY
.
在 WinDbg 中检查此信息的示例:
0:000> !peb
PEB at 000007fffffdb000
...
0:000> dt ntdll!_peb 000007fffffdb000
...
+0x018 Ldr : 0x00000000`76fbd640 _PEB_LDR_DATA
...
0:000> dt ntdll!_PEB_LDR_DATA 76fbd640
...
+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x00000000`00415bb0 - 0x00000000`070eb9c0 ]
...
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY 00415bb0
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000000`00415ca0 - 0x00000000`76fbd650 ]
...
+0x030 DllBase : 0x00000001`3f4d0000 Void
...
+0x058 BaseDllName : _UNICODE_STRING "procexp64.exe"
...
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY 00415ca0
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000000`00416020 - 0x00000000`00415bb0 ]
...
+0x030 DllBase : 0x00000000`76e90000 Void
...
+0x058 BaseDllName : _UNICODE_STRING "ntdll.dll"
...
代码中要执行的步骤如下:
- 通过通常的调用获取进程句柄
OpenProcess
功能。 - 读
PROCESS_BASIC_INFORMATION
结构体调用NtWow64QueryInformationProcess64
. - 获取地址
PEB
它存在于PROCESS_BASIC_INFORMATION
结构。 - 读
PEB
结构体调用NtWow64ReadVirtualMemory64
. - 获取地址
PEB_LDR_DATA
结构。 - 读
PEB_LDR_DATA
结构并获取第一个的地址LDR_DATA_TABLE_ENTRY
元素。 - 继续阅读记忆
LDR_DATA_TABLE_ENTRY
元素,而下一个元素的地址不等于第一个元素的地址。
在第 7 步,我们还读取了缓冲区 UNICODE_STRING
(位于 LDR_DATA_TABLE_ENTRY
) 获取当前模块名称。
下面提供了代码。它由两个文件组成, main.cpp
和 os_structs.hpp
.
主程序:
#include "os_structs.hpp"
#include <algorithm>
#include <codecvt>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#ifndef WIN32
# error "This application must be built as an x86 executable"
#endif
#define GET_FUNC_ADDR(name) _##name name = (_##name)::GetProcAddress(::GetModuleHandleA("ntdll.dll"), #name)
#define IS_TRUE(clause, msg) if (!(clause)) { throw std::runtime_error(msg); }
namespace
{
struct close_on_exit
{
close_on_exit(HANDLE ptr)
: ptr_(ptr)
{ };
~close_on_exit()
{
if (ptr_)
{
::CloseHandle(ptr_);
ptr_ = nullptr;
}
}
private:
HANDLE ptr_;
};
// Names of modules
std::string convert_unicode_to_utf8(std::vector<uint8_t> &raw_bytes)
{
std::vector<uint16_t> unicode(raw_bytes.size() >> 1, 0);
memcpy(unicode.data(), raw_bytes.data(), raw_bytes.size());
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
const std::wstring wide_string(unicode.begin(), unicode.end());
const std::string utf8_string = converter.to_bytes(wide_string);
return utf8_string;
}
void *get_handle(uint32_t id)
{
HANDLE handle = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, id);
std::cout << "Opening target process...";
IS_TRUE(NULL != handle, "OpenProcess failed");
std::cout << " ok" << std::endl;
return handle;
}
void check_if_process_is_x64(HANDLE handle)
{
BOOL is_wow64_process = TRUE;
IS_TRUE(::IsWow64Process(handle, &is_wow64_process), "IsWow64Process failed");
IS_TRUE(FALSE == is_wow64_process, "Target process is not x64 one");
}
std::vector<uint8_t> read_mem(HANDLE handle, uint64_t address, uint32_t length)
{
IS_TRUE(handle, "No process handle obtained");
std::vector<uint8_t> data(length, 0);
GET_FUNC_ADDR(NtWow64ReadVirtualMemory64);
NTSTATUS status = NtWow64ReadVirtualMemory64(handle, address, data.data(), data.size(), FALSE);
IS_TRUE(NT_SUCCESS(status), "NtWow64ReadVirtualMemory64 failed");
return data;
}
void read_pbi(HANDLE handle, sys::PROCESS_BASIC_INFORMATION64 &pbi)
{
IS_TRUE(handle, "No process handle obtained");
GET_FUNC_ADDR(NtWow64QueryInformationProcess64);
NTSTATUS status = NtWow64QueryInformationProcess64(handle, sys::ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
IS_TRUE(NT_SUCCESS(status), "NtQueryInformationProcess failed");
}
std::vector<uint8_t> read_peb_data(HANDLE handle)
{
sys::PROCESS_BASIC_INFORMATION64 pbi = { 0 };
read_pbi(handle, pbi);
return read_mem(handle, pbi.PebBaseAddress, sizeof(sys::PEB64));
}
bool get_modules_load_order_via_peb(HANDLE handle)
{
std::cout << "Getting module load order...\n" << std::endl;
std::vector<uint8_t> read_peb = read_peb_data(handle);
sys::PEB64 *peb = (sys::PEB64 *)read_peb.data();
// ------------------------------------------------------------------------
// Read memory from pointer to loader data structures.
// ------------------------------------------------------------------------
std::vector<uint8_t> read_peb_ldr_data = read_mem(handle, (uintptr_t)peb->LoaderData, sizeof(sys::PEB_LDR_DATA64));
sys::PEB_LDR_DATA64 *peb_ldr_data = (sys::PEB_LDR_DATA64 *)read_peb_ldr_data.data();
sys::PEB_LDR_DATA64 *loader_data = (sys::PEB_LDR_DATA64 *)peb->LoaderData;
const uintptr_t addr_of_ptr_to_first_ldr_module = (uintptr_t)loader_data
+ ((uintptr_t)&loader_data->InLoadOrderModuleList - (uintptr_t)&loader_data->Length);
ULONGLONG address = peb_ldr_data->InLoadOrderModuleList.Flink;
uint32_t counter = 1;
// ------------------------------------------------------------------------
// Traversing loader data structures.
// ------------------------------------------------------------------------
do
{
std::vector<uint8_t> read_ldr_table_entry = read_mem(handle, address, sizeof(sys::LDR_DATA_TABLE_ENTRY64));
sys::LDR_DATA_TABLE_ENTRY64 *ldr_table_entry = (sys::LDR_DATA_TABLE_ENTRY64 *)read_ldr_table_entry.data();
std::vector<uint8_t> unicode_name = read_mem(handle, ldr_table_entry->BaseDllName.Buffer, ldr_table_entry->BaseDllName.MaximumLength);
std::string name = convert_unicode_to_utf8(unicode_name);
std::cout << "Module: " << name << std::endl;
std::cout << " Image base: 0x" << std::hex << ldr_table_entry->BaseAddress << std::endl;
ldr_table_entry = (sys::LDR_DATA_TABLE_ENTRY64 *)read_ldr_table_entry.data();
address = (uintptr_t)ldr_table_entry->InLoadOrderModuleList.Flink;
} while (addr_of_ptr_to_first_ldr_module != address);
std::cout << "\nEnumeration finished" << std::endl;
return true;
}
} // namespace
int main()
{
try
{
HANDLE handle = get_handle(16944);
close_on_exit auto_close_handle(handle);
check_if_process_is_x64(handle);
get_modules_load_order_via_peb(handle);
}
catch (const std::runtime_error &e)
{
std::cerr << "\n----------------------------------------------------\n";
std::cerr << "Exception occurred: " << e.what();
std::cerr << "\n----------------------------------------------------\n";
}
return 0;
}
os_structs.hpp:
#pragma once
#include <windows.h>
#define NT_SUCCESS(x) ((x) >= 0)
// Namespace is present Not to collide with "winbase.h"
// definition of PROCESS_INFORMATION_CLASS and others.
namespace sys
{
typedef enum _PROCESS_INFORMATION_CLASS {
ProcessBasicInformation,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass
} PROCESS_INFORMATION_CLASS, *PPROCESS_INFORMATION_CLASS;
// ------------------------------------------------------------------------
// Structs.
// ------------------------------------------------------------------------
typedef struct _PROCESS_BASIC_INFORMATION64 {
ULONGLONG Reserved1;
ULONGLONG PebBaseAddress;
ULONGLONG Reserved2[2];
ULONGLONG UniqueProcessId;
ULONGLONG Reserved3;
} PROCESS_BASIC_INFORMATION64;
typedef struct _PEB_LDR_DATA64 {
ULONG Length;
BOOLEAN Initialized;
ULONGLONG SsHandle;
LIST_ENTRY64 InLoadOrderModuleList;
LIST_ENTRY64 InMemoryOrderModuleList;
LIST_ENTRY64 InInitializationOrderModuleList;
} PEB_LDR_DATA64, *PPEB_LDR_DATA64;
// Structure is cut down to ProcessHeap.
typedef struct _PEB64 {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN Spare;
ULONGLONG Mutant;
ULONGLONG ImageBaseAddress;
ULONGLONG LoaderData;
ULONGLONG ProcessParameters;
ULONGLONG SubSystemData;
ULONGLONG ProcessHeap;
} PEB64;
typedef struct _UNICODE_STRING64 {
USHORT Length;
USHORT MaximumLength;
ULONGLONG Buffer;
} UNICODE_STRING64;
typedef struct _LDR_DATA_TABLE_ENTRY64 {
LIST_ENTRY64 InLoadOrderModuleList;
LIST_ENTRY64 InMemoryOrderModuleList;
LIST_ENTRY64 InInitializationOrderModuleList;
ULONGLONG BaseAddress;
ULONGLONG EntryPoint;
DWORD64 SizeOfImage;
UNICODE_STRING64 FullDllName;
UNICODE_STRING64 BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY64 HashTableEntry;
ULONGLONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY64, *PLDR_DATA_TABLE_ENTRY64;
} // namespace sys
// ------------------------------------------------------------------------
// Function prototypes.
// ------------------------------------------------------------------------
typedef NTSTATUS(NTAPI *_NtWow64QueryInformationProcess64)(
IN HANDLE ProcessHandle,
ULONG ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);
typedef NTSTATUS(NTAPI *_NtWow64ReadVirtualMemory64)(
IN HANDLE ProcessHandle,
IN DWORD64 BaseAddress,
OUT PVOID Buffer,
IN ULONG64 Size,
OUT PDWORD64 NumberOfBytesRead);
如果您对初始结构定义感兴趣 - 我想出的最好方法是下载 WinDbg 的符号,然后在此调试器中观察结构的布局。您可以在上面这篇文章中看到示例。
使用Windows Management Instrumentation(WMI)。实施例(DELPHI):
function GetProcessCount(const aFileName: string): Integer;
var
lValue: LongWord;
lWMIService: OleVariant;
lWMIItems: OleVariant;
lWMIItem: OleVariant;
lWMIEnum: IEnumVariant;
begin
Result := -1;
lWMIService := GetWMIObject('winmgmts:\\.\root\CIMV2'); { Do not localize. }
if (TVarData(lWMIService).VType = varDispatch) and (TVarData(lWMIService).VDispatch <> nil) then
begin
Result := 0;
lWMIItems := lWMIService.ExecQuery(Format('SELECT * FROM Win32_Process WHERE Name=''%s''', [ExtractFileName(aFileName)])); { Do not localize. }
lWMIEnum := IUnknown(lWMIItems._NewEnum) as IEnumVariant;
while lWMIEnum.Next(1, lWMIItem, lValue) = 0 do
begin
Inc(Result);
end;
end;
end;