手游反外挂-反内核物理访问内存方案思考

准备工作

逆向的时候用到了这些工具,不过文章并没有详细解释工具的用法,需要读者自行摸索:

※ 读者应当有一定的逆向工具使用经验。部分分析过程没有详细解释操作,而是思路。

第六课课件:

手游反外挂-反内核物理访问内存方案思考插图

    • 包含解锁前后的第六课录像、通用解锁工具 v0.1.5 和对应源码。如果读者已通过其它方式下载到第六课的原件,也可以使用解锁工具进行转换来得到解锁后的文件。

同时也借助了 Hmily 提供的“编辑加密”锁定之前的原始文件用于对比参考。

开始逆向

准备好了吗?开始了哦!

内幕消息

H 大在我之前做了一点初步的分析,做了个 bindiff 列出二者的区别,以及关键部分的算法。

bindiff 节选:

地址 大小 未加密 “编辑加密”锁定后
B9C37h 14h 96 4A FE 49 F4 3D 70 91 FE 75 E7 A3 D6 8F F1 9B 73 59 8F 80 EE D6 32 34 FD 45 24 D4 48 0A 12 31 72 B4 9A EC 50 2F 04 3C
CC2D0h 14h 9F C2 32 79 02 66 A0 A0 25 F7 62 FB AD 0D 51 DF 64 79 48 A1 E7 5E DF 04 6F D6 FD 65 54 17 8A 85 72 7A 8E A4 2F 5F 3B 8B
都是 14h 或 13h 字节更改的情况
37A65BBh 2h 00 00 25 3B

以及关键的解密循环:

 复制代码 隐藏代码
lb_00402B37:
  xor esi,esi                        ; 计数器
  lea edx,dword ptr ss:[ebp-D4]      ; 密钥 1
  lea eax,dword ptr ss:[ebp-E8]      ; 解密后的内容

lb_00402B45:
  mov ecx,14
  sub ecx,esi                        ; ecx = 0x14 - 计数器
  inc esi                            ; 计数器自增
  mov cl,byte ptr ss:[ebp+ecx-C0]    ; 密钥 2 (偏移 = ecx)
  xor cl,byte ptr ds:[eax]           ; xor 原文
  xor cl,byte ptr ds:[edx]           ; xor 密钥 1 内容
  inc edx
  mov byte ptr ds:[eax],cl           ; 储存结果到原文位置
  inc eax
  cmp esi,14                         ; 一共处理 0x14 字节
  jl lb_00402B45

(关键点让我自己来找的话,估计又要花几个小时了)

看着很复杂,但整理到高级语言后其实还行:

 复制代码 隐藏代码
char* edx = ptr_d4;    // ebp-D4
char* eax = ptr_e8;    // ebp-E8
char* var_c0 = ptr_c0; // ebp-C0

for (int i = 0; i < 0x14; i++) {
  eax[i] = eax[i] ^ edx[i] ^ var_c0[0x14 - i];
}

(注意代码会跳过 C0[0] 的值,这不是文章的错误,而是播放器代码如此)

解密算法得到了,现在还差两项内容:

  • 密钥如何得到?
  • 帧储存在哪里?如何定位修改点?

密钥来源

首先想办法搞明白密钥从哪来。

稍微看看之前的代码,可以发现它会检查大小是否超过 10240。如果未超过则不进行解密处理。

在检查长度之后的地方,用 x64dbg 设置条件断点,使其在即将解密的时候打印各项目的值:

 

 复制代码 隐藏代码
断点地址    00402B37
暂停条件    0
日志文本    d4: {mem;14@ebp-D4} / e8: {mem;14@ebp-E8} / c0: {mem;14@ebp-C0}
日志条件    1

然后跑起来,看看日志:

 复制代码 隐藏代码
第一段:
  d4: 789CCC7D09785445B67FF592A43B6B771242BA89  <- 密钥 1
  e8: EED63234FD4524D4480A123172B49AEC502F043C  <- 密文 (解密后覆盖为明文)
  c0: 3135313431000000000000000000000000000000  <- 密钥 2

第二段:
  d4: 789CED7D6DB05DC571E0E87EDF77DF7B7A12421F
  e8: E75EDF046FD6FD6554178A85727A8EA42F5F3B8B
  c0: 3135313431000000000000000000000000000000

第三段:
  d4: 789CED7D59CC65D955DEAE3BFF53D5DF735563D3
  e8: 605CF1EF4359370F9196881782A75BD2A6634043
  c0: 3135313431000000000000000000000000000000

观察上述数据:

  • ebp-D4 的内容每次都不一样,但是开头都是 78 9C,可以在原文件找到。
  • ebp-E8 就是加密后改变的 0x14 长度内容,可以在原文件找到。
  • ebp-C0 是固定值,文件内找不到(注意解密时第一个字节 31 不参与运算)。

此外,对 DecompressImage_4027BC 进行交叉检索可以得到下述可能的调用路径:

  • (初始化) _TPlayForm_FormShow → _TPlayForm_repareplay2(...) → DecompressImage_4027BC(...)
  • (播放时) sub_403ACC → RenderFrame_405B80(PlayForm, ...) → DecompressImage_4027BC(...)
文件密钥 (ebp-C0)

ebp-C0 处的文件密钥每个文件都不一致,储存在 [_PlayForm]+338 处。

 复制代码 隐藏代码
// nonce = [[_PlayForm]+338]
memset(nonce_key, 0, 21u);
sprintf(nonce_key, "%d", nonce); // nonce = 15141, 0x3b25
另两个密钥

在文件进行查找,可以发现 ebp-D4 处的内容是个“长度前缀编码”的数据 (Length-Prefixed Encoding, 简称 LPE):

 复制代码 隐藏代码
         |  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F |       说明       |
---------+---------------------------------------------------+------------------|
00A:B1B0 |                           F6 D4 01 00 84 A9 05 00 |  帧头            |
00A:B1C0 |  78 9C CC 7D 09 78 54 45  B6 7F F5 92 A4 3B 6B 77 |  起始数据        |
00A:B1D0 |  12 42 BA 89                                      |                  |
         |                                                   |                  |
---------+---------------------------------------------------+------------------|
         |                                                   |                  |
00B:9C30 |                       EE  D6 32 34 FD 45 24 D4 48 |  修改点          |
00B:9C40 |  0A 12 31 72 B4 9A EC 50  2F 04 3C                |                  |
         |                                                   |                  |
---------+---------------------------------------------------+------------------|
         |                                                   |                  |
00C:86B0 |        00 00 00 00 61 03  00 00 CC 01 00 00 00 00 |  帧后面的内容    |
00C:86C0 |  00 00 FF FF FF FF 5B 03  00 00 C5 01 00 00 00 00 |                  |
00C:86D0 |  00 00 02 00 00 00 39 02  00 00 9D 00 00 00 3A 02 |                  |
---------+---------------------------------------------------+------------------|

其中:

  • F6 D4 01 00: 表示后续数据的长度为 0x0001d4f6
    • 下一节数据在 0x00AB1B8 + 0x0001d4f6,也就是偏移 0x00C86B2 处;
  • 84 A9 05 00: 表示解压后的数据长度(猜测)。
  • ebp-E8 指向的内容刚好在数据中间
    • 算法为 0xAB1C0 - 4 + (0x1d4f6 / 2),也就是 0xb9c37 处。

不过这个帧与帧之间又有一些“意义不明的数据”,需要结合代码分析。

帧储存格式

帧与帧之间有很多意义不明的数据,需要想办法知道它的计算规则(或如何正确的跳过这些内容)。

回溯调用方,找到播放时执行的 RenderFrame_405B80 方法,再想办法找点“小”的帧数据来看:

 复制代码 隐藏代码
--- 第一帧的数据
decompress_enter: pos=CFAF4; exp_len=(49)
decompress_done: pos=CFB3D

49 00 00 00    A2 00 00 00  // 帧头
78 9C 73 ... 66 0F 8D       // 数据

// 偏移: CFB3D
D3 FF FF FF // 负数,帧结束标志。
F5 01 00 00 | D0 00 00 00 // 意义不明,两个 f32 浮点数

// 新的“帧”开始:
00 00 00 00 // 另一个 LPE,空的

2E 00 00 00 // 帧开始标识符(负数表示没有更新或结束)
25 05 00 00|7D 02 00 00|31 05 00 00|89 02 00 00 // 坐标

--- 第二帧的数据
// 偏移: CFB61
98 00 00 00 // compressed size

decompress_enter: pos=CFB65; exp_len=(98)
decompress_done: pos=CFBFD

整理下逻辑,这就是跨越了 2 “帧”(实际上是帧引用的图像)的数据。

结合实际猜测,大概是这样的结构:

 复制代码 隐藏代码
struct frame_data {
    uint32_t compressed_size;
    uint32_t decompressed_size;
    uint8_t compressed_data[compressed_size - 4];
};

// 连续的 `other_frame` 结构体。
struct other_frame {
    // 未知数据流
    uint32_t stream2_len;
    uint8_t stream2[stream2_len];

    uint32_t frame_id; // 帧序号
    if (frame_id > 0) { // 负数表示无数据,例如屏幕无更新内容
        RECT patch_cord; // 应该是坐标

        while (frame_id > 0) {
            struct frame_data frame; // 帧信息
            uint32_t frame_id; // 帧序号
        }
    }

    // field_24 == 1 的情况,会有两个额外的 f32 数据。
    if (field_24 == 1) {
        float unknown_1;
        float unknown_2;
    }
};

毕竟是个播放器,合理怀疑 stream2 储存的数据其实是用于辅助定位上一个“完整帧”的信息或鼠标指针数据。
不过看起来和“编辑加密”的锁定无关,就没继续跟进了。

大概分析清楚“帧”储存的格式后,就可以尝试定位数据的起始位置。

本站资源来自互联网收集,仅提供信息发布
一旦您浏览本站,即表示您已接受以下条约:
1.使用辅助可能会违反游戏协议,甚至违法,用户有权决定使用,并自行承担风险;
2.本站辅助严禁用于任何形式的商业用途,若被恶意贩卖,利益与本站无关;
3.本站为非营利性网站,但为了分担服务器等运营费用,收费均为赞助,没有任何利益收益。
死神科技 » 手游反外挂-反内核物理访问内存方案思考

死神科技,因为专业,所以领先。

网站首页 24小时自动发卡
在线客服
24小时在线客服
阿里云自动发卡,购卡进群售后
12:01
您好,有任何疑问请与我们联系!

选择聊天工具: