百趣云 百趣云的博客

MD5 魔改识别:K 值、IV 与轮函数的对比法

为什么厂商爱魔改 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  # 不等 = 确认魔改

第三步:逐层排查魔改点

  1. 先假设只改了 IV:把标准实现的 IV 换成 App 里的值,对拍
  2. 再查 K 表:把 App 的 K 表抠出来替换,对拍
  3. 最后怀疑轮函数:这时只能逐轮对比中间状态——hook App 的每轮寄存器值,和标准实现的每轮对比,第一轮出现分歧的位置就是魔改点

逐轮对比的实现

MD5 的每轮更新 A/B/C/D 四个寄存器。hook 方案:

// 找到 MD5 的轮函数主循环,hook 每轮结束时的寄存器
// 或更实用:hook 第一轮结束和最后一轮结束
Interceptor.attach(roundFuncAddr, {
    onLeave: function(retval) {
        // 读寄存器/内存里的 A B C D 值,打印
    }
});

第一轮就不同 = IV 或 K 表魔改;中间某轮开始不同 = 该轮的函数或位移魔改;全都不同但结构对 = 可能整个被重写(放弃修补,直接抠实现)。

精髓:魔改 MD5 的工程决策

  1. 只改 IV/K 表:值得还原——把魔改参数抠出来,改标准实现的常量即可,工作量半小时
  2. 改了轮函数/位移:还原成本骤增——优先 Frida 直调或抠整个函数到 Node/Python(C 代码直接编译进 Python 扩展也行)
  3. 警惕"假魔改":有的 App 在标准 MD5 前后加盐(md5(salt + data + salt2)),这不是魔改算法,是加盐——对拍时先试常见加盐模式,别急着分析轮函数

总结

  1. MD5 魔改三个点:IV、K 表、轮函数/位移
  2. 识别靠常量搜索:0x67452301(IV)、0xd76aa478(K 表首项)
  3. 对拍定位:逐轮对比寄存器,第一轮分歧处即魔改点
  4. 工程决策:改常量值得还原,改轮函数优先直调

交流微信:run1255

By 百趣云 阅读量:22 On