本文基于AI分析 anti-token,不得不感慨AI确实强大。
测试环境
它对应 Java Native 方法:
其中:
com.xunmeng.pinduoduo.secure.DeviceNative.info2是一个动态注册的jni方法,通过Hook RegisterNatives,得到函数偏移0x1cfac,以及库名为 libpdd_secure.so

0x1CFAC 就是一个 native 方法入口。它接收 Java 传入的 Context 和一个 long 值,然后返回一个 Java String。
IDA 反编译出来的原型是:
把 JNI 语义补上后更容易理解:
参数含义:
这个函数做了控制流扁平化。

正常代码可能长这样:
但反编译中会看到大量类似代码:
这里的 i 是状态机变量。大量随机整数只是混淆后的状态编号,不是业务数据。分析时不要跟着这些数字走,而要抓住真正的调用和数据流:
混淆后大概798行

让AI帮我们分析规则,并编写去控制流平坦化脚本,去除后逻辑就比较清晰了。

IDA底部python 执行命令
本节把主流程涉及的关键子函数一并列出,保留能证明执行逻辑的核心语句,并把 IDA 临时变量名改成更容易理解的名字。

关键调用顺序可以还原为:
对应主函数中的关键证据:
Hook 验证:
整体逻辑:
此函数用于 C 字节指针/字符串 -> slot
该函数证明了 record slot 的内部布局:slot[0] 是 tag,slot+1 是 payload,slot+520 是 payload 长度,slot+524 是 record 长度。
伪代码:
如调用write_bytes_record(slot, 2, "google", 6) 会构造成这样的slot
该函数先把整数转成指定长度的大端字节,再复用 record slot 格式。
整数 value -> 大端 bytes -> slot
伪代码:
它调用的 sub_17FC74 可以简化为:
slot 布局和上面是一样的:
如sdk版本,我的是34,这个是整数型。
这个函数用于把 Java 字符串写入 slot,或者把字符串转整数后写入固定长度 payload。
Java jstring -> 根据 mode 决定按字符串写,还是按整数写 -> slot
tag32:
可还原为:
tag33:
可还原为:
该函数负责 tag1 到 tag11 的主要系统属性。
伪代码:
对应反编译能看到连续的:
Hook 验证,1697AC和169840,slot结构体为:

该函数读取 /system/build.prop 的文件状态,取其中一个时间字段写成 4 字节整数。
伪代码:
对应反编译核心:
Hook 验证:1230768000时间戳转换后是2009-01-01 08:00



该函数负责 tag13 到 tag28,多数来自 TelephonyManager。
伪代码:
对应反编译核心包括:
Hook 验证tag 18、21、22 、23:

该函数把三路检测结果 OR 到一起,然后写 tag31。
伪代码:
对应反编译核心:
Hook 验证为0,这里也能看出来,假如用unidbug调的话,java stack trace可能会为1,导致风控的存在。

该函数通过 JNI 调 Java 侧 UUID 逻辑,最后把 Java 字符串拷贝到 native 缓冲区。
主函数上是直接保存的,没有调用1697ac或者169840创建slot。

伪代码:
主函数随后把它作为 tag34 record 使用:
这与主函数里写全局 tag34 slot 的逻辑一致:
主函数里先统计所有 record 的 record_len,再 malloc(raw_len),最后把 slot 转成连续 TLV。

上面的逻辑执行完成后,他还会补上tag 32、33、34到raw packet。32和33的获取方式后面会提到。

伪代码:
对应主函数证据:
下面补充本次 hook 样例值和 raw_packet 写入形态。
raw_packet 中每个 record 的写法固定为:
例如 tag2 的 payload 是字符串 google:
依次对所有record转换,再按照tag大小,进行排序拼接。
组装后会传入 0x181980进行压缩,对此函数入参和出参分别打印一下 (UUID和设备信息 敏感信息打码了)

raw_packet包的数据

这个函数的作用是把 native 的 raw_packet 复制到 Java byte[],再调用压缩逻辑,返回 gzip 后的 byte[]。
伪代码:
对应反编译核心:
注意:主函数传入该函数的是压缩前明文:
所以 hook 这个函数入口可以直接拿到完整 raw_packet。
结合AI分析

gzip压缩实际上是ByteArrayOutputStream + GZIPOutputStream

该函数完成最终封装。
伪代码:
对应反编译核心:
info2_aes_cbc_encrypt_zero_iv 又能证明 IV 为 0:
aes函数 入参和出参

Base64 函数 info2_base64_encode 使用标准字母表,并在剩余 1 或 2 字节时写 = padding。
Hook 验证:

这个函数生成的原始数据叫 raw_packet。它由很多条小记录组成,每条记录就是一个 tag。
可以把它理解成一个表:
每条 record 写进 raw_packet 时,格式是:
说明:
例如 payload 为 8 字节的 tag32,record 长度就是:
在拼包前,native 先把每条 record 放进固定大小的临时槽位。
槽位大小是:
布局可以还原为:
相关 helper:
所有 record 拼好后,会形成 raw_packet:
当前版本号固定为:
所以包头的前 4 字节是小端序:
body_len 是后面所有 record 的总长度,不包含前 6 字节 packet header。
伪代码:
把混淆状态机去掉后,函数可以简化成下面这个流程:
真实 native 里分成几个阶段:
函数里有几组重要全局变量:
缓存存在的原因:
刷新条件大致是:
如果不需要刷新,本次调用只补充动态字段:
反编译关键点:
含义:
如果 timestamp_ms = 0x0000019f1cb27649,payload 就是:
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。