更改内存保护¶
当你想要破解游戏时,有时需要以内存页面保护禁止的方式访问内存,因此能够随意更改内存保护非常重要。好在 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,确保你能操作内存,同时仍然允许游戏执行它。