You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:50:02