4. 固件逆向分析基础:ARM/Thumb指令集入门、使用Ghidra/IDA Pro加载固件、定位签名验证函数、交叉引用分析
说实话,很多搞固件安全的朋友,一上来就想着怎么绕过签名验证。但我要泼盆冷水——你连固件里跑的是ARM还是Thumb都分不清,连签名函数长什么样都不知道,那绕过就是空谈。
这一章,咱们就踏踏实实把基础打牢。我带你从指令集入门,到用工具加载固件,再到精准定位签名验证函数。嗯,这些都是我这些年踩坑踩出来的经验。
4.1 ARM vs Thumb:你分得清吗?
先问个问题:你拿到一个固件,怎么知道它是ARM指令集还是Thumb指令集?
我刚开始做逆向时,就吃过这个亏。有一次分析一个IoT设备的固件,用IDA Pro加载后,反汇编出来的代码怎么看怎么不对劲——指令长度忽长忽短,跳转地址奇奇怪怪。折腾了半天,才发现是Thumb模式,我当成ARM在分析。
来,咱们把这两个指令集的核心区别说清楚:
| 特性 | ARM指令集 | Thumb指令集 |
|---|---|---|
| 指令长度 | 固定32位(4字节) | 16位为主,部分32位 |
| 代码密度 | 较低 | 较高(节省30%空间) |
| 寄存器访问 | 所有16个寄存器 | 受限(通常8个) |
| 典型应用 | 性能敏感场景 | 嵌入式、内存受限设备 |
| 模式切换 | 通过BX指令切换 | 通过BX指令切换 |
4.2 常用ARM/Thumb指令速查
说实话,你不需要背下所有指令。但下面这几条,是你在分析签名验证时一定会遇到的:
- LDR/STR:加载/存储指令。签名验证函数里,经常用LDR从内存加载公钥或签名数据。
- BL/BLX:带链接的分支调用。调用签名验证子函数时,用的就是它。
- CMP/TST:比较指令。验证通过/失败的结果,往往通过CMP设置标志位。
- MOV/MVN:数据传送。返回值0或1,通常通过MOV来实现。
- BX:分支并切换指令集。从ARM切到Thumb,或者反过来,全靠它。
重点记忆: 签名验证函数返回时,通常会用 MOV R0, #0 表示验证失败,MOV R0, #1 表示验证成功。你找到这个模式,就找到了关键跳转点。
4.3 用Ghidra加载固件:我的习惯流程
我个人习惯用Ghidra做固件逆向,因为它免费、开源,而且对ARM支持得不错。来,我带你走一遍加载流程:
- 创建新项目:File → New Project,选Non-Shared Project。
- 导入固件:把固件二进制文件拖进去。Ghidra会弹出一个对话框,让你选择语言。
- 选择处理器:ARM的话,选"ARM:LE:32:Cortex"(小端模式)。如果是Thumb,Ghidra会自动识别。
- 设置基地址:这个很关键。你得知道固件在内存中的加载地址。不知道的话,先设成0x0,后面再调整。
- 开始分析:点"Analyze",Ghidra会跑一遍自动分析。嗯,这个过程可能需要几分钟。
4.4 IDA Pro加载固件:老司机的选择
如果你手头有IDA Pro,那当然更好。它的反编译能力比Ghidra强不少。加载步骤类似:
- File → Load file → New
- 选择固件文件,IDA会弹出加载对话框
- Processor type选"ARM Little-endian [ARM]"
- Loading segment设成固件的基地址
- 点OK,等IDA分析完成
IDA Pro有个好处——它会对Thumb代码自动切换模式。你看到地址是奇数的函数,IDA会自动用Thumb模式反汇编。这一点比Ghidra省心不少。
4.5 定位签名验证函数:从字符串入手
好了,固件加载完了。现在怎么找到签名验证函数?
我的经验是——先找字符串。签名验证函数里,通常会有一些提示信息,比如:
- "Signature verification failed"
- "Invalid signature"
- "Firmware signature check error"
- "Verification OK"
在Ghidra或IDA里,直接搜索这些字符串。找到后,右键点击,选择"Show references to this address"(显示交叉引用)。这样就能定位到引用这些字符串的代码位置。
4.6 交叉引用分析:顺藤摸瓜
找到字符串引用后,接下来就是交叉引用分析。说白了,就是看谁调用了谁。
具体怎么做?
- 找到引用错误字符串的代码位置
- 向上回溯,看这个代码块被哪个函数调用
- 继续向上回溯,直到找到最顶层的调用者
- 这个最顶层的调用者,往往就是签名验证的入口函数
举个例子。我在分析一个智能门锁固件时,找到了"Signature check failed"字符串。回溯后发现,它被一个叫sub_1234的函数调用。再往上回溯,发现sub_1234被verify_firmware函数调用。好了,verify_firmware就是我们要找的签名验证函数。
核心思路: 签名验证函数通常具有以下特征:
- 接收固件数据、签名、公钥作为参数
- 内部调用哈希算法(SHA256等)
- 内部调用非对称解密算法(RSA/ECC)
- 返回0或1表示验证结果
4.7 实战:用Ghidra定位签名验证函数
来,我带你走一遍完整的实战流程。假设我们有一个路由器固件:
1. 打开Ghidra,导入固件文件
2. 选择ARM小端模式,基地址设为0x80000000
3. 运行自动分析
4. 在Symbol Tree中搜索"signature"、"verify"、"check"
5. 如果没找到,搜索"fail"、"error"、"invalid"
6. 找到字符串后,右键→References→Show References to Address
7. 双击引用,跳转到代码位置
8. 向上回溯,找到调用链的顶层函数
9. 重命名该函数为"verify_signature"
10. 分析该函数的参数和返回值
4.8 本章知识体系
下面这张图,是我梳理的本章知识体系。你可以把它当成一个思维导图来看:
嗯,这张图把本章的核心内容串起来了。从指令集基础,到工具使用,再到定位签名验证函数,每一步都是环环相扣的。
你想想看,如果你连ARM和Thumb都分不清,那后面分析签名验证函数时,反汇编出来的代码就是一团乱麻。反过来,如果你能熟练使用Ghidra或IDA Pro,再配合交叉引用分析,那找到签名验证函数就是分分钟的事。
好了,这一章的内容就到这儿。记住,基础不牢,地动山摇。把ARM/Thumb指令集和工具使用练熟了,后面的绕过方案才能玩得转。