跳转至

放置钩子

我遇到的下一个问题是钩子是针对 Adobe AIR.dll 模块内的代码,这不是游戏的主模块。由于代码的位置,我需要以不同的方式找到钩子的基址。此外,由于我需要这些钩子跨几个不同版本的 Adobe AIR 工作,我还必须为每个版本找到正确的地址。

我没有试图拿到所有不同版本的 Adobe AIR,而是从 airlog 的做法中又学了一招,决定编写一个小的内存扫描器以编程方式定位地址。在编写内存扫描器之前,我需要 Adobe AIR.dll 的基址和大小,以便把内存搜索限制在该区域内。

我用 Module32First() 和 Module32Next() 找到这些值,如下所示:

MODULEENTRY32 entry;
entry.dwSize = sizeof(MODULEENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_S
DWORD base, size;
if (Module32First(snapshot, &entry) == TRUE) {
  while (Module32Next(snapshot, &entry) == TRUE) {
      std::wstring binaryPath = entry.szModule;
      if (binaryPath.find("Adobe AIR.dll") != 
std::wstring::npos) {
          size = (DWORD)entry.modBaseSize;
          base = (DWORD)entry.modBaseAddr;
          break;
      }
    }
}
CloseHandle(snapshot);

这段代码遍历进程中的所有模块,直到找到 Adobe AIR.dll①。找到正确的模块条目②时,它从中获取 modBaseSize 和 modBaseAddr 属性,并立即跳出。

下一步是找到可以用来识别函数的一串字节。我决定使用每次调用周围的字节码。我还必须确保每个序列唯一,同时避免在模式中使用任何常量,以保证代码的可移植性。清单 8-13 展示了我最终得到的字节序列。

const char encodeSeq[16] = {
    0x8B, 0xCE, // MOV ECX, ESI
    0xE8, 0xA6, 0xFF, 0xFF, 0xFF, // CALL encode
    0x83, 0xF8, 0xFF, // CMP EAX, -1
    0x74, 0x16, // JE SHORT endLoop
    0x03, 0xF8, // ADD EDI, EAX
    0x3B, 0xBE}; // CMP EDI, [ESI+0x3C58] 的一部分
const char decodeSeq[12] = {
    0x8B, 0xCE, // MOV ECX, ESI
    0xE8, 0x7F, 0xF7, 0xFF, 0xFF, // CALL decode
    0x83, 0xF8, 0xFF, // CMP EAX, -1
    0x89, 0x86}; // MOV [ESI+0x1C54], EAX 的一部分

清单 8-13:encode() 和 decode() 字节序列

注意每个模式中的 CALL 指令;这些就是对我命名为 encode() 和 decode() 的 Adobe AIR 函数的调用。我用以下函数扫描这些序列:

    DWORD base, DWORD size,
    const char* sequence,
    unsigned int seqLen){
    for (DWORD adr = base; adr <= base + size - 
seqLen; adr++) {
        if (memcmp((LPVOID)sequence, (LPVOID)adr, 
seqLen) == 0)
            return adr;
    }
    return 0;
}

把 Adobe AIR.dll 的内存当作字节数组,findSequence() 函数在字节数组中查找字节序列作为子集,并返回找到的第一个匹配的地址。写完 findSequence() 函数后,找到要钩住 encode() 和 decode() 的地址就很简单了。以下是这些调用的样子:

DWORD encodeHookAt =
    findSequence(base, size, encodeSeq, 16) + 2;
DWORD decodeHookAt =
    findSequence(base, size, decodeSeq, 12) + 2;

由于每个目标调用都在其各自搜索序列的 2 字节处,我只需定位每个序列并加 2。之后,最后一步是用第一百五十三页「调用钩子」中描述的方法放置钩子。

钩子完成后,我能看到游戏客户端和服务器之间传输的每一块数据。此外,由于 RTMPS 协议发送序列化的 ActionScript 对象,数据基本上是不言自明的。每条信息都伴随一个变量名。每个变量都是描述良好的对象的成员。每个对象都有一致的名称。就像我说的——这是一座金矿。