如何从32位WOW进程中枚举64位进程中的模块

我有一个要求在Windows 32位WOW进程中检索一个64位进程的所有模块, EnumProcessModules将失败,如下所述:

如果从在WOW64上运行的32位应用程序调用此函数,则只能枚举32位进程的模块。 如果进程是64位进程,则此函数失败,最后一个错误代码为ERROR_PARTIAL_COPY(299)。

至于EnumProcessModulesEx和CreateToolhelp32Snapshot。

你有什么想法如何实现它?

谢谢。

不要进入无证的API,你不能这样做。 一般来说,由于地址空间的差异,从32位进程读取64位进程的内存将不起作用。

具有LIST_MODULES_32BITLIST_MODULES_64BIT过滤器标志的LIST_MODULES_64BIT有这样的说法:

此功能主要用于64位应用程序。 如果该函数由在WOW64下运行的32位应用程序调用,则dwFilterFlag选项将被忽略,该函数将提供与EnumProcessmodulees函数相同的结果。

您可以通过将程序转换为64位,使用64位以外的COM服务器(特别是使用DLL代理 )或者使用独立的进程来进行通信。 或者,根据您的流程何时开始相对于您的目标流程,您可以使用WMI来获取模块加载事件。 请参阅Win32_moduleeLoadTrace事件。

Process Explorer是一个32位的exe文件,可以显示32位和64位进程的模块,但这真是烟雾缭绕:32位exe文件包含一个64位版本的文件,它被写入磁盘并在64位机器上执行。

使用Windows管理规范(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; 

您的请求的解决方案与从x86进程读取x64进程内存的任务有一些交集。 主要是,我们应该知道在x86 ntdll.dll中存在的函数NtWow64QueryInformationProcess64NtWow64ReadVirtualMemory64 ,它们是专门为从x86获取有关x64进程的信息而设计的。

我们也应该知道OS结构之间的一些依赖关系。

PROCESS_BASIC_INFORMATION包含PEB地址。 PEB代表过程环境块。 它包含PEB_LDR_DATA结构的地址。 它又包含LIST_ENTRY链中第一个LDR_DATA_TABLE_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 InLoadOrdermoduleeList : _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" ... 

在代码中采取的步骤如下:

  1. 通常调用OpenProcess函数获取进程句柄。
  2. 通过调用NtWow64QueryInformationProcess64来读取PROCESS_BASIC_INFORMATION结构。
  3. 获取PROCESS_BASIC_INFORMATION结构中存在的PEB地址。
  4. 通过调用NtWow64ReadVirtualMemory64读取PEB结构。
  5. 获取PEB_LDR_DATA结构的地址。
  6. 读取PEB_LDR_DATA结构并获取第一个LDR_DATA_TABLE_ENTRY元素的地址。
  7. 继续读取LDR_DATA_TABLE_ENTRY元素的内存,而下一个元素的地址不等于第一个元素的地址。

在第7步,我们还读取UNICODE_STRING缓冲区(驻留在LDR_DATA_TABLE_ENTRY )以获取当前的模块名称。

代码如下所示。 它由两个文件main.cppos_structs.hpp

main.cpp

 #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(::GetmoduleeHandleA("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->InLoadOrdermoduleeList - (uintptr_t)&loader_data->Length); ULONGLONG address = peb_ldr_data->InLoadOrdermoduleeList.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 << "modulee: " << 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->InLoadOrdermoduleeList.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 InLoadOrdermoduleeList; LIST_ENTRY64 InMemoryOrdermoduleeList; LIST_ENTRY64 InInitializationOrdermoduleeList; } 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 InLoadOrdermoduleeList; LIST_ENTRY64 InMemoryOrdermoduleeList; LIST_ENTRY64 InInitializationOrdermoduleeList; 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的符号,然后在这个调试器中观察结构的布局。 你可以在上面的这篇文章中看到这个例子。