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

时间:2010-09-27 06:24:50

标签: windows process 32bit-64bit wow64

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

  

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

EnumProcessModulesEx和CreateToolhelp32Snapshot。

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

感谢。

3 个答案:

答案 0 :(得分:16)

不进入未记录的API,您无法执行此操作。通常,由于地址空间不同,从32位进程读取64位进程的内存将不起作用。

EnumProcessModulesEx,其中包含LIST_MODULES_32BITLIST_MODULES_64BIT过滤器标记,可以这样说:

  

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

您可以通过将程序转换为64位,使用out-of-proc 64位COM服务器(特别是使用DLL surrogate),或者使用与之通信的单独进程来完成此操作。或者,根据您的进程相对于目标进程的启动时间,您可以使用WMI来获取模块加载事件。请参阅Win32_ModuleLoadTrace事件。

Process Explorer,一个32位的exe,可以显示32位和64位进程的模块,但它确实是冒烟和镜像:32位exe包含自身的64位版本它被写入磁盘并在64位计算机上执行。

答案 1 :(得分:4)

您的请求的解决方案与reading x64 process memory from x86 process的任务有一些交集。主要是,我们应该了解x86 NtWow64QueryInformationProcess64中存在的函数NtWow64ReadVirtualMemory64ntdll.dll,它们专门用于从x86获取有关x64进程的信息。

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

PROCESS_BASIC_INFORMATION包含PEB的地址。 PEB代表Process Environment Block。它包含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"
...

代码中采取的步骤如下:

  1. 通过调用OpenProcess函数获取进程句柄。
  2. 通过调用PROCESS_BASIC_INFORMATION阅读NtWow64QueryInformationProcess64结构。
  3. 获取PEB结构中存在的PROCESS_BASIC_INFORMATION的地址。
  4. 通过调用PEB阅读NtWow64ReadVirtualMemory64结构。
  5. 获取PEB_LDR_DATA结构的地址。
  6. 阅读PEB_LDR_DATA结构并获取第一个LDR_DATA_TABLE_ENTRY元素的地址。
  7. 继续读取LDR_DATA_TABLE_ENTRY元素的内存,而下一个元素的地址不等于第一个元素的地址。
  8. 在步骤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(::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下载符号,然后在此调试器中观察结构的布局。您可以在上面的这篇文章中看到示例。

答案 2 :(得分:1)

使用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;