固件逆向工程基础:固件结构解析、二进制文件格式(ELF/BIN)、固件提取与解包

各位同学,今天我们来聊聊固件逆向工程里最基础、也最绕不开的一步——搞清楚固件到底长什么样。说实话,我见过太多新手一上来就拿着IDA对着一个.bin文件猛分析,结果折腾半天连入口点都没找到。为什么?因为他根本不知道这个固件是什么格式、怎么组织的。

所以这一章,咱们就把地基打牢。我会从固件的整体结构讲起,然后深入到ELF和BIN这两种最常见的二进制格式,最后再聊聊怎么把固件从设备里“请”出来并拆解开。嗯,这些都是我这些年踩坑踩出来的经验,你跟着走一遍,能少走不少弯路。

一、固件结构:它到底是个什么玩意儿?

固件,说白了就是烧录在ROM/Flash里的软件。但它不是随便堆在一起的二进制数据,而是有组织、有结构的。我习惯把固件想象成一个“三明治”:

  • 最上层:引导加载程序(Bootloader)——负责初始化硬件、加载主固件。比如U-Boot、Coreboot。
  • 中间层:操作系统内核——Linux、RTOS或者裸机程序。这是固件的核心逻辑。
  • 最下层:文件系统/数据区——存放配置文件、Web页面、证书、密钥等。

你想想看,如果不知道这三层是怎么拼接的,你连从哪里开始分析都不知道。我在一个路由器固件分析项目中就遇到过这种情况——明明已经解包了,但死活找不到Web管理界面的代码。后来才发现,文件系统被压缩后藏在了内核镜像的末尾。嗯,这种“套娃”操作在IoT设备里太常见了。

核心要点:固件逆向的第一步,永远是“识别结构”。别急着反汇编,先搞清楚它是什么格式、怎么组织的。

下面这张图是我自己整理的固件结构分析流程,你可以对照着看:

固件结构分析流程 ① 获取固件 官网下载 / 编程器读取 / OTA抓包 ② 格式识别 file命令 / binwalk / hexdump 魔数 ③ 解包 binwalk -e / dd / unsquashfs / jefferson ④ 分析 反汇编 / 字符串提取 / 漏洞挖掘 / 密钥提取 ELF格式 BIN格式 💡 核心原则:先结构,后细节;先识别,后分析

二、二进制文件格式: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