-
-
[原创]第六题:酉时·书院迷局 Writeup
-
发表于: 13小时前 235
-
应用要求输入严格为 100 个小写十六进制字符,Java 层将其解码成 50 字节后调用静态 JNI 函数:
JNI 入口使用三次 vld2 对 50 字节做奇偶解交织,得到两个 25 字节序列:
后续逻辑分成两条通道:
两条通道通过一个 8 位 seed 和一个 16 字节块比较闭环。
动态库没有 JNI_OnLoad,JNI 函数以完整符号名静态导出。.init_array 中只有一个初始化函数,主要读取 /proc/self/status 的 TracerPid 并更新反调试状态。它不参与 flag 的数学计算。
Java 层的密钥函数会读取自身 APK,解析 ELF 节表,定位 96 字节的 .kctfguard 节并结合 CRC、节偏移和节大小生成 32 字节数据。还原该流程得到:
JNI 中再与固定向量异或,实际参与 native 运算的 16 字节密钥为:
库中还有一段运行时自解密数据。还原解密状态机后得到 32 字节隐藏目标:
native 校验大量使用长函数和手写位运算隐藏简单语义。先独立验证这些基础函数:
多处连续 30 层的结构:
只是普通加法器,整体直接替换成 x + y。
sub_E9F4 是 AES 逆 MixColumns。它前面经常出现一段展开的正向 MixColumns,因此:
这些成对结构可以全部删除。
另一个反复出现的 32 位散列为:
SIMD 代码同时计算两个 mix32。在多数调用点,保存的低散列字节随后又与合并值异或,二者相消。完成这些替换后,3116 条 odd 终检指令可以缩减成少量字节运算、一个四轮字函数和一个 46 位稀疏布尔门。
XOF 输入是:
将 32 字节按小端序解释成四个 64 位字 a,b,c,d。每轮先计算:
轮函数可逆。由轮后状态恢复轮前状态:
随机测试满足:
XOF 每次输出当前的四个 64 位字,然后更新:
校验要求 XOF[80:96] 等于隐藏目标的前 16 字节。它对应第三个输出块的 c,d,所以可以直接固定初始 XOF 状态中的两个字:
第一组八轮混淆完全消去后,每轮只剩:
目标为:
外层标量门只允许两组参数:
对八个字节方程直接逆算,每个 seed 都只剩一个 a:
后续代码多次调用 4328 字节的 sub_F32C。将内部 MixColumns 对、SIMD 低字节相消和进位网络删除后,它只是四个短轮。每轮从四个 32 位参数取不同字节,进行:
四轮结束后再做一次正向 MixColumns 和一个循环移位。简化实现与原 ARM64 函数使用随机参数逐组对拍,结果完全一致。
用它还原第二组八轮后,正确分支要求:
阶段二开始前先计算一个固定 64 位量:
两个看似复杂的 sub_10800 调用恒返回 0,因此最终 46 位 mux:
中的 v524=0,整体直接化为 v530。
右侧目标由固定 seed words、sub_F020(1)、sub_F020(2) 和恒零状态混合组成:
真正约束为:
逐位做数据依赖分析后,46 个输出位各自只依赖 b 的 2 到 5 位。例如:
因此不需要建立 64 位大位向量约束。对每个输出位的局部输入枚举真值表,任何不能产生目标位的赋值都写成一条阻断子句:
最终只有 669 条局部 CNF 子句。固定 B[0:4] 将全集分成 16 个互斥 cube,各 cube 独立枚举 SAT 模型。每个模型立即代入 XOF 逆函数,使用最强且便宜的 padding 条件筛选:
在 cube 7 的第 6849 个模型得到唯一命中:
完整 XOF 状态为:
将 odd25、seed=d7 放回原始 3116 条 ARM64 指令执行,五个最终 gate 均为 0,函数返回 1。
even 通道首先执行一个可逆字节置换和链式状态变换。正向过程为:
所以逆变换为:
后续全部在 local25 上分析。
前置条件给出:
S-box 初始化使用 word9 驱动 xorshift32。由前三个 S-box 自检字节反解得到唯一值:
最终的小配置域直接枚举得到:
代码使用标准 TEA 常量:
轮密钥由 32 位 LCG 生成:
共有三组 16 轮 KAT:
这里只有一个 32 位未知量,且每个候选的前向计算极快。使用 CUDA 让每个线程测试一个 seed,先用第一组 KAT 筛选,命中后在 CPU 验证另外两组。RTX 4060 Laptop 上全域扫描约 0.2 秒,得到:
全局 selector 的低字节为 0x8c,所以:
最终 local state 为:
逆回原 even share: