跳转至

如何进行 NOP 填充

NOP 填充一块汇编代码的第一步是让代码所在的内存块可写。不过,写入 NOP 命令时,同一内存页面上的代码可能正在执行,所以你还想确保内存仍然可执行。你可以通过把内存保护设置为 PAGE_EXECUTE_READWRITE 同时完成这两件事。内存保护正确后,你可以写入 NOP 命令,大功告成。让内存保持可写在技术上无害,但完成后恢复原始保护是好习惯。

假设你有第 6 章描述的写入和保护内存的工具,你可以编写像清单 8-2 所示的函数,向游戏内存写入 NOP 命令。(打开项目的 NOPExample.cpp 文件跟着做。)

template<int SIZE>
void writeNop(DWORD address)
    auto oldProtection =
        protectMemory<BYTE[SIZE]>(address, 
PAGE_EXECUTE_READWRITE);
    for (int i = 0; i < SIZE; i++)
        writeMemory<BYTE>(address + i, 0x90);
    protectMemory<BYTE[SIZE]>(address, 
oldProtection);
}

清单 8-2:正确的 NOP 填充,包含内存保护

在这个示例中,writeNop() 函数设置合适的内存保护,写入等于 SIZE 的 NOP 命令数量,并重新应用原始内存保护级别。

writeNop() 函数把要放置的 NOP 指令数量作为模板参数,因为内存函数在编译时需要一个大小正确的类型。传入整数 SIZE 告诉内存函数在编译时操作 BYTE[SIZE] 类型的对象。要在运行时指定动态大小,只需去掉循环,改为调用 protectMemory 并把 address 和 address + SIZE 作为参数传递。只要大小不超过一个页面(实际上,你不应该 NOP 填充整个页面),即使代码在页面边界上,这也能确保内存得到正确保护。

用要放置 NOP 的地址和要放置的 NOP 命令数量调用这个函数:

writeNop<2>(0xDEADBEEF);

请记住,NOP 命令的数量应该与要移除命令的字节大小匹配。这次对 writeNop() 的调用向地址 0xDEADBEEF 写入两条 NOP 命令。