ai文,还是将关键数据的加密trace扔给ai,1GB,自动还原出白盒aes的加密方式和密钥
本文记录如何从 ARM64 指令 trace D:\tome\tome_u2_new4.trace 出发,定位 extra 字段的数据处理路径,识别其中的白盒 AES-128-CTR,并从 libriver.so 第一轮白盒 T-box 中恢复标准 AES-128 主密钥。
最终结论:
本报告强调三个层次必须分开:
报告内 0x19C72C、0x19E280 等地址均为 libriver.so 的模块相对地址(RVA),不是进程绝对地址。
综合使用:
trace 最终组装的外层 JSON 包含:
extra 的明文数据源是紧凑 JSON,包含 dynver、Root/ADB/Boot 状态、系统指纹、ABI、文件权限、Frida 检测、随机字段和 userenv 等。trace 中紧凑明文缓冲区为:
加密后内部缓冲区为:
多出的 8 字节位于最前面,后面 766 字节与明文等长,初步提示“元数据前缀 + 流加密密文”,而不是带 PKCS#7 padding 的 CBC。
libriver.so + 0x19E280 附近的动态指令是最直接的证据:
第一字节:
第二字节:
因此核心关系被动态确认:
这排除了“JSON 直接哈希”“逐字段编码”等不可逆处理,并说明前面的复杂函数是在生成密钥流块。
密钥流缓冲区按 index & 0x0f 访问,每 16 字节重新执行一次块变换,说明底层块大小为 128 bit。这与 AES 的 16 字节块大小一致,但仅凭这一点还不能断言 AES。
白盒块变换完成后,0x19E1F0–0x19E268 从状态第 15 字节开始加一,并在溢出时向低地址传播进位:
去混淆后的语义为:
因此这是 128-bit 大端计数器。结合:
可以识别为 CTR 模式,而非 CBC、CFB 或 OFB。
在完整轮入口处,输入状态按以下索引取字节:
这正是 AES 状态按列线性存储时的 ShiftRows 置换:
第一列选择 state[0]、state[5]、state[10]、state[15]。动态 trace 对初始状态确实读取:
精确 ShiftRows 置换是识别 AES 的强证据,通常比搜索 AES S-box 字节更可靠,因为白盒实现会把 S-box 和轮密钥融合进表,使标准 S-box 不再以明文形式出现。
每个输出列对四个输入字节分别查 32-bit 表,四张表之间间隔 0x400:
第一列动态查表值示例:
普通 T-table AES 会直接 XOR 四个 word;白盒实现则需要在隐藏内部编码的条件下合并它们。
trace 中反复出现如下地址构造:
随后读取一个字节。等价抽象为:
其语义不是简单 table[a][b] = a ^ b,而是:
Ea/Eb/Ec 是内部编码双射。四个 32-bit T-box 输出经过多层高低 nibble 合并,最终得到四个编码状态字节。这是 Chow 类白盒 AES 的典型结构:
动态 trace 显示完整轮循环计数到 9:
每轮表基址增加:
一轮表大小恰好是:
九轮总表大小:
之后进入独立末轮。
末轮使用 16 张 256-byte 表:
抽象为:
末轮之后没有 MixColumns 合并网络。这与 AES 最后一轮“SubBytes + ShiftRows + AddRoundKey,无 MixColumns”一致。
综合特征:
因此是 AES。轮数为 10,故是 AES-128,而非 AES-192(12 轮)或 AES-256(14 轮)。
本次时间戳:
代码先把整数转换为 IEEE-754 double:
得到大端字节:
注意这不是 uint64_t timestamp 的大端表示,而是 (double)timestamp 的 bit pattern。
加密结果前 8 字节也保存这一值:
每个 selector 使用三次 arc4random():
查找:
地址结构:
本次两组结果:
因此初始 16-byte AES 输入是:
布局:
AES-CTR 输出并未直接 Base64,而是调用 sub_D912C 封包。
Base64 解码后的头:
其中两个 16-bit 大端位置:
两个插入块并非随机垃圾,而是 selector:
解包公式:
恢复后长度:
外层最终编码:
01 是字符串协议版本前缀,不属于 Base64。
白盒 AES 中不会出现普通的:
主密钥被融合进第一轮 T-box,后续轮密钥被融合进对应轮表。因此:
本次使用的是 BGE 思路中的仿射等价秩测试,直接从第一轮 16 张 T-box 逐字节恢复首轮 key byte。
对于第 i 个第一轮 T-box,可抽象为:
其中:
虽然 A_i 和 b_i 未知,但正确 k_i 下,T_i(x) 与 S(x XOR k_i) 之间仍是仿射关系。
错误 key guess 会让两个映射之间通常不存在低维仿射关系。
对每张 T-box、每个候选 k ∈ [0,255] 和所有 x ∈ [0,255],构造 41-bit 向量:
位数:
代码表达:
若存在仿射映射:
那么 32-bit T_i(x) 完全由:
线性决定,所以所有 41-bit 图向量最多张成 9 维空间。
正确 key byte 因而满足:
错误 key guess 通常使 rank 明显高于 9。
实现以 Python 大整数保存 41-bit 行,逐列选 pivot 并 XOR 消元:
对每张表测试全部 256 个候选。实际每张表均只有一个 rank-9 候选。
第一轮白盒表位于二进制文件偏移:
大小:
读取方式:
第 i 张表:
每项是 little-endian uint32_t:
然后执行秩测试:
16 张 T-box 得到的唯一候选,按 T-box/ShiftRows 顺序为:
拼接:
这是 T-box 输入使用 ShiftRows 排列后的 key byte 顺序,不是标准 AES key 顺序。
已知表索引对应标准状态索引:
逆排列:
得到标准 AES-128 主密钥:
ASCII:
初始 counter:
使用恢复密钥执行普通 AES-128-ECB 单块加密:
结果:
与 trace 第一块白盒输出完全一致。
这与解包后的密文首块完全一致,实现三重闭环:
因此密钥不是猜测,也不是仅凭可打印字符串判断。
CTR 加解密相同:
恢复 key 后,不再需要模拟白盒表。可直接用标准 AES-128-ECB 生成 CTR 密钥流:
脚本位置:
运行:
实际输出:
错误。CTR 模式本来就是明文 XOR 块密码输出。必须继续分析密钥流生成器。
错误。白盒 AES 会融合 S-box、轮密钥、MixColumns 和编码,标准 S-box 字节序列往往不存在。
应优先寻找:
错误。两个 4-byte 插入块携带 bank/lane/index,接收方需要它们重建 counter 后 8 字节。
错误。01 是 ASCII 协议前缀,真正 Base64 从后面的 D8EA... 开始。
helloworldxxxxxx 很可读,但仍必须验证。最终可信度来自标准 AES 对已知输入输出的精确匹配。
从 trace 指令识别白盒 AES 的关键路径是:
密钥恢复路径是:
最终恢复结果:
验证状态:
{
"8": {"realtime": "{\"content\":\"0|Built-in Screen|2138x1080|131|1\"}"},
"6": {"1": ["394232"]},
"an": 8,
"id": "3240a81b31837bae",
"ts": 1784793365424,
"seq": 4,
"pk": "yqFYOTeuVR46dP452Qd+kpQf4sJqiDeqX2ne5+w+Y0E=",
"ver": "1.0.0",
"extra": "01D8EAABAIAukAm0J5+N+Xuw..."
}
地址:0xb4000077a1a24580
长度:0x2fe = 766 字节
首字节:7b 22 64 79 6e 76 65 72 ...
ASCII:{"dynver"...
地址:0xb400007841783b30
长度:0x306 = 774 字节
0x19E280 LDRB W10, [plaintext, index]
0x19E284 LDRB W11, [keystream, block_index]
0x19E294 EOR W8, W11, W10
0x19E2B0 STRB W8, [ciphertext, index]
明文 = 0x7b ('{')
密钥流 = 0xc3
密文 = 0xb8
0x7b XOR 0xc3 = 0xb8
明文 = 0x22 ('"')
密钥流 = 0x5e
密文 = 0x7c
0x22 XOR 0x5e = 0x7c
C[i] = P[i] XOR KS[i]
MOV W8, #15
LDRB W11, [counter, W8]
ADD W11, W11, #1
STRB W11, [counter, W8]
...
SUB index, index, #1
for (int i = 15; i >= 0; --i) {
counter[i]++;
if (counter[i] != 0)
break;
}
KS_n = BlockEncrypt(counter_n)
C_n = P_n XOR KS_n
counter_{n+1} = counter_n + 1