第28章:加密算法逆向(一):MD5/SHA系列哈希算法逆向分析
哈希算法,说白了就是给数据算个「指纹」。你给它一段数据,它给你一串固定长度的摘要。这串摘要理论上不能反推原文,而且只要原文改一个比特,摘要就面目全非。MD5和SHA系列,是逆向分析中最常碰到的哈希算法。
我刚开始做逆向那会儿,碰到哈希函数就头大。一堆移位、异或、加法,看得眼花缭乱。后来摸清了套路,发现其实就那么几个核心操作。今天咱们就把MD5和SHA-1/SHA-256的逆向分析讲透。
28.1 哈希算法逆向的核心思路
逆向哈希算法,跟逆向加密算法不太一样。加密算法你往往需要找到密钥,然后解密。哈希算法呢?你不需要「解密」,因为它是单向的。我们逆向分析哈希算法,通常是为了这几个目的:
- 识别算法类型:通过常量、操作模式判断是MD5还是SHA-1还是SHA-256
- 定位关键数据:找到被哈希的原始数据在哪里
- 验证完整性:确认程序是否用哈希校验了关键代码或数据
- 寻找碰撞或绕过:在某些场景下,我们需要绕过哈希校验
我个人习惯,拿到一个二进制文件,先搜一下有没有明显的哈希常量。比如MD5的初始向量(IV),或者SHA-1的五个初始值。这些常量是识别算法最直接的线索。
28.2 MD5算法逆向分析
28.2.1 MD5算法特征
MD5输出128位(16字节)的摘要。它的核心特征包括:
- 四个初始向量:0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476
- 64轮迭代:分四轮,每轮16步
- 非线性函数:F, G, H, I 四个函数
- 移位操作:每步有固定的循环左移位数
我在项目中遇到过,有些混淆过的代码会把MD5的初始向量拆成多个常量,然后通过运算拼回去。这时候光搜常量就不够了,得看操作模式。
28.2.2 MD5的逆向识别技巧
在IDA Pro或Ghidra里,看到下面这些模式,基本可以确定是MD5:
模式一:四个常量的赋值
mov dword ptr [var_4], 67452301h
mov dword ptr [var_8], 0EFCDAB89h
mov dword ptr [var_C], 98BADCFEh
mov dword ptr [var_10], 10325476h
嗯,这里要注意,有些编译器优化后,常量可能以立即数形式出现在循环里。但不管怎么优化,这四个值不会变。
小技巧:在IDA里用Search -> Immediate value,分别搜这四个常量的低16位(因为高16位可能被拆开)。比如搜0x2301,0xAB89,0xDCFE,0x5476。如果四个都搜到了,基本就是MD5。
28.2.3 MD5的逆向分析实战
假设我们在一个恶意软件样本里看到了MD5的调用。怎么找到它哈希的是什么数据?
我一般这么干:
- 找到MD5函数的入口:通过常量定位,或者通过调用约定(通常有四个参数:输出缓冲区、输入数据、数据长度、初始向量)
- 回溯参数:看调用前,输入数据指针是从哪里来的。可能是从文件读取、内存复制、或者网络接收
- 分析上下文:哈希结果用在哪里?是跟硬编码的字符串比较?还是作为密钥派生的一部分?
举个例子,我曾经逆向过一个勒索软件,它用MD5哈希了每个文件的路径,然后用哈希值作为文件名的一部分。找到MD5函数后,我回溯了输入参数,发现它读的是文件路径字符串。嗯,这就清楚了——它在给文件路径算指纹。
28.3 SHA-1算法逆向分析
28.3.1 SHA-1算法特征
SHA-1输出160位(20字节)。它的特征常量是:
- 五个初始向量:0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
- 80轮迭代:分四轮,每轮20步
- 非线性函数:f1, f2, f3, f4
- 常量K值:0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6
你发现没有?SHA-1的前四个初始向量跟MD5一模一样。所以光看前四个常量,不能区分MD5和SHA-1。得看有没有第五个常量0xC3D2E1F0,或者看有没有那四个K值。
避坑指南:我曾经在分析一个加壳程序时,看到四个MD5常量就以为是MD5,结果分析半天发现是SHA-1。原来那个壳把SHA-1的第五个常量藏在了另一个函数里。所以识别算法时,一定要多看几个特征,别急着下结论。
28.3.2 SHA-1的逆向识别技巧
SHA-1的80轮循环在反汇编里看起来很长。但有个特点:每轮的操作模式非常规整。你会看到类似这样的模式重复出现:
; SHA-1 一轮操作
mov eax, [var_A] ; 取a
mov ebx, [var_B] ; 取b
mov ecx, [var_C] ; 取c
; ... 计算 f(b,c,d)
add eax, [var_E] ; a += e
add eax, [var_K] ; a += K
add eax, [var_W] ; a += W[i]
rol eax, 5 ; a 左移5位
add [var_B], eax ; b += a
; ... 更新寄存器
这种「取a、算f、加e、加K、加W、左移、更新」的模式,是SHA-1的标志。你看到这种模式重复80次(或者在一个循环里),基本可以确定是SHA-1。
28.4 SHA-256算法逆向分析
28.4.1 SHA-256算法特征
SHA-256输出256位(32字节)。它的特征包括:
- 八个初始向量:0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
- 64轮迭代:每轮使用不同的K常量
- 六个逻辑函数:Ch, Maj, Σ0, Σ1, σ0, σ1
- 64个K常量:从0x428A2F98到0xC67178F2
SHA-256的初始向量跟MD5、SHA-1完全不同。而且它有64个K常量,这些常量在二进制里很容易被搜到。
28.4.2 SHA-256的逆向识别技巧
SHA-256的代码量比MD5和SHA-1都大。但识别起来反而更容易——因为它的常量太有特点了。
我建议你直接在二进制里搜0x6A09E667或0xBB67AE85。如果搜到了,再看周围有没有其他七个常量。有的话,基本就是SHA-256。
小技巧:有些程序会把SHA-256的K常量表放在一个单独的数据段里。这个表有64个32位整数,共256字节。在IDA里看到一段256字节的数据,里面全是看起来像随机数的32位值,那很可能就是SHA-256的K表。
28.5 哈希算法逆向的通用方法
不管遇到哪种哈希算法,有几个通用的逆向方法:
| 方法 | 说明 | 适用场景 |
|---|---|---|
| 常量搜索 | 搜索初始向量、K常量、移位值 | 所有哈希算法 |
| 操作模式匹配 | 识别移位、异或、加法组合模式 | 混淆程度不高的代码 |
| 调用约定分析 | 通过函数参数推断算法类型 | 动态链接库调用 |
| 动态调试 | 在哈希函数入口下断点,观察输入输出 | 需要确认数据流向时 |
| 差分分析 | 修改输入数据,观察输出变化 | 验证算法实现是否正确 |
我个人最常用的是常量搜索加动态调试的组合。先搜常量定位函数,然后下断点看输入数据。这样效率最高。
28.6 实战案例:识别并绕过哈希校验
说个实战案例。我之前分析过一个游戏外挂检测程序,它会用SHA-256哈希游戏主模块的代码段,然后跟硬编码的哈希值比较。如果哈希不匹配,就判定游戏被修改了。
逆向过程是这样的:
- 在二进制里搜到了SHA-256的八个初始向量,定位到哈希函数
- 回溯调用,发现它读的是模块基址加上一个偏移范围
- 动态调试,在比较处下断点,看到它把计算出的哈希跟一个固定值比较
- 绕过方法:把比较指令改成无条件跳转,或者把硬编码的哈希值改成我们计算出的新哈希
嗯,这里要注意,有些程序会做哈希值的二次校验。比如把哈希值再哈希一次,或者用哈希值作为解密密钥的一部分。所以改完比较后,最好跑一遍完整流程,确保没有其他校验点。
28.7 哈希算法逆向的常见陷阱
做哈希算法逆向,有几个坑我踩过,分享给你:
- 混淆的常量:有些保护壳会把常量拆成多个部分,运行时再拼起来。这时候直接搜常量搜不到,得看运算逻辑
- 自定义哈希:有些程序不用标准哈希,而是自己魔改一个。比如改几个移位值或常量。这时候得仔细分析每一步操作
- 哈希链:有些程序会多次哈希同一个数据,或者把多个哈希结果拼接起来。别看到一个哈希函数就以为完事了
- 硬件加速:现代CPU有SHA指令集(如SHA256RNDS2),这时候哈希操作可能是一条指令而不是一个函数。逆向时要注意识别这些指令
避坑指南:我曾经遇到一个程序,它用了Intel的SHA扩展指令集。我在IDA里找了半天没找到哈希函数,后来才发现它直接调用了SHA256RNDS2指令。所以逆向时,别忘了看看有没有用到硬件加速指令。
28.8 总结
哈希算法逆向,说白了就是三步:识别算法、定位数据、分析用途。MD5看四个初始向量和64轮操作,SHA-1看五个初始向量和80轮操作,SHA-256看八个初始向量和64个K常量。
我给你的建议是:先在脑子里把MD5、SHA-1、SHA-256的核心特征背下来。然后多练几个样本,看到常量就能条件反射地认出算法。等你练熟了,哈希算法逆向就是分分钟的事。
最后说一句,哈希算法虽然不能解密,但逆向分析的价值一点都不比加密算法小。很多安全机制都建立在哈希校验上,能识别并绕过这些校验,你就掌握了主动权。
公众号:蓝海资料掘金营,微信deep3321