逆向工程实战:我是如何通过Hook SHGetFolderPathW给Euro Truck Simulator 2 Mod“搬家”的
逆向工程实战Hook SHGetFolderPathW实现欧卡2 Mod路径重定向当50GB的Mod文件塞满系统盘时每个《欧洲卡车模拟2》ETS2玩家都会面临一个抉择是忍受C盘爆红的痛苦还是挑战游戏程序的路径限制本文将揭示如何通过逆向工程破解这个难题从API拦截到内存补丁带你深入Windows系统调用的核心战场。1. 逆向工程的目标与思路Mod路径重定向本质上是对程序行为的深度定制。游戏启动时会通过SHGetFolderPathW获取我的文档路径随后拼接出Euro Truck Simulator 2\mod子目录。我们的目标是劫持这个过程使其返回自定义路径。技术路线对比方案实现难度稳定性通用性直接修改游戏二进制★★★★★★注入DLL实现API Hook★★★★★★★★★★内存实时补丁★★★★★★★★★★选择SHGetFolderPathW作为突破口是因为它是Windows获取特殊文件夹的标准API调用层级较浅易于追踪参数结构简单易于伪造提示在逆向工程中优先选择系统API而非游戏自有函数进行拦截能显著降低后续维护成本。2. 动态分析与关键定位使用x64dbg附加到运行中的eurotrucks2.exe进程通过API调用链追踪定位核心逻辑。2.1 设置硬件断点# 在x64dbg控制台执行 bp Shell32.SHGetFolderPathW当断点触发时观察栈帧和寄存器状态RCX: 0x00000005 (CSIDL_PERSONAL) RDX: 0x00000000 R8: 0x1A3FEFF820 (输出缓冲区)2.2 调用链回溯通过栈回溯发现关键调用序列1. eurotrucks2.exe!0x1400D6E8A (GetDocumentPathWithEts2) 2. eurotrucks2.exe!0x1400E54C0 (GetDirPath) 3. Shell32.dll!SHGetFolderPathW在IDA中分析0x1400D6E8A处的函数发现其核心逻辑mov rcx, 5 ; CSIDL_PERSONAL lea rdx, [rsp28h] call GetDirPath lea rcx, [rsp28h] lea rdx, aEuroTruckSim ; \\Euro Truck Simulator 2 call PathAppendW2.3 路径拼接点定位继续追踪发现最终路径处理函数.text:0000000140047DB0 lea rdx, aSMod ; %s/mod/ .text:0000000140047DB7 mov r8, [rdi1A8h] ; 原始路径 .text:0000000140047DBE call sprintf此处[rdi1A8h]正是我们要修改的关键内存地址。3. Hook方案实现3.1 DLL注入方案创建注入DLL使用MinHook库实现API拦截#include Windows.h #include pathcch.h #include ShlObj.h #include MinHook.h typedef HRESULT(WINAPI* SHGetFolderPathW_t)( HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath); SHGetFolderPathW_t original_SHGetFolderPathW nullptr; wchar_t customPath[MAX_PATH] L; HRESULT WINAPI Hook_SHGetFolderPathW( HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath) { if (csidl CSIDL_PERSONAL) { wcscpy_s(pszPath, MAX_PATH, customPath); return S_OK; } return original_SHGetFolderPathW(hwnd, csidl, hToken, dwFlags, pszPath); } BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { if (reason DLL_PROCESS_ATTACH) { MH_Initialize(); MH_CreateHook(SHGetFolderPathW, Hook_SHGetFolderPathW, reinterpret_castvoid**(original_SHGetFolderPathW)); MH_EnableHook(MH_ALL_HOOKS); // 从配置文件读取自定义路径 GetPrivateProfileStringW(LSettings, LModPath, L, customPath, MAX_PATH, L.\\ModRedirector.ini); } return TRUE; }3.2 内存补丁方案针对特定版本游戏的直接修改# 使用pywin32实现内存补丁 import win32process import win32con PROCESS_ALL_ACCESS (0x000F0000 | 0x00100000 | 0xFFF) pid 1234 # 通过进程名获取 h_process win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid) # 定位关键指令地址 patch_addr 0x140047DB7 new_path_addr 0x1401F0000 # 在.rdata段空白处写入新路径 # 构造汇编指令mov r8, new_path_addr patch_bytes b\x49\xB8 new_path_addr.to_bytes(8, little) win32process.WriteProcessMemory(h_process, patch_addr, patch_bytes, None)3.3 方案对比测试在不同游戏版本上的测试结果游戏版本DLL Hook内存补丁备注1.40✔✔补丁需更新偏移地址1.41✔✘代码段地址变化1.42✔✔需重新定位补丁位置1.43✔✘新增了路径校验逻辑4. 进阶技巧与异常处理4.1 多线程安全处理游戏可能在工作线程调用API需要添加线程同步CRITICAL_SECTION cs; InitializeCriticalSection(cs); HRESULT WINAPI Hook_SHGetFolderPathW(...) { EnterCriticalSection(cs); HRESULT result original_SHGetFolderPathW(...); LeaveCriticalSection(cs); return result; }4.2 路径重定向验证# 使用Process Monitor验证 Filter: Process Name - eurotrucks2.exe Operation - CreateFile Path - ends with .scs4.3 异常处理机制__try { return original_SHGetFolderPathW(...); } __except(EXCEPTION_EXECUTE_HANDLER) { Log(API调用异常); return E_FAIL; }5. 工程化实现建议对于希望长期维护Mod系统的开发者推荐采用模块化设计配置管理模块支持INI/JSON配置热重载机制版本适配模块特征码扫描定位关键函数自动偏移计算日志系统文件/控制台双输出详细错误记录// 示例版本自适应扫描 DWORD FindPattern(HMODULE module, const BYTE* pattern, const char* mask) { MODULEINFO info; GetModuleInformation(GetCurrentProcess(), module, info, sizeof(info)); BYTE* scanStart (BYTE*)module; BYTE* scanEnd scanStart info.SizeOfImage - strlen(mask); for (BYTE* p scanStart; p scanEnd; p) { bool found true; for (size_t i 0; i strlen(mask); i) { if (mask[i] x pattern[i] ! p[i]) { found false; break; } } if (found) return (DWORD)p; } return 0; }在实际项目中这种技术方案不仅解决了Mod路径问题更为后续的游戏行为定制打下了基础。通过Hook系统API我们实际上构建了一个轻量级的游戏运行时干预框架为后续开发更多实用功能提供了可能。