跳转至

击败基于签名的检测

即使有惊人的混淆,你也不会轻易击败签名检测。分析机器人并编写签名的工程师非常熟练,混淆最多只是让他们的工作稍微困难一点的麻烦。

要完全规避 SBD,你需要破坏检测代码。这需要确切知道 SBD 如何工作。例如,PunkBuster 用 NtQueryVirtualMemory()扫描所有运行进程的内存寻找任何签名。如果你想绕过它,你可以通过钩住 NtQueryVirtualMemory() 函数向所有 PunkBuster 进程注入代码。函数尝试查询你的机器人进程的内存时,你可以给它任何你想要的数据,像这样:

NTSTATUS onNtQueryVirtualMemory(
    HANDLE process, PVOID baseAddress,
    MEMORY_INFORMATION_CLASS 
memoryInformationClass,
    PVOID buffer, ULONG numberOfBytes, PULONG 
numberOfBytesRead) {
    // 如果扫描针对此进程,确保它看不到钩子 DLL
    if ((process == INVALID_HANDLE_VALUE ||
        process == GetCurrentProcess()) &&
        baseAddress >= MY_HOOK_DLL_BASE &&
        baseAddress <= MY_HOOK_DLL_BASE_PLUS_SIZE)
      return STATUS_ACCESS_DENIED;
    // 如果扫描针对机器人,把返回的内存清零
    auto ret = origNtQueryVirtualMemory(
        process, baseAddress,
        memoryInformationClass,
        buffer, numberOfBytes, numberOfBytesRead);
    if(GetProcessId(process) == MY_BOT_PROCESS)
      ZeroMemory(buffer, numberOfBytesRead);
    return ret;
}

这个 onNtQueryVirtualMemory() 钩子在 NtQueryVirtualMemory() 尝试查询钩子 DLL 的内存时返回 STATUS_ACCESS_DENIED①,但在 NtQueryVirtualMemory()尝试查询机器人内存时给出清零的内存②。这里只是展示两种向 NtQueryVirtualMemory() 函数调用隐藏的方法。如果你真的多疑,你甚至可以用随机字节序列替换整个缓冲区。

当然,这种方法只对发生在用户模式的 SBD 有效,比如 PunkBuster 或 VAC 中的 SBD。发生在驱动程序的 SBD(比如 ESEA 的)或不可预测的 SBD(比如 Warden 的)不容易绕过。

这些情况下,你可以采取预防措施消除机器人中的独特签名。不过,如果你把机器人分发给十几个人以上,移除所有区分属性就很棘手。为了甩掉分析师的线索,每次你把机器人副本交给某人时,你可以尝试以下某些组合:

(cid:127)用不同编译器编译机器人

(cid:127)更改编译器优化设置

(cid:127)在 __fastcall 和 __cdecl 之间切换

(cid:127)用不同加壳程序给二进制加壳

(cid:127)在运行时库的静态和动态链接之间切换

改变这些元素会为每个用户生成不同的汇编,但用这种方式能产生的独特机器人版本数量有限。超过某个点,这种方法无法扩展以满足需求,最终游戏公司会为你机器人的每个化身都有签名。

除了混淆和代码变异,没有太多方法可以击败高级 SBD 机制。你可以在驱动程序中实现机器人,或创建内核模式 rootkit 隐藏机器人,但即使这些方法也不是万无一失的。

 本书不涉及在驱动程序中实现机器人或创建 rootkit 隐藏机器人,因为这两个主题都相当复杂。仅 rootkit 开发就是一个已经被几十本书覆盖的主题。我推荐 Bill Blunden 的《The Rootkit Arsenal: Escape and Evasion in The Dark Corners of The System》(Jones & Bartlett Learning, 2009)。

一些游戏黑客试图覆盖每一个角落,钩住每个内存读取函数和整个文件系统 API,但仍然被Warden 这样坚定的系统抓住。事实上,我建议无论如何都要远离 Warden 和 Blizzard。