跳转至

用 IAT 钩子与游戏线程同步

有了清单 8-10 中的代码,钩住任何 Windows API 函数就像知道函数名和正确原型一样简单。Sleep() API 是游戏破解时常见的钩子目标,因为机器人可以用 Sleep() 钩子与游戏主循环进行线程同步。

你的注入代码不可避免地必须与游戏主循环同步,否则无法工作。例如,读取或写入大于 4 字节的数据时,不同步会让游戏与你同时读取或写入该数据。你会踩到游戏的脚趾,反之亦然,导致各种竞态条件和数据损坏问题。同样,如果你尝试从自己的线程调用游戏函数,如果函数不是线程安全的,就有崩溃游戏的风险。

由于 IAT 钩子是对 PE 头的线程安全修改,它们可以从任何线程放置。通过在游戏主循环之前或之后调用的函数上放置一个,你可以有效地与游戏主线程同步。你只需放置钩子,并从钩子回调执行任何对线程敏感的代码。

以下是用 hookIAT() 钩住 Sleep() API 的一种方式:

VOID WINAPI newSleepFunction(DWORD ms)
{
    // 做对线程敏感的事情
    originalSleep(ms);
}
typedef VOID (WINAPI _origSleep)(DWORD ms);
_origSleep* originalSleep =
    (_origSleep*)hookIAT("Sleep", 
(DWORD)&newSleepFunction);

这是它有效的原因。在游戏主循环结束时,它可能调用 Sleep() 休息,直到准备好绘制下一帧。由于它在睡觉,你可以安全地做任何想做的事情,而不用担心同步问题。有些游戏可能不这样做,或者可能从多个线程调用 Sleep(),这些游戏需要不同的方法。

更可移植的替代方案是钩住 PeekMessageA() API 函数,因为游戏经常在主循环中等待输入时调用该函数。然后,你的机器人可以在 PeekMessageA() 钩子内部执行对线程敏感的操作,确保它们从游戏主线程完成。你可能还希望你的机器人用这个方法钩住 send() 和 recv() API 函数,因为拦截它们可以让你相对简单地创建数据包嗅探器。