猿人学 App 第一题离机复现:dex 层标准 MD4(附非标准输出的坑)
本文最后更新于:2026年9月1日 下午
[猿人学 App 逆向系列]的第一篇。第一题(HTTP / dex,难度”简单”)没有 native、没有加壳,签名逻辑完全写在 dex 里——是最适合开篇、把”静态读请求模型 → 认出标准算法 → 纯 Python 还原”这条主线走一遍的题。
本文记录怎么从 Fragment 顺到签名类,认出它是标准 MD4,并留意一个非标准的输出字节序的小坑。⚠️ 猿人学闯关 App 是面向逆向练习的 CTF 式靶场;本文仅用于授权范围内的学习研究,请遵守相关服务条款与法律。
本文目录
- 请求模型(静态反编译)
- 签名类:一眼标准 MD4
- 唯一的坑:非标准的输出字节序
- 纯 Python 还原
一、请求模型(静态反编译)
dex2jar 转 jar 后反射 Retrofit 接口,端点很直接:
1 | |
ChallengeOneFragment 里翻页时怎么拼参数:
1 | |
即 content = "page=" + page + 毫秒(两者直接拼接,无分隔符,如 page=11788241201381),请求带 page / sign / t。核心就是签名类 o00oOOoO.o000OOo 的 OooO()。
二、签名类:一眼标准 MD4
o000OOo 是纯 Java(没有 native、没有加密 dex),jadx 直接还原。看几个特征就能定性:
- 辅助函数
F=(~x&z)|(y&x)、G=(x&y)|(x&z)|(y&z)、H=x^y^z; - 常量
1518500249 = 0x5A827999、1859775393 = 0x6ED9EBA1; - IV
0x67452301 / 0xEFCDAB89 / 0x98BADCFE / 0x10325476; - 三轮移位
3,7,11,19/3,5,9,13/3,9,11,15,第三轮消息字顺序0,8,4,12 · 2,10,6,14 · …。
这就是教科书 MD4——IV、移位、常量、轮函数全是标准值,没有第五题那种魔改。消息分块 iArr[i6] = get(i7+3)<<24 | get(i7) | get(i7+1)<<8 | get(i7+2)<<16 是标准小端装字。
小注:
get()返回的是Integer.valueOf(signedByte),字节 ≥0x80 会变负数、和<<24一起 OR 可能出问题;但本题 content 永远是"page="+ 纯数字(全是 ASCII,< 0x80),不会触发,等价标准 MD4。
三、唯一的坑:非标准的输出字节序
MD4 主体是标准的,唯一要留意的是输出:
1 | |
标准 MD4 的 hex 摘要,是把 4 个 32 位状态字各自小端成 4 字节再拼(如字 0x67452301 → 01234567)。而这里是 %02x 直接格式化整型,得到的是大端 hex(0x67452301 → 67452301)。%02x 只保证最小 2 位宽(某个字很小时补前导 0),字满时就是 8 位。
所以复现时,别用现成 MD4 库的 hexdigest(那是小端字节序),要么自己实现、直接把状态字按大端 %02x 输出,要么把标准摘要每 4 字节反转一下。
四、纯 Python 还原
自己实现 MD4、按大端 %02x 输出,一把梭:
1 | |
到这一步就完全离机、零依赖:不碰手机、纯 Python 复刻签名即可逐页取数。t 和 sign 只要自洽(sign == MD4("page={page}{t}"))即可,服务器不额外卡时间戳新鲜度。
小结
第一题是整个系列的”热身”:静态读请求模型 → 认出标准算法 → 纯 Python 还原,全程不碰手机。唯一容易踩的是输出字节序——主体是标准 MD4,但它把状态字按大端 %02x 直出,用现成库的 hexdigest 会对不上。往后的题会逐步加码:dex 层魔改(第五题)、native goron 混淆(第四题)、加壳(第十一题)、native 反爬(第十三题)。