跳转至

更改内存保护

当你想要破解游戏时,有时需要以内存页面保护禁止的方式访问内存,因此能够随意更改内存保护非常重要。好在 Windows 提供了 VirtualProtectEx() 函数。这是该函数的原型:

BOOL VirtualProtectEx(
    HANDLE Process, LPVOID Address,
    DWORD Size, DWORD NewProtect,
    PDWORD OldProtect
);

参数 Process、Address 和 Size 接受与 ReadProcessMemory()和 WriteProcessMemory() 函数相同的输入。NewProtect 应该指定内存的新保护标志,OldProtect 可以可选地指向一个 DWORD,旧保护标志会存储在其中。

内存保护最细的粒度是每页,这意味着 VirtualProtectEx()会把新保护设置到 Address 和 Address + Size - 1 之间或之上的每个页面。

 VirtualProtectEx() 函数有一个姊妹函数 VirtualProtect()。它们的工作方式相同,但 VirtualProtect() 只操作调用它的进程,因此没有进程句柄参数。

编写自己的更改内存保护代码时,我建议通过创建模板让它更灵活。VirtualProtectEx() 的通用包装函数应该看起来像清单 6-7。

template<typename T>
DWORD protectMemory(HANDLE proc, LPVOID adr, DWORD 
prot) {
    DWORD oldProt;
    VirtualProtectEx(proc, adr, sizeof(T), prot, 
&oldProt);
    return oldProt;
}

清单 6-7:更改内存保护的通用函数

有了这个模板,如果你想,比如说,把 DWORD 写入没有写权限的内存页面,你可能会这样做:

protectMemory<DWORD>(process, address, 
PAGE_READWRITE)
writeMemory<DWORD>(process, address, newValue)

首先,这把要更改的内存的保护设置为 PAGE_READWRITE。有了写权限,就可以调用 writeMemory()更改 address 处的数据。

更改内存保护时,最佳实践是让更改只持续所需时间,并尽快恢复原始保护。这样效率较低,但能确保游戏不会检测到你的机器人(例如,通过注意到它的一些汇编代码页面变得可写)。

典型的只读内存写入操作应该看起来像这样:

DWORD oldProt =
    protectMemory<DWORD>(process, address, 
PAGE_READWRITE);
writeMemory<DWORD>(process, address, newValue);
protectMemory<DWORD>(process, address, oldProt);

这段代码调用清单 6-7 中的 protectMemory() 函数,把保护更改为 PAGE_READWRITE。然后它把 newValue 写入内存,再把保护改回 oldProt,oldProt 由第一次调用 protectMemory()设置为页面的原始保护。这里使用的 writeMemory() 函数与清单 6-5 中定义的相同。

最后一点重要的是,操纵游戏内存时,游戏完全可能同时访问该内存。如果你设置的新保护与原始保护不兼容,游戏进程会得到 ACCESS_VIOLATION 异常并崩溃。例如,如果你把内存保护从 PAGE_EXECUTE 改为 PAGE_READWRITE,游戏可能会在内存未标记为可执行时尝试执行页面上的代码。这种情况下,你应该把内存保护设置为 PAGE_EXECUTE_READ WRITE,确保你能操作内存,同时仍然允许游戏执行它。