C++ 获取 64 位应用程序的模块基地址

Posted

技术标签:

【中文标题】C++ 获取 64 位应用程序的模块基地址【英文标题】:C++ Get Module Base Address for 64bit Application 【发布时间】:2014-10-26 11:28:09 【问题描述】:

我最近一直在玩内存读取/编辑,并遇到了一个我认为是由于 64 位应用程序的问题,我也尝试在 64 位下编译。 我对这个脚本使用 32 位应用程序没有任何问题,但是当我在纸牌上尝试这个时,它无法获得基地址,然后无法锻炼正确的偏移量等。 这是脚本:

#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;

DWORD dwGetModuleBaseAddress(DWORD dwProcessID, TCHAR *lpszModuleName)

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
    DWORD dwModuleBaseAddress = 0;
    if (hSnapshot != INVALID_HANDLE_VALUE)
    
        MODULEENTRY32 ModuleEntry32 =  0 ;
        ModuleEntry32.dwSize = sizeof(MODULEENTRY32);
        if (Module32First(hSnapshot, &ModuleEntry32))
        
            do
            
                if (_tcscmp(ModuleEntry32.szModule, lpszModuleName) == 0)
                
                    dwModuleBaseAddress = (DWORD)ModuleEntry32.modBaseAddr;
                    break;
                
             while (Module32Next(hSnapshot, &ModuleEntry32));
        
        CloseHandle(hSnapshot);
    
    return dwModuleBaseAddress;


int main()

    DWORD address = 0xBAFA8;
    HWND hwnd = FindWindow(0, L"Solitaire");
    DWORD pid;
    int data = 0;
    int newData = 0;
    if (hwnd)
    
        GetWindowThreadProcessId(hwnd, &pid);
        HANDLE phandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
        if (phandle)
        
            DWORD base = dwGetModuleBaseAddress(pid, L"Solitaire.exe");
            cout << "Base: " << (void*)base << endl;

            ReadProcessMemory(phandle, (LPCVOID)(base + address), &data, sizeof(data), 0);
        
        else 
            cout << "Couldnt get handle" << endl;
        

    
    else 
        cout << "Couldn't find window" << endl;
    
    cin.get();
    return 0;

问题可能是我使用的函数使用了 MODULE32,但是我尝试了其他函数(使用 EnumModules)仍然无法返回地址。

任何想法如何获得 64 位应用程序的基地址或让这个脚本工作?

谢谢

【问题讨论】:

这可能有用(注意讨论引用目标程序和寻找程序的位数)***.com/questions/17412545/… modBaseAddr 是一个指针。停止将其转换为 32 位整数。您知道在 64 位 Windows 上指针是 64 位宽的吗?您将无法从在 32 位 WOW64 模拟器下运行的 32 位进程查看 64 位进程。所以以 64 位运行是必不可少的。 【参考方案1】:

好吧,您的代码永远不会成功运行,因为您在谈论 64 位,但您使用 DWORD 作为基址! Solitare可能有一个 32 位地址,但你不能保证,你永远不应该假设它。

此功能有效。它只需要相关进程的进程 ID,并且假定您需要该进程的基地址。即不是它的 DLL 之一。如果您不想要拥有进程,则需要使用 for (int i=0; i&lt;moduleCount; i++ ) // do something with moduleArray[i] 之类的内容遍历 moduleArray,然后检查模块文件名。

如果您只想要启动进程(可执行文件),您可以假设它是数组中的第一个元素。

DWORD_PTR GetProcessBaseAddress( DWORD processID )

    DWORD_PTR   baseAddress = 0;
    HANDLE      processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
    HMODULE     *moduleArray;
    LPBYTE      moduleArrayBytes;
    DWORD       bytesRequired;

    if ( processHandle )
    
        if ( EnumProcessModules( processHandle, NULL, 0, &bytesRequired ) )
        
            if ( bytesRequired )
            
                moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired );

                if ( moduleArrayBytes )
                
                    unsigned int moduleCount;

                    moduleCount = bytesRequired / sizeof( HMODULE );
                    moduleArray = (HMODULE *)moduleArrayBytes;

                    if ( EnumProcessModules( processHandle, moduleArray, bytesRequired, &bytesRequired ) )
                    
                        baseAddress = (DWORD_PTR)moduleArray[0];
                    

                    LocalFree( moduleArrayBytes );
                
            
        

        CloseHandle( processHandle );
    

    return baseAddress;

【讨论】:

感谢那个工作脚本,我想这只能在 64 位上工作并且没有通用的解决方案? 我确实将代码编译为 64 位。我真的不知道你想对图像做什么。您可能可以将其编译为 32 位并对其进行处理,但您必须为任何指针类型(如基地址和任何偏移量)显式使用 64 位值。但是 64 位程序可以更轻松地访问 32 位或 64 位图像。【参考方案2】:

正如大卫所暗示的那样,这行是错误的:

dwModuleBaseAddress = (DWORD)ModuleEntry32.modBaseAddr;

因为您将 64 位指针(在 64 位应用程序上)投射到 DWORD 截断值(来自调试器的屏幕截图,64 位应用程序打开 64 位进程)

您应该选择指针类型(DWORD_PTR 也适用于便携式解决方案)。

【讨论】:

为什么是ULONGLONG?将指针存储在指针数据类型中。 unsigned char* 似乎很难被击败。 @DavidHeffernan 这是图像库的 64 类型。无论如何,DWORD_PTR 可能更便携。 所以我可以使用原来的功能,只需将其更改为强制转换 DWORD_PTR? 这将解决这个特定的截断问题,是的。 DWORD_PTR 被定义为 32 位代码上的 32 位指针和 64 位代码上的 64 位指针,因此它也应该是可移植的。我相信其他问题会阻止你从 32 位应用程序打开 64 位进程,但除此之外你应该很好。 我确实将其更改为强制转换 DWORD_PTR 并更改了 'DWORD dwModuleBaseAddress = 0;'到 DWORD_PTR 并且仍然无法让原始功能发挥作用?【参考方案3】:

基于@Nostromoo 给出的示例的示例控制台应用程序

应该在 x86/x64 上工作

#include "stdafx.h"
#include <windows.h>
#include <psapi.h>
#include <vector>

#define MODULE_NAME L"TestModule"
#define WINDOW_NAME L"TestConsole"

bool GetBaseModuleInfo(MODULEINFO* baseModuleInfo);
HMODULE GetProcessBaseAddressFromProcessHandle(HANDLE processHandle);
bool EnumProcessModulesPlattform(HANDLE processHandle, HMODULE* lphModule, DWORD cb, LPDWORD bytesRequired);
DWORD GetProcessIdentifier(const wchar_t* windowName);

int main()

    SetConsoleTitle(WINDOW_NAME);

    // Wait for the title to be set otherwise the window might not be found.
    Sleep(200);

    MODULEINFO baseModuleInfo;
    bool success = GetBaseModuleInfo(&baseModuleInfo);

    if (success == false) 
        wprintf(MODULE_NAME L"main() - GetBaseModuleInfo() failed.\n");
        std::getchar();
        return 1;
    

        wchar_t buffer[2000];
#ifdef _WIN64
        swprintf_s(buffer, sizeof(buffer) / sizeof(*buffer), MODULE_NAME L"main() - baseAddress: '%llX' size: '%lX'\n", baseModuleInfo.lpBaseOfDll, baseModuleInfo.SizeOfImage);
#elif _WIN32
        swprintf_s(buffer, sizeof(buffer) / sizeof(*buffer), MODULE_NAME L"main() - baseAddress: '%lX' size: '%lX'\n", baseModuleInfo.lpBaseOfDll, baseModuleInfo.SizeOfImage);
#endif
        wprintf(buffer);

    std::getchar();
    return 0;


bool GetBaseModuleInfo(MODULEINFO* baseModuleInfo)

    DWORD processID = GetProcessIdentifier(WINDOW_NAME);
    if (processID == NULL) 
        wprintf(MODULE_NAME L"GetBaseModuleInfo() - GetProcessIdentifier() returned NULL.\n");
        return false;
    

    HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
    if (processHandle == NULL) 
        wprintf(MODULE_NAME L"GetBaseModuleInfo() - OpenProcess() returned NULL.\n");
        return false;
    

    HMODULE baseAddress = GetProcessBaseAddressFromProcessHandle(processHandle);
    if (baseAddress == NULL) 
        wprintf(MODULE_NAME L"GetBaseModuleInfo() - GetProcessBaseAddressFromProcessHandle() returned NULL.\n");
        return false;
    

    bool resultGMI = GetModuleInformation(processHandle, baseAddress, baseModuleInfo, sizeof(*baseModuleInfo));
    // NOTE: GetModuleInformation doesn't fail even if the baseAddress is wrong. Maybe it uses the nearest module?
    if (resultGMI == false) 
        wprintf(MODULE_NAME L"GetBaseModuleInfo() - GetModuleInformation() failed.\n");
        return false;
    

    CloseHandle(processHandle);

    return true;


HMODULE GetProcessBaseAddressFromProcessHandle(HANDLE processHandle)

    DWORD bytesRequired;
    if (EnumProcessModulesPlattform(processHandle, NULL, 0, &bytesRequired) == false) 
        wprintf(MODULE_NAME L"GetProcessBaseAddressFromProcessHandle() - EnumProcessModules() error.\n");
        return NULL;
    

    if (bytesRequired == NULL) 
        wprintf(MODULE_NAME L"GetProcessBaseAddressFromProcessHandle() - EnumProcessModules() returned 0 bytesRequired.\n");
        return NULL;
    

    unsigned int moduleCount = bytesRequired / sizeof(HMODULE);

    std::vector<HMODULE> hModules(moduleCount, 0);
    if (EnumProcessModulesPlattform(processHandle, &hModules[0], bytesRequired, &bytesRequired) == false) 
        wprintf(MODULE_NAME L"GetProcessBaseAddressFromProcessHandle() - EnumProcessModules(moduleArray) error.\n");
        return NULL;
    

    // The first item is always the baseModule.
    HMODULE baseAddress = hModules[0];
    return baseAddress;


bool EnumProcessModulesPlattform(HANDLE processHandle, HMODULE* lphModule, DWORD cb, LPDWORD bytesRequired)

#ifdef _WIN64
    bool returnValue = EnumProcessModulesEx(processHandle, lphModule, cb, bytesRequired, LIST_MODULES_64BIT);
#elif _WIN32
    bool returnValue = EnumProcessModulesEx(processHandle, lphModule, cb, bytesRequired, LIST_MODULES_32BIT);
#endif

    DWORD lastError = GetLastError();
    if (lastError != 0) 
        wprintf(MODULE_NAME L"EnumProcessModulesPlattform() - lastError != 0 EnumProcessModulesEx().\n");
        return false;
    

    return returnValue;


DWORD GetProcessIdentifier(const wchar_t* windowName)

    HWND windowHandle = FindWindow(NULL, windowName);
    if (windowHandle == NULL) 
        wprintf(MODULE_NAME L"GetProcessIdentifier() - Could not find hwnd of '%s'.\n", windowName);
        return NULL;
    

    DWORD processID;
    GetWindowThreadProcessId(windowHandle, &processID);
    return processID;

【讨论】:

怎么想 - sizeof(baseModuleInfo.lpBaseOfDll) 的价值是什么?基于什么 ?它不是在编译时已经定义了吗? @RbMm 如果它是在 x86 上编译的,它是 4 字节,如果它是在 x64 上编译的,我是 8 字节。它是在编译时定义的......它可以与定义有关,但这部分只是为了展示该函数是如何使用的。 是的。所以这首先必须不是在运行时而是在编译时由#if 块选择。其次,如果您编译为 32 位代码 - 您不会获得 64 位模块基址。第三,您的解决方案效率不高,也不是最好的 我不明白你所说的“如果你编译为 32 位代码 - 你不会得到 64 位模块基址”是什么意思。高效:我不希望它在程序中经常运行,所以它足够高效。 我的意思是32位代码不能得到64位模块的基地址。 EnumProcessModules 在这种情况下失败

以上是关于C++ 获取 64 位应用程序的模块基地址的主要内容,如果未能解决你的问题,请参考以下文章

将 32 位 C++ 代码移植到 64 位 - 值得吗?为啥?

从 32 位进程获取 64 位进程的命令行字符串

在 MS VC 2013 Express 中将 C++ dll 从 32 位转换为 64 位

Linux x64 -- 内核程序(驱动程序)读取任意进程数据实现

尝试使用 64 位 MFC DLL 的 C++ 32 位应用程序

32位程序在64位系统上获取系统安装时间(要使用KEY_WOW64_64KEY标记)