固件基础知识:固件定义、固件类型与固件文件格式
固件(Firmware)是嵌入式系统中最接近硬件的一层软件形态,它直接运行在处理器或微控制器之上,负责硬件初始化、资源调度、协议解析与业务逻辑执行。与普通应用程序不同,固件通常存储在非易失性存储器(如 NOR Flash、NAND Flash、eMMC、SPI Flash)中,上电后由 BootROM 引导加载。理解固件的定义、分类与文件格式,是开展二进制逆向分析、固件提取、漏洞挖掘和移植改造的前提。本章将从三个维度展开:固件的本质定义、固件在嵌入式系统中的类型划分(Bootloader、RTOS、应用固件),以及三种最常见的固件文件格式(BIN、HEX、ELF)的结构特征与解析方法。
固件的本质定义与存储形态
固件是嵌入式系统中固化在非易失性存储器中的软件程序,它直接面向处理器指令集和硬件寄存器,不依赖操作系统或中间件即可完成硬件初始化与基本控制。固件的核心特征在于其“半硬件半软件”属性:它由软件代码构成,但运行行为与硬件电路强耦合,且通常不提供用户可交互的界面。在逆向工程中,固件是分析目标的主要载体——无论是提取固件镜像、定位漏洞函数,还是理解设备启动流程,都需要先明确固件的存储位置、加载地址和运行方式。
固件的存储介质决定了提取难度和分析路径。常见存储介质包括:SPI NOR Flash(常用于 Bootloader 和低容量固件)、NAND Flash(高容量,存在坏块管理和页/块结构)、eMMC(内置控制器,包含分区表)、以及 MCU 内部 Flash(通过调试接口或 BootROM 读取)。固件在存储介质中的布局通常不是单一镜像,而是由 Bootloader、内核、文件系统、配置参数等多个分区组成。逆向分析的第一步,往往是从 Flash 全量镜像中识别各分区的边界和类型。
固件类型:Bootloader、RTOS 与应用固件
从功能层次划分,嵌入式固件通常分为三类:Bootloader、RTOS(实时操作系统)内核、以及应用固件。三者承担不同职责,在逆向分析时也需要采用不同的策略和工具链。
Bootloader(引导加载程序)
Bootloader 是设备上电后最先执行的固件,负责初始化 CPU、内存控制器、时钟、外设等基础硬件,并引导加载真正的操作系统或应用固件。常见的 Bootloader 包括 U-Boot、Barebox、Coreboot,以及 MCU 厂商提供的 BootROM 和自定义引导程序。Bootloader 的逆向重点在于:启动流程分析(从 Reset Vector 到跳转地址)、硬件初始化序列(寄存器配置)、以及引导参数传递(如设备树 Blob、启动参数结构体)。
RTOS(实时操作系统内核)
RTOS 固件提供任务调度、中断管理、信号量、消息队列等内核服务,常见的有 FreeRTOS、RT-Thread、uC/OS-III、Zephyr 等。RTOS 固件通常与应用代码编译链接成一个整体镜像,没有独立的文件系统或用户态/内核态隔离。逆向 RTOS 固件时,需要识别任务控制块(TCB)结构、任务入口函数、调度器实现方式,以及内核对象(如队列、互斥锁)的内存布局。通过定位 RTOS 的启动函数(如 vTaskStartScheduler)和空闲任务,可以快速建立分析锚点。
应用固件(Application Firmware)
应用固件实现设备的具体业务功能,如协议栈、传感器数据处理、用户接口控制等。在带操作系统的设备中,应用固件可能以独立进程或动态库形式存在;在裸机或 RTOS 环境中,应用固件与内核代码混合编译。应用固件的逆向重点在于业务逻辑还原、协议解析、加密算法识别和漏洞挖掘。分析时通常需要结合字符串引用、函数调用关系和外部输入接口(如 UART、网络、USB)来定位关键处理函数。
| 固件类型 | 典型特征 | 逆向分析重点 | 常用工具/方法 |
|---|---|---|---|
| Bootloader | 启动最早、代码量小、直接操作寄存器 | Reset Vector、DDR 初始化、跳转地址 | Ghidra(ARM/Thumb)、objdump、JTAG 调试 |
| RTOS 内核 | 任务调度、内核对象、无 MMU 隔离 | TCB 结构、任务入口、调度器函数 | FreeRTOS 插件、字符串扫描、结构体逆向 |
| 应用固件 | 业务逻辑、协议处理、用户交互 | 协议解析、加密算法、漏洞函数 | Frida 动态插桩、模拟执行(Unicorn)、符号恢复 |
vTaskStartScheduler 的符号引用,可以确认是否使用了 FreeRTOS,并进一步定位任务创建函数 xTaskCreate 的调用点。
固件文件格式:BIN、HEX 与 ELF
固件在开发、烧录和发布阶段会以不同文件格式存在。理解这些格式的差异,对于固件提取、格式转换和逆向加载至关重要。三种最常见的格式分别是:BIN(原始二进制镜像)、HEX(Intel HEX 文本格式)和 ELF(Executable and Linkable Format)。
BIN 格式
BIN 文件是纯二进制数据,不包含任何地址信息、校验和或元数据。它直接从固件的加载地址开始排列,烧录时按顺序写入 Flash。BIN 格式的优点是简洁、无冗余,适合直接分析;缺点是没有地址映射,逆向时需手动指定加载基址。例如,一个 ARM Cortex-M 的固件 BIN 文件,前 4 字节通常是初始栈指针(MSP),紧接着 4 字节是 Reset Handler 地址。
# 查看 BIN 文件头部信息(以 STM32 固件为例)
xxd firmware.bin | head -n 4
# 输出示例:
# 00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
# 00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
# 实际有效固件通常从 0x08000000 映射,BIN 文件偏移 0 对应 Flash 起始地址
HEX 格式
Intel HEX 是一种文本格式,每行记录包含起始地址、数据类型、数据长度和校验和。它支持非连续地址布局,适合烧录到不同 Flash 区域。HEX 格式在逆向时需先解析为 BIN 或直接加载到支持地址映射的反汇编器中。HEX 记录类型包括:数据记录(00)、文件结束记录(01)、扩展段地址记录(02)、扩展线性地址记录(04)等。
:020000040800F2 ; 扩展线性地址记录,高16位地址 = 0x0800
:1000000000000000000000000000000000000000F0 ; 16字节数据,起始地址 0x0000
:00000001FF ; 文件结束记录
ELF 格式
ELF 是 Linux 和多数 RTOS 使用的可执行文件格式,包含程序头(Program Header)、节头(Section Header)、符号表(Symbol Table)和调试信息(可选)。ELF 固件通常保留了符号信息,极大方便逆向分析。但实际发布到设备中的固件往往经过 strip 处理,符号被移除。逆向 ELF 固件时,可直接用 Ghidra 或 IDA 加载,自动识别入口点和节区布局。
| 格式 | 地址信息 | 校验机制 | 逆向加载方式 | 典型应用场景 |
|---|---|---|---|---|
| BIN | 无,需手动指定基址 | 无 | 指定基址后反汇编 | Flash 全量镜像、MCU 固件 |
| HEX | 每行记录包含地址 | 每行校验和 | 解析后按地址加载 | 烧录文件、小容量固件 |
| ELF | 程序头/节头完整 | 无(依赖文件完整性) | 直接加载,保留符号 | 开发调试版本、Linux 固件 |
固件格式识别与转换实操
在实际逆向项目中,经常需要将 HEX 转换为 BIN,或从 ELF 中提取可执行的二进制段。以下是一个典型的操作流程,适用于固件提取后的预处理阶段。
- 识别文件类型:使用
file命令或十六进制查看工具检查文件头。ELF 文件以0x7F 0x45 0x4C 0x46开头;HEX 文件以:开头;BIN 文件无固定特征,需结合上下文判断。 - HEX 转 BIN:使用
objcopy工具:objcopy -I ihex -O binary firmware.hex firmware.bin。转换时注意地址偏移,必要时使用--change-addresses调整。 - ELF 提取段:使用
objcopy -O binary --only-section=.text firmware.elf text.bin提取指定节区,或使用readelf -S查看所有节区。 - BIN 基址猜测:对于未知 BIN,可通过分析中断向量表(ARM Cortex-M)或搜索字符串引用的交叉参考来推测基址。例如,在 Ghidra 中尝试多个基址,观察反汇编结果中函数序言(PUSH/STMFD)的比例。
binwalk 扫描发现偏移 0x100000 处存在 SquashFS 文件系统,偏移 0x500000 处存在 LZMA 压缩数据。进一步分析发现,0x0 到 0x100000 区域是 U-Boot Bootloader。使用 Ghidra 加载该区域,基址设为 0x80000000(根据 U-Boot 链接脚本),成功识别出 board_init、relocate_code 等关键函数。随后在 0x80000 处发现一个未压缩的 RTOS 内核(FreeRTOS),通过搜索 "IDLE" 任务名定位到任务调度代码。整个分析过程的关键在于:先通过文件格式识别和分区边界划分,再针对不同固件类型采用差异化分析策略。
固件逆向的常见误区与复盘
在固件基础知识的应用中,初学者常犯以下错误,导致分析效率低下或结果错误。
- 忽略字节序:ARM 处理器支持大端和小端模式,MIPS 也有大小端变体。加载固件前必须确认目标架构的字节序,否则所有立即数和地址都会反转。
- 混淆虚拟地址与物理地址:带 MMU 的处理器(如 ARM Cortex-A)中,固件内代码使用虚拟地址,而 Flash 中的存储位置是物理地址。逆向时需通过页表或链接脚本还原映射关系。
- 忽视压缩与加密:许多商业固件对内核或文件系统进行压缩(如 LZMA、gzip)或加密(如 AES、XOR)。直接对压缩数据反汇编会产生大量无意义指令。应先使用
binwalk或熵分析识别压缩段,解压后再分析。 - 不验证基址假设:基址错误是逆向中最隐蔽的问题。建议通过多种方式交叉验证:检查中断向量表、查找字符串引用的地址、分析函数调用指令(BL/BLX)的目标范围。