猿人学 App 第二十三题「rustndk」离机复现:从 Rust NDK 里抠出标准 XTEA
本文最后更新于:2026年9月2日 下午
[猿人学 App 逆向系列]第二十三题「九重春色醉仙桃【rustndk】」。签名逻辑放在 Rust 写的 NDK 库(libtwentythree.so)里,JNI 方法
data(int page)直接返回 sign 串。
Rust 二进制的反汇编乍看比 C 更”劝退”(泛型、迭代器、panic 分支满天飞),但这题的核心其实是一段标准 XTEA。本文记录:靠调试符号缩小范围 → 反汇编认出 XTEA → Frida 逐位对拍确定输入编码 → 纯 Python 复现。⚠️ 猿人学闯关 App 是面向逆向练习的 CTF 式靶场;本文仅用于授权范围内的学习研究,请遵守相关服务条款与法律。
本文目录
- 请求模型:sign 来自 native data(page)
- 先读”招牌”:Rust 的 crate 都写在二进制里
- 反汇编:0x9E3779B9 + 32 轮 = XTEA
- 认清是标准 XTEA 而非 TEA/XXTEA
- Frida 对拍:定位输入编码
- 纯 Python 复现
一、请求模型:sign 来自 native data(page)
ChallengeTwentyThreeFragment 很干净:
1 | |
data(page) 是 Rust native 返回的字符串,就是 sign。请求只有 page + sign,没有单独的时间戳字段——说明时间被编进 sign 里了。
二、先读”招牌”:Rust 的 crate 都写在二进制里
Rust release 库虽然函数符号常被 strip,但 **panic 信息 / file!() 宏会把 .cargo/registry/.../<crate>-<版本>/src/xxx.rs 的路径原样打进 .rodata**。strings 一扫就知道用了什么:
1 | |
没有任何 crypto 哈希 crate(没有 sha2/md5/hmac)。这基本排除了”标准哈希”,提示签名要么是自研位运算,要么是经典轻量分组算法。带着这个先验去反汇编,方向就清楚多了。
三、反汇编:0x9E3779B9 + 32 轮 = XTEA
Java_..._data 是标准 JNI 导出,好定位。反汇编它,很快撞见两个信号:
1 | |
0x9E3779B9(黄金分割常量)+ 32 轮几乎是 TEA 家族的身份证。再看循环体:
1 | |
四、认清是标准 XTEA 而非 TEA/XXTEA
TEA 家族有好几个变种,区别全在细节:
- TEA:key 固定 k0/k1、k2/k3,sum 两半共用;
- XXTEA:整块循环、上下文相关;
- XTEA:
sum在两半之间累加,且用key[sum & 3]和key[(sum>>11) & 3]动态取密钥字。
这段汇编里 and w8,#3 取 key[sum&3]、ubfx …#0xb,#2 取 key[(sum>>11)&3]、sum += delta 夹在两半中间——这正是教科书 XTEA。认出这个”取密钥字的方式”,就能把它和 TEA/XXTEA 区分开(第 22 题那道就是个非标准的 TEA 变种,别混)。
key 从内联的两个 64 位常量按小端拆成 4 个字:
1 | |
五、Frida 对拍:定位输入编码
XTEA 的输入是两个 32 位字 v0, v1。v0 早就从 w2(JNI 的 page 参数)来,那 v1 是什么时间编码?光看汇编里那一堆时间转换(clock_gettime + 除 1000 的魔数)容易数错精度。直接 Frida 对拍最稳:
1 | |
抓到一条:page=1,并同时 hook clock_gettime 拿到 native 用的精确时间 CLOCK_REALTIME = 1788336369.613…,输出 sign = 9e129c4e1718c4f7(16 hex = 两个 32 位密文字大端拼接)。
拿这条去暴力/反解输入编码,一发命中:
1 | |
关键坑:v1 不是”秒”也不是完整”毫秒”,而是毫秒截断到 32 位——native 把毫秒塞进 32 位寄存器时溢出丢高位。这种细节不对拍很难猜准。
六、纯 Python 复现
1 | |
自检(Frida 那条)+ 服务器 200 双双通过,逐页求和即通关。服务器端用同 key XTEA 把 sign 解回 (page, ms32),校验 page、并用 ms32 做新鲜度。
小结
第二十三题的”难”是心理上的——一看 “rustndk” + 3MB 二进制就容易怯。但拆开看:
- Rust 的 crate 清单白送(panic 路径里),先排除标准哈希、锁定”轻量分组算法”;
0x9E3779B9+ 32 轮 = TEA 家族;key[sum&3]/key[(sum>>11)&3]的取字方式 = 标准 XTEA;- Frida 逐位对拍确定输入编码(
v0=page,v1=毫秒&0xFFFFFFFF,那个 32 位截断是精髓)。
一句话:Rust native 不神秘,认出经典算法的”指纹”(magic 常量 + 取密钥方式)比逐行读汇编重要得多。