Windows平台下C语言自修改代码无法写入补丁的问题排查
解决自修改代码无法写入内存的问题
我帮你梳理下代码里的几个关键问题,这些问题导致你无法成功修改say_hi函数的内存:
1. 替换字节串的语法错误
你定义的REPLACE宏里有个明显的拼写错误:
#define REPLACE "\x55\x48\x89\xe5\x90\x5d\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90x\90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xc3"
这里的\x90x\90是错误的,应该改成\x90\x90——你把十六进制转义符写错了,这会导致替换的字节串长度和内容完全不符合预期,自然无法正确覆盖目标函数。
2. 未检查关键API的返回值
你调用VirtualProtect时没有验证是否成功,如果内存权限修改失败,后续的CopyMemory根本无法写入内存。同样,OpenProcess也可能失败,导致FlushInstructionCache无法正常执行。
建议在调用这些系统API后立即检查返回值,比如:
if (!VirtualProtect((exe + patternOffset), LEN, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf("VirtualProtect失败,错误码: %d\n", GetLastError()); return; }
3. 特征码匹配的可靠性问题
你的SearchPattern函数循环上限硬设为0x200000,如果你的模块实际大小超过这个值,很可能会错过目标函数。另外,编译器的优化选项(比如/O2)会改变say_hi函数的汇编代码,导致你定义的PATTERN和实际生成的机器码不匹配。
你可以先通过调试器查看say_hi函数实际的机器码,再调整PATTERN和LEN的值。比如默认编译下say_hi的汇编可能是:
say_hi: push rbp mov rbp, rsp sub rsp, 20h lea rcx, offset string "hi!" call printf add rsp, 20h pop rbp ret
对应的机器码是否和你定义的PATTERN完全一致?如果编译器添加了额外的指令(比如栈对齐操作),特征码就会匹配失败。
4. 刷新指令缓存的简化写法
你不需要调用OpenProcess来获取当前进程的句柄,直接用GetCurrentProcess()即可,这样更简单且不会出错:
if (!FlushInstructionCache(GetCurrentProcess(), (LPCVOID)(exe + patternOffset), LEN)) { printf("FlushInstructionCache失败,错误码: %d\n", GetLastError()); }
修复后的完整代码示例
这里是修正后的代码,解决了上述问题:
#include <windows.h> #include <psapi.h> #include <stdio.h> // 确保这个特征码和say_hi实际的机器码一致,建议用调试器确认 #define PATTERN "\x55\x48\x89\xe5\x48\x83\xec\x20\x48\x8d\x0d\x91\x2a\x00\x00\xe8\x3c\x16\x00\x00\x90\x48\x83\xc4\x20\x5d\xc3" // 修正了拼写错误的替换串,逻辑是:push rbp; mov rbp, rsp; nop; pop rbp; 多nop填充后直接ret #define REPLACE "\x55\x48\x89\xe5\x90\x5d\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xc3" #define LEN 27 DWORD SearchPattern(unsigned char* mem, unsigned char* signature, DWORD signatureLen) { // 获取模块实际大小,避免循环上限不够 MODULEINFO modInfo; GetModuleInformation(GetCurrentProcess(), (HMODULE)mem, &modInfo, sizeof(modInfo)); DWORD modSize = modInfo.SizeOfImage; for (DWORD i = 0; i < modSize - signatureLen; i++) { // 先匹配前两个字节再全匹配,优化性能 if (*(unsigned char*)(mem + i) == signature[0] && *(unsigned char*)(mem + i + 1) == signature[1]) { if (memcmp(mem + i, signature, signatureLen) == 0) { return i; } } } return 0; } void patch_mem() { DWORD oldProtect, oldOldProtect; unsigned char *exe = (unsigned char *)GetModuleHandle("patching_tests.exe"); if (!exe) { printf("获取模块基址失败,错误码: %d\n", GetLastError()); return; } DWORD patternOffset = SearchPattern(exe, (unsigned char*)PATTERN, LEN); if (patternOffset == 0) { printf("查找偏移失败,返回\n"); return; } printf("偏移地址: %#010x\n", (exe + patternOffset)); // 修改内存权限并检查返回值 if (!VirtualProtect((exe + patternOffset), LEN, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf("VirtualProtect失败,错误码: %d\n", GetLastError()); return; } // 写入补丁 CopyMemory((exe + patternOffset), REPLACE, LEN); // 恢复内存权限 if (!VirtualProtect((exe + patternOffset), LEN, oldProtect, &oldOldProtect)) { printf("恢复内存权限失败,错误码: %d\n", GetLastError()); } // 刷新指令缓存 if (!FlushInstructionCache(GetCurrentProcess(), (LPCVOID)(exe + patternOffset), LEN)) { printf("FlushInstructionCache失败,错误码: %d\n", GetLastError()); } return; } int say_hi() { printf("hi!\n"); return 0; // 补充返回值,避免编译器警告 } int main() { printf("调用say_hi...\n"); say_hi(); printf("内存补丁中...\n"); patch_mem(); printf("再次调用say_hi...\n"); say_hi(); return 0; }
额外建议
- 编译时关闭优化选项(比如在VS中选择Debug模式,或者Release模式下禁用
/O2),这样say_hi的汇编代码更稳定,特征码更容易匹配。 - 如果后续需要处理更复杂的特征码,可以支持通配符(比如用
?表示任意字节),这样能兼容编译器的微小差异。
内容的提问来源于stack exchange,提问作者Dylan halls
相关产品推荐
相关产品推荐

