创建汇编代码洞穴¶
在第一百二十八页「生产中绕过 ASLR」中,我通过 CreateRemoteThread() 用线程注入调用 GetModuleHandle() 函数并获取进程句柄。在那个例子中,GetModuleHandle() 充当代码洞穴;它有适当的代码结构充当新线程的入口点。不过,线程注入并不总是那么容易。
例如,假设你想让你的外部机器人在游戏内远程调用一个函数,而该函数有这样的原型:
有几件事让远程调用这个函数变得棘手。首先,它有两个参数,意味着你需要创建一个既能设置栈又能正确发起调用的代码洞穴。CreateRemoteThread() 允许你向代码洞穴传递一个参数,你可以相对于 ESP 访问该参数,但另一个参数仍然需要硬编码到代码洞穴中。硬编码第一个参数 times 最容易。此外,你需要确保代码洞穴正确清理栈。
注 回想第 6 章绕过 ASLR 时,我用 CreateRemoteThread()通过在任何给定地址执行任意代码并向该代码传递单个参数来启动新线程。这就是为什么这些示例可以通过栈传递一个参数。
最终,要把对 someFunction 的调用注入正在运行的游戏进程,代码洞穴看起来像这样的伪代码:
这个代码洞穴几乎完美,但可以更简单。CALL 操作期望两种操作数之一:要么是带有绝对函数地址的寄存器,要么是保存到函数偏移量(相对于返回地址)的立即整数。这意味着你必须做一堆偏移量计算,这可能很繁琐。
为了让洞穴与位置无关,修改它使用寄存器,如清单 7-1 所示。
清单 7-1:调用 someFunction 的代码洞穴
由于调用者知道它调用的函数会用返回值覆盖 EAX,调用者应该确保 EAX 不保存任何关键数据。知道这一点,你可以用 EAX 保存 someFunction 的绝对地址。