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 知识体系结构图
下面这张图,帮你理清固件加载与入口点分析的核心逻辑:
4.6 实战小技巧
最后,分享几个我常用的技巧:
- BIN文件猜地址:如果固件开头是
00 00 00 00或者FF FF FF FF,那很可能是未初始化的区域。真正的代码在后面。 - HEX文件看地址:HEX文件每行开头都有地址信息。用
hexdump -C或者xxd查看,能快速定位加载地址。 - ELF文件看符号:如果固件带符号表,直接用
nm firmware.elf | grep -i main找到main函数,入口点就在它附近。
一句话总结: 加载地址是固件分析的基石,入口点是逆向的起点。搞清楚了这两个,后面的反汇编、动态调试才能有的放矢。