4、固件加载与入口点分析:固件文件格式(BIN/ELF/HEX)、加载地址与入口点确定、使用readelf/objdump分析固件结构

做固件逆向,第一步就是跟固件文件打交道。

你拿到一个.bin文件,或者一个.hex文件,甚至一个.elf文件。怎么下手?

我刚开始做这行的时候,也犯过傻——直接拿IDA打开一个裸BIN,结果反汇编出来全是乱码。后来才明白,加载地址不对,一切都是白搭

这一节,我们就来聊聊固件的三种常见格式,以及怎么找到那个关键的入口点。

4.1 三种固件格式:BIN、ELF、HEX

说白了,这三种格式代表了固件从开发到烧录的三个阶段。

格式 特点 典型场景
BIN 纯二进制数据,无元信息。不知道加载地址,不知道代码段在哪。 从Flash/SRAM中dump出来的固件,或者最终烧录文件。
ELF 包含段信息、符号表、重定位表。调试器最爱。 开发阶段编译出来的中间产物,或者带调试符号的固件。
HEX Intel HEX格式,文本形式存储,每行包含地址和数据。 烧录器常用的格式,尤其是单片机领域。
我的经验: 如果你拿到的是BIN文件,先别急着反汇编。先想想它可能被加载到哪个地址。ARM Cortex-M系列默认从0x08000000启动,而很多MIPS路由器从0x80000000或0x90000000启动。这个地址猜对了,后面就顺了。

4.2 加载地址与入口点确定

入口点,就是CPU上电后执行的第一条指令的地址。

对于ELF文件,入口点直接写在文件头里。用readelf -h就能看到。

但对于BIN文件,入口点需要你自己推断。

怎么推断?

  • 看中断向量表:ARM Cortex-M系列,地址0处存放的是栈顶指针,地址4处存放的是复位向量(即入口点)。
  • 看启动代码特征:很多固件开头是初始化代码,比如设置栈指针、跳转到main函数。
  • 看已知的Bootloader模式:比如U-Boot的入口点通常是0x00000000或0xFFFF0000。
注意: 我曾经遇到过一个固件,入口点被故意混淆了。它先跳到一个看似随机的地址,执行一段垃圾指令,再跳回真正的入口。这种时候,别慌,用动态调试单步跟一下,就能看到真相。

4.3 使用readelf分析ELF结构

readelf是分析ELF文件的利器。我个人习惯先用它看文件头。

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

# 输出示例:
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Entry point address:               0x08000123
  Start of program headers:          52 (bytes into file)
  Start of section headers:          123456 (bytes into file)
  ...

看到Entry point address了吗?那就是入口点。

接下来,看程序头(Program Headers),它描述了如何将ELF加载到内存中。

readelf -l firmware.elf

# 输出示例:
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08000000 0x08000000 0x12345 0x12345 RW  0x1000
  LOAD           0x010000 0x08010000 0x08010000 0x23456 0x23456 R X 0x1000

这里,VirtAddr就是加载地址。Flg字段标记了段的权限:R=读,W=写,X=执行。

关键点: 可执行段(R X)通常就是代码段。加载地址就是它的VirtAddr。入口点必须落在某个可执行段内,否则固件跑不起来。

4.4 使用objdump反汇编与查看段

objdump是另一个好工具。我常用它来反汇编特定段,或者查看段信息。

# 查看所有段
objdump -h firmware.elf

# 输出示例:
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00012345  08000000  08000000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000100  08012345  08012345  00002000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000200  08012445  08012445  00002100  2**2
                  ALLOC

.text段就是代码段,.data段是已初始化的全局变量,.bss段是未初始化的全局变量。

反汇编代码段,看看入口点附近是什么:

objdump -d --start-address=0x08000123 --stop-address=0x08000150 firmware.elf

这样就能看到入口点处的指令了。通常是一条跳转指令,或者初始化代码。

避坑指南: 我曾经遇到一个固件,它的入口点指向了一个奇怪的地址,反汇编出来全是nop。后来才发现,那是编译器为了对齐插入的填充。真正的入口点其实在_start符号处。所以,别只看地址,要看上下文

4.5 知识体系结构图

下面这张图,帮你理清固件加载与入口点分析的核心逻辑:

固件加载与入口点分析知识体系 BIN格式 ELF格式 HEX格式 核心分析步骤 确定加载地址 找到入口点 分析启动代码 工具:readelf / objdump / hexdump

4.6 实战小技巧

最后,分享几个我常用的技巧:

  • BIN文件猜地址:如果固件开头是00 00 00 00或者FF FF FF FF,那很可能是未初始化的区域。真正的代码在后面。
  • HEX文件看地址:HEX文件每行开头都有地址信息。用hexdump -C或者xxd查看,能快速定位加载地址。
  • ELF文件看符号:如果固件带符号表,直接用nm firmware.elf | grep -i main找到main函数,入口点就在它附近。
一句话总结: 加载地址是固件分析的基石,入口点是逆向的起点。搞清楚了这两个,后面的反汇编、动态调试才能有的放矢。

专注资料整理