C++修复GetGameFunctionAddress实现64位模块基址获取兼容
64位EXE模块基址获取兼容修复方案
原有实现直接依赖GetModuleHandleA获取EXE模块基址,在64位进程下会因为文件名匹配规则、路径格式、参数大小写等问题返回空值,导致后续地址计算错误、Hook崩溃。以下是可直接替换的兼容实现,同时支持32/64位进程场景。
修复后完整代码
#include <windows.h> #include <tlhelp32.h> #include <string> #include <cstdlib> #include <cstring> #include <cctype> // 内部工具:不区分大小写匹配模块名,自动忽略路径前缀 static bool MatchModuleName(const char* loadedModulePath, const char* targetExePath) { if (!loadedModulePath || !targetExePath) return false; // 提取路径末尾的纯文件名 auto extractFileName = [](const char* fullPath) -> const char* { const char* split1 = strrchr(fullPath, '\\'); const char* split2 = strrchr(fullPath, '/'); const char* splitPos = split1 > split2 ? split1 : split2; return splitPos ? splitPos + 1 : fullPath; }; const char* loadedName = extractFileName(loadedModulePath); const char* targetName = extractFileName(targetExePath); // 逐字符小写比对,适配Windows文件名大小写不敏感规则 while (*loadedName && *targetName) { if (tolower((unsigned char)*loadedName) != tolower((unsigned char)*targetName)) return false; loadedName++; targetName++; } return *loadedName == 0 && *targetName == 0; } __int64 GetGameFunctionAddress(std::string GameFileExe, std::string Address) { uintptr_t moduleBase = 0; // 通过进程模块快照枚举基址,32/64位通用 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); if (hSnapshot != INVALID_HANDLE_VALUE) { MODULEENTRY32A moduleEntry; moduleEntry.dwSize = sizeof(moduleEntry); if (Module32FirstA(hSnapshot, &moduleEntry)) { do { if (MatchModuleName(moduleEntry.szModule, GameFileExe.c_str()) || MatchModuleName(moduleEntry.szExePath, GameFileExe.c_str())) { moduleBase = (uintptr_t)moduleEntry.modBaseAddr; break; } } while (Module32NextA(hSnapshot, &moduleEntry)); } CloseHandle(hSnapshot); } // 兜底逻辑:快照枚举失败时重试GetModuleHandleA if (moduleBase == 0) { HMODULE hModule = GetModuleHandleA(GameFileExe.c_str()); if (!hModule) { // 传入全路径时自动提取文件名重试 const char* split1 = strrchr(GameFileExe.c_str(), '\\'); const char* split2 = strrchr(GameFileExe.c_str(), '/'); const char* splitPos = split1 > split2 ? split1 : split2; if (splitPos) hModule = GetModuleHandleA(splitPos + 1); } moduleBase = (uintptr_t)hModule; } // 基址获取失败直接返回空,避免生成野地址 if (moduleBase == 0) return 0; // 用strtoull解析偏移,兼容64位大地址场景,避免strtoul的32位截断问题 uintptr_t funcRva = (uintptr_t)std::strtoull(Address.c_str(), NULL, 16); return (__int64)(moduleBase + funcRva); }
关键修复说明
- 替换原有分架构分支的实现,统一用模块快照枚举逻辑获取基址,加
TH32CS_SNAPMODULE32标志支持64位进程下的模块枚举场景,完全规避64位下GetModuleHandleA的匹配失败问题 - 新增文件名自动匹配逻辑:自动剥离传入路径的目录前缀、不区分大小写比对模块名,解决传入全路径、文件名大小写不一致导致的查找失败
- 偏移解析从
strtoul替换为strtoull,解决64位下RVA偏移超过32位长度时的截断问题 - 增加多层兜底和空值校验,基址获取失败时直接返回0,避免计算出非法内存地址导致Detours Hook时进程崩溃
- 原有调用逻辑、Detours对接代码不需要做任何修改,直接替换函数实现即可生效
使用注意事项
64位下使用Detours Hook时,需要确认返回的函数地址所在内存页具备可执行权限,且目标函数长度大于Detours要求的最小跳转桩长度(通常大于5字节),否则会出现Hook失败或内存访问异常。
内容的提问来源于stack exchange,提问作者user3449922
相关产品推荐
相关产品推荐

