为什么厂商爱魔改 MD5
MD5 是 App 签名最常用的摘要算法,但标准 MD5 人人会算,防护约等于零。于是厂商流行"魔改 MD5"——改动算法内部的常量或轮函数,让标准实现算出的结果对不上。逆向的核心技能就是识别魔改点。
标准 MD5 的三个可改点
1. 初始向量(IV)
标准 MD5 的四个初始寄存器:
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476魔改手法:改这四个值(+1、异或、换成别的常量)。识别方法:IDA 里搜 0x67452301(十进制 1732584193),搜不到但附近有相似常量 = IV 被改。
2. K 表(64 个常量)
标准 MD5 每轮用 K[i] = floor(2^32 × abs(sin(i+1))) 生成的 64 个常量:
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, ...魔改手法:换 K 表(随机数替换或整体偏移)。识别:搜 0xd76aa478,对比后续常量是否连续匹配标准表。
3. 轮函数与位移
标准 MD5 四轮用 F/G/H/I 四个非线性函数和固定位移表。魔改手法:交换轮函数顺序、改位移量。这种魔改最难识别——常量都对,但运算顺序变了。
识别方法论:对拍定位法
面对一个疑似魔改 MD5,标准流程:
第一步:确认是 MD5 家族。搜到部分 MD5 常量(哪怕 IV 被改,K 表通常至少部分保留),或代码结构是明显的"64 轮循环 + 4 寄存器"。
第二步:标准实现对拍。
import hashlib
# 用 App 的已知输入输出对拍
known_input = b"test"
app_output = bytes.fromhex("a8f5...") # hook 拿到的 App 输出
assert hashlib.md5(known_input).digest() != app_output # 不等 = 确认魔改第三步:逐层排查魔改点。
- 先假设只改了 IV:把标准实现的 IV 换成 App 里的值,对拍
- 再查 K 表:把 App 的 K 表抠出来替换,对拍
- 最后怀疑轮函数:这时只能逐轮对比中间状态——hook App 的每轮寄存器值,和标准实现的每轮对比,第一轮出现分歧的位置就是魔改点
逐轮对比的实现
MD5 的每轮更新 A/B/C/D 四个寄存器。hook 方案:
// 找到 MD5 的轮函数主循环,hook 每轮结束时的寄存器
// 或更实用:hook 第一轮结束和最后一轮结束
Interceptor.attach(roundFuncAddr, {
onLeave: function(retval) {
// 读寄存器/内存里的 A B C D 值,打印
}
});第一轮就不同 = IV 或 K 表魔改;中间某轮开始不同 = 该轮的函数或位移魔改;全都不同但结构对 = 可能整个被重写(放弃修补,直接抠实现)。
精髓:魔改 MD5 的工程决策
- 只改 IV/K 表:值得还原——把魔改参数抠出来,改标准实现的常量即可,工作量半小时
- 改了轮函数/位移:还原成本骤增——优先 Frida 直调或抠整个函数到 Node/Python(C 代码直接编译进 Python 扩展也行)
- 警惕"假魔改":有的 App 在标准 MD5 前后加盐(
md5(salt + data + salt2)),这不是魔改算法,是加盐——对拍时先试常见加盐模式,别急着分析轮函数
总结
- MD5 魔改三个点:IV、K 表、轮函数/位移
- 识别靠常量搜索:
0x67452301(IV)、0xd76aa478(K 表首项) - 对拍定位:逐轮对比寄存器,第一轮分歧处即魔改点
- 工程决策:改常量值得还原,改轮函数优先直调
交流微信:run1255