4、固件结构分析:固件文件格式、加载地址与入口点、向量表分析、段解析、固件解包与打包

做固件逆向,第一步就是跟固件文件打交道。你拿到的可能是个.bin,也可能是个.hex,或者是个.elf。这三种格式,说白了就是不同阶段的产物。我刚开始接触时也犯过迷糊,以为它们都一样,结果分析半天发现地址全不对。嗯,这里咱们得捋清楚。

4.1 固件文件格式:bin、hex、elf

这三种格式,我习惯这么理解:

  • .bin:最纯粹的二进制镜像。没有地址信息,没有元数据,就是裸数据。你把它烧到Flash的哪个地址,它就在哪个地址执行。我遇到过不少新手,拿到.bin直接反汇编,结果不知道加载基址,反出来的代码全是错的。
  • .hex:Intel HEX格式,带地址信息。每一行都记录了数据应该放在哪个地址。说白了,就是给烧录器看的。我个人觉得,做逆向时.hex比.bin友好,因为地址信息直接写在文件里。
  • .elf:可执行与链接格式。这是最完整的,包含段信息、符号表、调试信息。如果你能拿到.elf文件,那逆向工作至少省一半力气。我在项目中遇到过客户只给了.bin,我硬着头皮猜加载地址,折腾了两天才搞定。

核心区别一句话总结:.bin是裸数据,.hex是带地址的裸数据,.elf是带符号和段信息的完整可执行文件。

4.2 固件加载地址与入口点

这是逆向的第一个拦路虎。你拿到一个.bin,不知道它该加载到哪个地址,反汇编出来的代码全是错的。为什么会这样?因为ARM的跳转指令(比如B、BL)用的是相对偏移,但数据访问指令(比如LDR)用的是绝对地址。

我一般这么找加载地址:

  1. 看向量表:ARM的向量表前四个字节是栈指针,后四个字节是复位向量。复位向量指向的地址,通常就是入口点。
  2. 查中断向量:如果固件里有中断向量表,向量地址往往能反推加载基址。
  3. 用工具猜:比如binwalk的“-A”选项可以尝试自动分析架构和入口点。

个人经验:我习惯先用binwalk扫一遍,看看有没有已知的固件头或者文件系统。如果没有,就手动看前16个字节。ARM的向量表很有特征——第一条指令通常是LDR PC, [PC, #offset]或者直接B指令。找到复位向量后,减去偏移就能算出加载基址。

4.3 向量表分析

ARM的向量表,说白了就是一张“异常处理跳转表”。它固定在地址0x00000000(或者0xFFFF0000,取决于系统配置)。每个向量占4个字节,一共8个向量。

偏移异常类型说明
0x00复位上电或复位后执行的第一条指令
0x04未定义指令CPU遇到不认识的操作码
0x08软件中断(SWI)系统调用入口
0x0C预取中止指令预取失败
0x10数据中止数据访问失败
0x14保留未使用
0x18IRQ普通中断请求
0x1CFIQ快速中断请求

我曾经分析过一个IoT设备的固件,发现它的向量表里复位向量指向0x08000101。注意这个地址的最后一个bit是1——这表示Thumb模式。嗯,这里要注意,ARM的向量表里存的地址,最低位用来指示指令集模式(0=ARM,1=Thumb)。

避坑指南:我曾经因为没注意向量表里地址的LSB,把Thumb代码当成ARM代码反汇编,结果出来的指令全是乱的。记住:地址最低位是模式标志,不是真正的地址位。

4.4 段(.text, .data, .bss)解析

一个典型的ARM固件,内存布局大概是这样:

  • .text:代码段。只读,存放指令和常量。我习惯从这里开始分析主逻辑。
  • .data:数据段。存放已初始化的全局变量和静态变量。这部分在Flash里有个初始值,启动时会被拷贝到RAM里。
  • .bss:未初始化数据段。存放未初始化的全局变量。启动时会被清零。

怎么在.bin里找到这些段的边界?我一般这么干:

  1. 先找到向量表,确定复位向量指向的地址。
  2. 从复位向量开始,反汇编启动代码。启动代码里通常会有拷贝.data和清零.bss的循环。
  3. 找到这些循环里用的源地址和目的地址,就能确定段的边界。

实用技巧:如果你看到类似“LDR R0, =_sdata”、“LDR R1, =_edata”这样的指令,那恭喜你——这是编译器生成的段边界符号。直接记下这些地址就行。

4.5 固件解包与打包

很多固件不是裸的.bin,而是被封装过的。常见的有:

  • 文件系统固件:比如SquashFS、JFFS2、CramFS。用binwalk可以自动识别和提取。
  • 加密固件:整个固件被加密,需要先解密才能分析。我遇到过AES加密的,也遇到过简单的XOR加密。
  • 压缩固件:用LZMA、gzip等压缩。binwalk可以自动解压。
  • 自定义格式:厂商自己定义的封装格式,有头部、校验、分段等。

我常用的解包工具链:

  1. binwalk:第一道工序,扫描固件里的已知签名。
  2. dd:手动切割固件,提取指定偏移和大小的数据块。
  3. hexdump / xxd:查看固件头部结构。
  4. sasquatch:专门解SquashFS文件系统。
  5. jefferson:解JFFS2文件系统。

打包逆向:有时候你需要修改固件再打包回去。我建议先分析原厂的打包工具,或者自己写脚本模拟打包过程。注意校验和——很多设备会校验固件的完整性,改了内容不更新校验和,设备会拒绝启动。我曾经因为漏了CRC校验,把设备刷成砖,最后用JTAG才救回来。

4.6 知识体系结构图

下面这张图,是我自己梳理的固件结构分析流程。你跟着这个思路走,基本不会迷路。

固件结构分析知识体系 固件文件格式 加载地址与入口点 向量表分析 bin / hex / elf 区别 裸数据 vs 带地址 vs 带符号 烧录地址 vs 加载地址 反汇编基址确定 入口点定位方法 向量表复位向量 binwalk自动分析 启动代码反汇编 8个异常向量 复位/未定义/SWI 预取中止/数据中止 IRQ/FIQ 段解析:.text(代码) → .data(已初始化数据) → .bss(未初始化数据) 固件解包与打包:binwalk / dd / sasquatch / 自定义脚本 分析流程:识别格式 → 定位入口 → 解析向量表 → 划分段 → 解包/打包

这张图从左到右,从上到下,就是我分析固件的标准流程。你跟着这个思路走,基本不会漏掉关键步骤。

最后提醒一句:固件分析没有银弹。每个厂商都有自己的“小九九”,有的会在固件头里藏校验,有的会加混淆。我遇到过最离谱的一个固件,厂商把整个向量表都挪到了别的位置,还在原位置放了假数据。嗯,这种时候就只能靠经验硬啃了。


公众号:蓝海资料掘金营,微信deep3321