猿人学 App 第二十八题「GoNDK」离机复现:从 Go 二进制认出 Tiger 哈希
本文最后更新于:2026年9月3日 下午
[猿人学 App 逆向系列]第二十八题「一池新水碧荷圆【GoNDK】」。签名由 Go 写的 NDK 库(gomobile 打的 libgojni.so)计算。
Go 二进制乍看是个 1.4MB 的黑盒,但它有个”送分”特性——panic 信息里带着源码文件路径,一眼就能看出用了什么。本文记录:靠源码路径认出 Tiger 哈希 → Frida hook 对拍 → 从 so 里抠 S-box 做纯 Python 还原 → 补上 token 这道坑。⚠️ 猿人学闯关 App 是面向逆向练习的 CTF 式靶场;本文仅用于授权范围内的学习研究,请遵守相关服务条款与法律。
本文目录
- 请求模型:content 和第一题一样
- Go 二进制的”送分题”:panic 路径暴露源码
- Frida hook getSign 对拍
- 从 libgojni.so 抠出 Tiger 的 S-box
- 补最后一道坑:token
- 纯 Python 复现
一、请求模型:content 和第一题一样
ChallengeTwentyEightFragment 里:
1 | |
**content = "page=" + page + ms**——和第一题一模一样的格式(第一题那次是 dex 层 MD4)。这次 getSign 是 Go native,得进 libgojni.so 里看。
二、Go 二进制的”送分题”:panic 路径暴露源码
Go 的 release 库体积大、符号也常被裁,但有个几乎没法去掉的特征:panic/错误信息里嵌了编译时的源码文件绝对路径(file:line)。strings 一扫:
1 | |
还有 challenge-twenty-eight/sign.(*digest).Reset / .Sum 这种方法名。**tiger.go + compress.go + digest = Tiger 哈希**——一个 192 位(24 字节 / 48 hex)的老牌哈希。方向瞬间明确:签名就是 Tiger(content)。
Go 逆向的第一步永远是
strings | grep .go——它经常直接把作者的意图(用了哪个算法/库)写在脸上。
三、Frida hook getSign 对拍
猜到 Tiger,先用 Frida 抓一条真机 (content -> sign) 坐实。getSign 是标准 JNI 导出 Java_..._Sign_getSign(JNIEnv*, jclass, jbyteArray),用 JNI 函数表读入参数组、读返回串:
1 | |
抓到:content = "page=11788414311409",sign = 4f4b4a5335c657e68f1cfae616291d8bebf8b69501e1e763。
拿 rhash --tiger 一对:
1 | |
确认:sign = 标准 Tiger/192(小端字节序输出)。
四、从 libgojni.so 抠出 Tiger 的 S-box
要做纯 Python 客户端,得有 Tiger。Python 没有内置 Tiger,而它的 4 张 S-box 是 8KB 固定常量,自己按官方口令生成很容易写错(我第一版就错了)。最稳的办法:直接从 libgojni.so 的 .rodata 里把 S-box 抠出来。
Tiger 的 t1[0] 是公开已知的 0x02AAB17CF7E90C5E,拿它当锚点:
1 | |
有了正确的 S-box,自己写的 Tiger 压缩函数(3 趟、乘子 5/7/9、key_schedule、0x01 填充)立刻就对了:Tiger("") = 3293ac63…(标准测试向量),样本也逐位一致。把这 8KB base64 内嵌进脚本即可。
五、补最后一道坑:token
算法对了,直接请求却回 {"error": "pass"}(403)。抓一条真机 app28 请求对比才发现:除了 page/sign/ts,请求还带了一个 token(登录态)。补上就 200 了:
1 | |
token 存在设备 MMKV 的 com.yuanrenxue.match.utils.KEY_TOKEN,过期换新即可。
六、纯 Python 复现
1 | |
自检(Frida 那条)+ 服务器 200 双通过,逐页求和即通关。
小结
第二十八题的破解链很”教科书”:
- **Go 二进制先
strings | grep .go**——tiger.go直接点名算法; - Frida hook
getSign对拍坐实是标准 Tiger(别忘了 Frida 17 用Process.findModuleByName+ JNI 函数表读 jbyteArray/jstring); - 纯 Python 需要 S-box 时,从 so 里抠比自己生成靠谱——用已知首值
0x02AAB17CF7E90C5E当锚点一抓一个准; - **算法对了还进不去,就去抓真机请求补”环境”**——这题少的是
token。
一句话:Go/gomobile 看着唬人,但它把源码路径写在 panic 里;认出经典算法 + 从二进制抠常量,比硬啃汇编省事得多。