3、固件基础知识:固件类型(Bootloader、RTOS、应用固件)、文件系统、固件打包格式
在嵌入式固件逆向分析中,第一步往往不是直接面对二进制指令,而是先回答三个问题:这段固件属于哪一层?它内部的文件是如何组织的?它是以什么格式被封装和分发的? 这三个问题决定了后续使用何种工具链、从何处定位关键代码、以及如何正确解析固件中的资源。本章将系统梳理固件的三大类型(Bootloader、RTOS、应用固件)、嵌入式文件系统的常见形态,以及主流固件打包格式的底层结构,为后续的静态分析与动态调试建立必要的知识坐标系。
3.1 固件三大类型:Bootloader、RTOS 与应用固件
从系统启动的视角看,嵌入式固件并非单一整体,而是由多个职责不同的软件层构成。理解这些层次有助于逆向分析时快速定位目标代码段,避免在无关区域浪费时间。
3.1.1 核心原理:启动链与职责边界
一个典型的嵌入式系统启动链为:ROM Bootloader → 一级引导(SPL/MLO)→ 二级引导(U-Boot)→ RTOS/内核 → 应用固件。每一层都负责特定的硬件初始化和交接工作。Bootloader 通常运行在芯片复位后的第一条指令处,负责设置 CPU 模式、初始化 DDR 控制器、时钟 PLL、串口等基础外设,然后将控制权交给下一阶段。RTOS 内核则提供任务调度、中断管理、内存分配等基础服务,而应用固件承载具体的业务逻辑,如网络协议栈、用户界面、传感器数据处理等。
在逆向分析中,区分这三类固件至关重要:Bootloader 中通常包含硬件寄存器配置序列和启动校验逻辑,是理解硬件平台的关键;RTOS 部分则包含任务控制块(TCB)、调度器入口和内核对象(信号量、队列)的定义;应用固件则是功能逻辑最密集的区域,也是漏洞挖掘的主要目标。
3.1.2 判断方法:如何从二进制中识别固件类型
当拿到一个未知的固件文件时,可以通过以下特征进行初步分类:
| 特征维度 | Bootloader | RTOS内核 | 应用固件 |
|---|---|---|---|
| 入口地址 | 通常位于 Flash 起始地址(0x0 或 0x08000000) | 由 Bootloader 跳转,地址固定 | 由 RTOS 加载,地址灵活 |
| 字符串特征 | 包含 "U-Boot"、"Barebox"、DDR 初始化参数 | 包含 "FreeRTOS"、"Task"、"Semaphore" | 包含业务相关字符串、版本号、URL |
| 代码密度 | 大量外设寄存器操作(如 0x40021000 等) | 上下文切换代码、调度器入口 | 协议解析、状态机、算法实现 |
| 体积特征 | 通常 64KB ~ 2MB | 几十 KB ~ 数百 KB | 数百 KB ~ 数十 MB |
实际操作中,可以使用 binwalk 扫描固件中的字符串和文件签名,再结合反汇编工具(如 Ghidra、IDA Pro)查看入口点附近的代码风格。例如,若在入口处发现大量对 0x40000000 附近地址的读写操作,则极可能是 Bootloader 的硬件初始化代码。
3.1.3 案例拆解:某路由器固件的三层识别
假设我们拿到一个 16MB 的路由器固件 router.bin,通过 binwalk 扫描后,发现偏移 0x0 处为 U-Boot 镜像,偏移 0x40000 处为 Linux 内核(RTOS 的一种),偏移 0x200000 处为 SquashFS 文件系统(应用固件)。分析过程如下:
- 初始观察: 使用
binwalk router.bin输出显示三段明显的文件签名,分别对应 U-Boot、LZMA 压缩内核和 SquashFS。 - 提取 Bootloader:
dd if=router.bin of=uboot.bin bs=1 skip=0 count=262144,然后用 Ghidra 加载,入口处看到reset向量和 DDR 初始化代码。 - 定位内核: 提取内核镜像后,使用
vmlinux-to-elf工具转换为 ELF,发现包含 "Linux version 3.10.14" 字符串,确认是 RTOS 层。 - 解析文件系统: 使用
unsquashfs解包 SquashFS,得到 Web 管理界面、二进制程序和应用配置。
该案例表明,通过分层识别,可以快速将 16MB 的固件拆解为三个可独立分析的模块,大幅降低逆向分析的复杂度。
3.1.4 常见误区与复盘
- 误区一: 认为所有固件都是单一二进制。实际上,现代设备普遍采用多阶段引导,忽略 Bootloader 可能导致遗漏硬件初始化逻辑。
- 误区二: 将 RTOS 与应用固件混为一谈。FreeRTOS 等 RTOS 内核与应用代码通常编译在同一镜像中,但通过符号表或任务列表可以区分。
- 误区三: 忽视 Bootloader 中的安全校验。许多设备在 Bootloader 中实现签名验证,若跳过该部分,后续的固件修改分析将失去基础。
复盘要点: 在分析任何固件前,先绘制启动链图,标注各阶段的地址范围和大小,再决定从哪一层开始深入。
3.2 嵌入式文件系统:结构与识别
文件系统是固件中组织和存储文件的方式。与 PC 不同,嵌入式文件系统通常针对 Flash 介质的特性(如擦除块大小、写寿命)进行了优化,且多为只读或可读写混合模式。
3.2.1 核心原理:Flash 介质与文件系统的适配
NOR Flash 支持字节级随机读取,适合存放代码;NAND Flash 以页为单位读写,适合大容量数据存储。因此,嵌入式文件系统分为两大类:面向 NOR 的只读压缩文件系统(如 CramFS、SquashFS)和面向 NAND 的日志型文件系统(如 JFFS2、UBIFS)。SquashFS 是目前最主流的只读文件系统,支持 gzip/lzma/lzo 压缩,广泛用于路由器、IP 摄像头等设备。JFFS2 和 UBIFS 则支持磨损均衡和掉电恢复,常用于需要频繁写入的工业设备。
3.2.2 判断方法:识别文件系统类型与偏移
识别固件中文件系统的最常用工具是 binwalk,它通过魔数签名(Magic Number)匹配已知文件系统。例如,SquashFS 的魔数为 hsqs(0x68737173),JFFS2 的魔数为 0x1985。当 binwalk 无法识别时,可以手动搜索文件系统特征:
- SquashFS: 搜索 "hsqs" 字符串,其后 4 字节为版本号,再后 4 字节为压缩类型。
- JFFS2: 搜索 0x1985 或 0x1986 的 16 位值,通常每 4KB 对齐。
- CramFS: 搜索 "CramFS" 字符串,通常位于镜像头部。
对于无法自动识别的文件系统,可以尝试使用 binwalk -A 进行熵分析,高熵区域通常表示压缩或加密数据。
3.2.3 案例拆解:SquashFS 的完整解析流程
以某 IP 摄像头固件为例,binwalk 输出显示偏移 0x2A0000 处存在 SquashFS 文件系统。操作步骤如下:
- 提取镜像:
dd if=camera.bin of=squashfs.img bs=1 skip=2752512(0x2A0000 的十进制值)。 - 验证类型:
file squashfs.img输出 "Squashfs filesystem, little endian, version 4.0, compression: xz"。 - 解包:
unsquashfs squashfs.img,得到完整的文件目录树。 - 分析内容: 在解包后的
etc/目录下发现shadow文件,usr/bin/下发现主程序app_server,web/目录下发现 Web 管理页面。
该案例展示了从原始固件到文件级访问的完整链路,为后续的漏洞挖掘(如命令注入、硬编码凭据)提供了基础。
3.2.4 常见误区与复盘
- 误区一: 认为所有文件系统都是可读写的。SquashFS 是只读的,修改文件后需要重新打包并更新校验值。
- 误区二: 忽略文件系统末尾的填充数据。某些设备在文件系统后附加了配置分区或日志分区,需要一并提取。
- 误区三: 混淆 JFFS2 的节点结构。JFFS2 的节点包含 CRC 校验,修改文件后必须重新计算 CRC,否则设备无法挂载。
复盘要点: 在解包文件系统后,应首先检查 etc/passwd、etc/shadow、init.d/ 等关键文件,这些往往是安全分析的突破口。
3.3 固件打包格式:从裸二进制到复合镜像
固件打包格式决定了固件如何被分发、烧录和升级。不同厂商和芯片平台采用不同的格式,理解这些格式的头部结构是提取和分析固件的前提。
3.3.1 核心原理:常见打包格式的头部结构
最基础的格式是裸二进制(Raw Binary),即纯代码和数据,无任何头部信息,通常用于 MCU 固件。Intel HEX 和 Motorola S-Record 是文本格式的固件,每行包含地址、数据和校验和,常用于烧录器。ELF 格式则包含段表、符号表和调试信息,是分析阶段最理想的格式。对于复杂的嵌入式系统(如路由器、手机),厂商会自定义打包格式,常见的有:
- TRX 格式: 用于 Broadcom 平台路由器,头部包含魔数 "TRX"、长度、CRC32 和分区偏移。
- U-Boot FIT/ITS: 基于设备树(DTS)的描述文件,将内核、设备树、ramdisk 打包为一个 FIT 镜像。
- CHK 格式: 用于 Atheros 平台,头部包含魔数 "0x43484B"(CHK)、版本、长度和校验。
3.3.2 判断方法:解析头部与提取有效载荷
解析打包格式的核心是定位头部字段并提取有效载荷。以 TRX 格式为例,其头部结构如下:
| 偏移 | 字段 | 大小 | 说明 |
|---|---|---|---|
| 0x00 | 魔数 | 4 字节 | 0x30524448("HDR0") |
| 0x04 | 长度 | 4 字节 | 整个 TRX 镜像的长度 |
| 0x08 | CRC32 | 4 字节 | 从偏移 0x0C 开始的数据校验 |
| 0x0C | 标志 | 4 字节 | 通常为 0 |
| 0x10 | 分区偏移 | 4×4 字节 | 最多 4 个分区的偏移地址 |
使用 binwalk -R "HDR0" 可以快速定位 TRX 头部,然后根据长度字段提取整个镜像。对于 FIT 镜像,则需要解析 ITS 文件中的描述,找到各子镜像的偏移和大小。
binwalk -E 查看熵分布,找出高熵区域(通常是压缩或加密数据);2) 在头部区域搜索 ASCII 字符串(如 "TRX"、"CHK"、"FIT");3) 使用 hexdump -C 检查头部前 64 字节,寻找长度和校验字段的规律。
3.3.3 案例拆解:从 TRX 固件中提取内核与文件系统
某 Broadcom 路由器固件 router.trx,大小 8MB。分析过程:
- 定位头部:
binwalk -R "HDR0" router.trx输出偏移 0x0 处找到魔数。 - 解析头部: 使用
hexdump -C -s 0 -n 32 router.trx,得到长度字段为 0x800000,CRC32 为 0x1A2B3C4D。 - 提取分区: 头部偏移 0x10 处的分区偏移表显示:分区 0 偏移 0x1C(内核),分区 1 偏移 0x200000(文件系统)。
- 分离镜像:
dd if=router.trx of=kernel.bin bs=1 skip=28 count=2097124,dd if=router.trx of=rootfs.bin bs=1 skip=2097152。 - 验证:
file kernel.bin显示 "Linux kernel ARM boot executable zImage",file rootfs.bin显示 "Squashfs filesystem"。
该案例展示了从 TRX 容器到内核和文件系统的完整提取流程,整个过程仅需数分钟。
3.3.4 常见误区与复盘
- 误区一: 忽略头部中的 CRC 校验。修改固件后必须重新计算 CRC,否则设备拒绝升级。
- 误区二: 认为分区偏移是固定的。某些固件在头部使用相对偏移,需要结合头部基址计算。
- 误区三: 混淆大小端。TRX 头部在 ARM 平台通常为小端,在 MIPS 平台可能为大端,解析时需注意。
复盘要点: 在解析任何打包格式时,先绘制头部字段的字节序和偏移图,再编写脚本批量提取,避免手动计算错误。