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指令切换
我的小技巧: 判断固件是ARM还是Thumb,最简单的方法就是看入口地址。如果入口地址是偶数,通常是ARM模式;如果是奇数,那基本就是Thumb模式。为什么?因为Thumb模式下,PC的最低有效位(LSB)会被置1,表示当前处于Thumb状态。

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支持得不错。来,我带你走一遍加载流程:

  1. 创建新项目:File → New Project,选Non-Shared Project。
  2. 导入固件:把固件二进制文件拖进去。Ghidra会弹出一个对话框,让你选择语言。
  3. 选择处理器:ARM的话,选"ARM:LE:32:Cortex"(小端模式)。如果是Thumb,Ghidra会自动识别。
  4. 设置基地址:这个很关键。你得知道固件在内存中的加载地址。不知道的话,先设成0x0,后面再调整。
  5. 开始分析:点"Analyze",Ghidra会跑一遍自动分析。嗯,这个过程可能需要几分钟。
注意: 有些固件会混合使用ARM和Thumb指令。Ghidra默认可能只识别一种模式。这时候你需要手动标记函数入口,告诉Ghidra这里用Thumb模式。我曾经在一个路由器固件里,花了整整一天才把混合模式的函数全部理清楚。

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"(显示交叉引用)。这样就能定位到引用这些字符串的代码位置。

避坑指南: 我曾经遇到一个固件,所有字符串都被加密了。搜索"signature"什么都找不到。后来我换了个思路——搜索"RSA"、"ECC"、"SHA"这些算法相关的字符串,果然找到了。有时候,签名验证函数不会直接报错,而是调用一个通用的错误处理函数。

4.6 交叉引用分析:顺藤摸瓜

找到字符串引用后,接下来就是交叉引用分析。说白了,就是看谁调用了谁。

具体怎么做?

  1. 找到引用错误字符串的代码位置
  2. 向上回溯,看这个代码块被哪个函数调用
  3. 继续向上回溯,直到找到最顶层的调用者
  4. 这个最顶层的调用者,往往就是签名验证的入口函数

举个例子。我在分析一个智能门锁固件时,找到了"Signature check failed"字符串。回溯后发现,它被一个叫sub_1234的函数调用。再往上回溯,发现sub_1234verify_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指令集 ARM:32位固定长度 Thumb:16位为主 常用指令:LDR/BL/CMP Ghidra / IDA Pro 加载固件:选择处理器 设置基地址 自动分析/手动调整 定位签名验证函数 搜索字符串 交叉引用分析 回溯调用链 核心目标:找到签名验证函数入口 为后续绕过/修补做准备

嗯,这张图把本章的核心内容串起来了。从指令集基础,到工具使用,再到定位签名验证函数,每一步都是环环相扣的。

你想想看,如果你连ARM和Thumb都分不清,那后面分析签名验证函数时,反汇编出来的代码就是一团乱麻。反过来,如果你能熟练使用Ghidra或IDA Pro,再配合交叉引用分析,那找到签名验证函数就是分分钟的事。

好了,这一章的内容就到这儿。记住,基础不牢,地动山摇。把ARM/Thumb指令集和工具使用练熟了,后面的绕过方案才能玩得转。


专注资料整理