固件逆向工程基础:固件结构解析、二进制文件格式(ELF/BIN)、固件提取与解包
各位同学,今天我们来聊聊固件逆向工程里最基础、也最绕不开的一步——搞清楚固件到底长什么样。说实话,我见过太多新手一上来就拿着IDA对着一个.bin文件猛分析,结果折腾半天连入口点都没找到。为什么?因为他根本不知道这个固件是什么格式、怎么组织的。
所以这一章,咱们就把地基打牢。我会从固件的整体结构讲起,然后深入到ELF和BIN这两种最常见的二进制格式,最后再聊聊怎么把固件从设备里“请”出来并拆解开。嗯,这些都是我这些年踩坑踩出来的经验,你跟着走一遍,能少走不少弯路。
一、固件结构:它到底是个什么玩意儿?
固件,说白了就是烧录在ROM/Flash里的软件。但它不是随便堆在一起的二进制数据,而是有组织、有结构的。我习惯把固件想象成一个“三明治”:
- 最上层:引导加载程序(Bootloader)——负责初始化硬件、加载主固件。比如U-Boot、Coreboot。
- 中间层:操作系统内核——Linux、RTOS或者裸机程序。这是固件的核心逻辑。
- 最下层:文件系统/数据区——存放配置文件、Web页面、证书、密钥等。
你想想看,如果不知道这三层是怎么拼接的,你连从哪里开始分析都不知道。我在一个路由器固件分析项目中就遇到过这种情况——明明已经解包了,但死活找不到Web管理界面的代码。后来才发现,文件系统被压缩后藏在了内核镜像的末尾。嗯,这种“套娃”操作在IoT设备里太常见了。
核心要点:固件逆向的第一步,永远是“识别结构”。别急着反汇编,先搞清楚它是什么格式、怎么组织的。
下面这张图是我自己整理的固件结构分析流程,你可以对照着看:
二、二进制文件格式:ELF vs BIN
搞固件逆向,你打交道最多的就是两种格式:ELF和BIN。它们有什么区别?我简单给你捋一捋。
2.1 ELF(Executable and Linkable Format)
ELF是Linux世界里最通用的可执行文件格式。你平时在Linux下跑的ls、grep,还有交叉编译出来的ARM/MIPS程序,基本都是ELF。它的结构非常清晰,我习惯把它分成三部分来看:
- ELF头(ELF Header):文件最开头,固定64字节(64位系统)。里面包含了魔数(0x7f 'ELF')、架构(ARM/MIPS/x86)、入口点地址、段表偏移等信息。
- 程序头(Program Header):描述运行时如何加载到内存。每个段(Segment)对应一个程序头。
- 节头(Section Header):描述链接和调试信息。每个节(Section)对应一个节头,比如.text(代码)、.data(数据)、.rodata(只读数据)、.bss(未初始化数据)。
我的小技巧:拿到一个ELF文件,我第一件事就是看它的入口点。用readelf -h或者objdump -f就能看到。入口点通常指向_start函数,这是分析的起点。我曾经遇到过一个恶意固件,它的入口点被修改到了数据段,差点把我带沟里去。
来看一个实际的例子。假设你有一个ARM架构的ELF文件,用readelf查看头信息:
$ readelf -h firmware.elf
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Entry point address: 0x400080
Start of program headers: 64 (bytes into file)
Start of section headers: 123456 (bytes into file)
Number of program headers: 8
Number of section headers: 28
看到Entry point address了吗?0x400080,这就是CPU第一条指令的地址。接下来你可以用objdump -d反汇编这个地址附近的代码。
2.2 BIN(原始二进制)
BIN格式就简单多了——它没有头,没有段表,没有节表。说白了就是纯粹的机器码和数据,从某个起始地址开始直接烧录到Flash里。很多嵌入式设备(路由器、摄像头、智能家居)的固件都是BIN格式。
为什么用BIN?因为嵌入式设备的Bootloader不需要解析复杂的文件格式,它只需要把Flash里的数据拷贝到RAM里,然后跳过去执行就行。简单、粗暴、高效。
注意:BIN格式没有入口点信息!你拿到一个.bin文件,第一件事就是猜它的加载地址。猜错了,反汇编出来的代码全是错的。我常用的方法是:
- 看Bootloader的源码或文档,找到它加载固件的地址
- 用binwalk分析,看它能否自动识别
- 手动分析——找中断向量表(ARM的Reset向量通常在0x0或0x8000)
三、固件提取与解包:把大象装进冰箱需要几步?
好,现在你知道了固件长什么样。下一步就是把它从设备里“请”出来,然后拆开看看里面到底有什么。我把它分成三步:
3.1 获取固件
获取固件的方式有很多,我按难度排个序:
| 方法 | 难度 | 说明 |
|---|---|---|
| 官网下载 | ★☆☆☆☆ | 最简单,但厂商可能只提供加密或差分更新包 |
| OTA抓包 | ★★★☆☆ | 抓取设备升级时的HTTP/HTTPS流量,分析固件下载链接 |
| 编程器读取 | ★★★★☆ | 拆机,用SPI/NAND编程器直接读取Flash芯片内容 |
| JTAG/SWD调试 | ★★★★★ | 通过调试接口dump内存或Flash,需要硬件知识 |
我个人最常用的是“官网下载+OTA抓包”组合。编程器读取虽然最彻底,但需要拆机,而且有些Flash芯片有读保护,挺麻烦的。
3.2 识别固件格式
拿到固件后,别急着解包。先用file命令和binwalk看看它到底是什么:
$ file firmware.bin
firmware.bin: data
$ binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 uImage header, header size: 64 bytes, ...
64 0x40 LZMA compressed data, properties: 0x5D, ...
131072 0x20000 Squashfs filesystem, little endian, ...
看到了吗?binwalk一下子就识别出了三个东西:uImage头、LZMA压缩数据、Squashfs文件系统。这就是固件的“三层结构”。
避坑指南:我曾经遇到过一个固件,binwalk怎么都识别不出来。后来我用hexdump一看,发现厂商把魔数改掉了——把"uImage"改成了"myImage"。这种小把戏在国产设备里很常见。所以,别完全依赖工具,有时候得手动分析。
3.3 解包
识别出格式后,解包就水到渠成了。常用的工具有:
- binwalk -e:自动解包,支持大部分常见格式
- dd:手动切割,适合知道偏移量的情况
- unsquashfs:解压Squashfs文件系统
- jefferson:解压JFFS2文件系统
- firmware-mod-kit:老牌固件解包工具集
举个例子,用binwalk自动解包:
$ binwalk -e firmware.bin
Scan Time: 2024-01-15 10:30:00
Target File: firmware.bin
MD5 Checksum: a1b2c3d4e5f6...
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 uImage header, ...
64 0x40 LZMA compressed data, ...
131072 0x20000 Squashfs filesystem, ...
Extraction completed. Output directory: _firmware.bin.extracted/
解包完成后,你会得到一个目录,里面包含了所有提取出来的文件。这时候你就可以开始真正的逆向分析了——找Web界面、找硬编码密码、找调试接口、找漏洞。
我的习惯:解包后第一件事,用grep -r "password"搜索硬编码密码。第二件事,找/etc/shadow或者/etc/passwd文件。第三件事,看启动脚本/etc/init.d/或者/etc/rc.d/。这三个地方往往藏着最多的“惊喜”。
四、总结
这一章我们聊了固件逆向的“地基”——固件结构、ELF/BIN格式、提取与解包。说白了,就是搞清楚“固件是什么、怎么拿到它、怎么拆开它”。
你想想看,如果连固件是ELF还是BIN都分不清,连入口点都找不到,后面的分析根本无从谈起。所以,别嫌这些基础内容枯燥。我当年刚入行时,就是因为没搞懂这些,在一个简单的路由器固件上浪费了整整一周。
嗯,下一章我们会深入ELF的内部结构,手把手教你分析段表和节表。但今天的内容,建议你找个真实的固件练练手——去OpenWrt官网下载一个路由器固件,用binwalk解包,看看里面都有什么。实践出真知。
公众号:蓝海资料掘金营,微信deep3321