4、固件二进制分析:Bin/Hex/ELF格式解析、固件加载地址识别、入口点定位

拿到一个固件文件,第一件事是什么?

别急着上反汇编器。你得先搞清楚手里拿的是什么格式。Bin、Hex、ELF,这三种格式我几乎天天打交道。它们长得不一样,解析方式也完全不同。搞错了,后面全是白费功夫。

4.1 三种固件格式的本质差异

先看一张对比表,心里有个底:

格式 是否包含地址信息 是否分段 典型场景 解析难度
Bin 否 否 量产烧录、OTA升级包 低
Hex (Intel HEX) 是(每行记录地址) 是(按记录类型分段) 调试器下载、IDE输出 中
ELF 是(完整符号表) 是(段表+节表) 调试、逆向分析 高

我个人习惯,拿到固件先看文件头。Bin文件最简单,就是裸二进制流,没有头、没有校验、没有地址。Hex文件开头是冒号,每行都有地址偏移。ELF文件开头是7f 45 4c 46(即.ELF),一看便知。

小技巧: 用hexdump -C firmware.bin | head -20看一眼前几行,基本就能判断格式。如果是纯机器码,没有ASCII字符,大概率是Bin。如果看到大量冒号开头的行,那就是Hex。

4.2 Bin文件:裸数据,地址全靠猜

Bin文件最让人头疼。它没有地址信息,你得自己猜加载地址。

我在项目中遇到过好几次,客户只给了个Bin文件,连芯片型号都没说。怎么办?

  1. 看中断向量表:STM32的启动代码,前4字节是栈顶指针,接着4字节是复位向量。复位向量的值,往往能反推出加载基址。
  2. 找常见模式:比如0x08000000开头的地址,基本就是Flash基址。
  3. 试错法:用IDA或Ghidra,分别尝试0x08000000、0x20000000、0x00000000,看哪个能正确解析出代码。
注意: 有些固件做了地址混淆,复位向量可能被修改。我曾经遇到一个案例,复位向量指向了RAM区,其实是固件做了自解压。这时候不能只看向量表,还得结合上下文。

4.3 Hex文件:逐行解析,地址分段

Intel HEX格式,每行格式固定:

:10000000 00 08002000 080001F1 080001F5 080001F9 6A
|  |       |  |         |         |         |        |
|  长度    地址 记录类型  数据...                      校验和
冒号

记录类型有6种,但常用的就两种:

  • 00(数据记录):真正的固件内容,地址+数据。
  • 01(文件结束记录):表示文件结束。
  • 04(扩展线性地址):用于指定高16位地址,配合数据记录的低16位,组成32位地址。

解析时,我建议按以下步骤:

  1. 逐行读取,跳过空行。
  2. 校验每行的校验和。如果校验失败,说明文件损坏。
  3. 遇到04记录,更新当前高地址段。
  4. 遇到00记录,将数据写入对应地址。
  5. 遇到01记录,停止解析。
核心要点: Hex文件中的地址是逻辑地址,不是物理地址。STM32的Flash基址通常是0x08000000,但Hex文件里可能从0x00000000开始。你需要根据芯片手册,做地址偏移。

4.4 ELF文件:段与节,信息最丰富

ELF文件是逆向分析的最爱。它包含了符号表、段信息、重定位表,甚至调试信息。

ELF的结构分两层:

  • 链接视图(节表):用于编译链接阶段,包含.text、.data、.bss等节。
  • 执行视图(段表):用于加载运行阶段,包含LOAD、DYNAMIC等段。

我一般用readelf工具快速查看:

# 查看ELF文件头
readelf -h firmware.elf

# 查看段表(执行视图)
readelf -l firmware.elf

# 查看节表(链接视图)
readelf -S firmware.elf

# 查看符号表
readelf -s firmware.elf

对于STM32固件,重点关注LOAD段。它告诉固件应该加载到哪个地址。比如:

LOAD 0x08000000 0x08000000 0x00010000
LOAD 0x20000000 0x20000000 0x00002000

第一行:Flash区,加载地址0x08000000,大小0x10000。第二行:RAM区,加载地址0x20000000,大小0x2000。

个人经验: 如果ELF文件被strip过(去除了符号表),别慌。段表和节表还在,至少能知道代码段和数据段的位置。我曾经分析过一个被strip的固件,靠段表信息硬是找到了关键函数入口。

4.5 固件加载地址识别

加载地址错了,反汇编出来的代码全是错的。怎么确认?

我总结了三个方法:

  1. 向量表法:STM32的复位向量,通常指向Reset_Handler。如果这个地址在0x08000000附近,那基址就是0x08000000。
  2. 字符串搜索法:在固件中搜索常见字符串,比如STM32、HAL、Error。这些字符串的地址,能帮你反推基址。
  3. 交叉引用法:用IDA加载后,看函数调用关系。如果大部分函数地址都在某个范围内,那这个范围就是代码段。
避坑指南: 我曾经遇到一个固件,向量表被故意篡改,复位向量指向了一个无效地址。后来发现,固件在启动时会先执行一段自校验代码,然后才跳转到真正的复位向量。这时候,你得手动跟踪启动流程,不能轻信向量表。

4.6 入口点定位

入口点,就是CPU上电后执行的第一条指令。对于STM32,入口点就是复位向量指向的地址。

定位步骤:

  1. 找到向量表基址(通常是0x08000000)。
  2. 读取偏移0x04处的4字节,这就是复位向量。
  3. 复位向量的值,就是入口点地址。

举个例子:

地址: 0x08000000  数据: 0x20001000  (栈顶指针)
地址: 0x08000004  数据: 0x080001F1  (复位向量 → 入口点)

入口点就是0x080001F1。注意,这个地址通常是奇数(Thumb模式),实际执行地址是0x080001F0。

为什么是奇数? ARM Cortex-M系列使用Thumb-2指令集,指令地址最低位为1表示Thumb模式。所以入口点地址总是奇数。反汇编时,记得把最低位清零。

4.7 知识体系总览

下面这张图,概括了本章的核心逻辑:

固件二进制分析知识体系 Bin格式 Hex格式 ELF格式 裸数据 + 地址猜测 逐行解析 + 地址分段 段表/节表 + 符号表 加载地址识别 入口点定位 可反汇编的固件映像 格式决定解析策略

说白了,固件分析的第一步就是搞清楚格式、地址、入口。这三件事搞定了,后面反汇编、逻辑还原才有基础。你想想看,如果连代码从哪开始执行都不知道,那分析还有什么意义?

嗯,这一章的内容就到这里。记住,拿到固件先看格式,再看地址,最后找入口。三步走,稳得很。