3、固件基础知识:固件类型(Bootloader、RTOS、应用固件)、文件系统、固件打包格式

在嵌入式固件逆向分析中,第一步往往不是直接面对二进制指令,而是先回答三个问题:这段固件属于哪一层?它内部的文件是如何组织的?它是以什么格式被封装和分发的? 这三个问题决定了后续使用何种工具链、从何处定位关键代码、以及如何正确解析固件中的资源。本章将系统梳理固件的三大类型(Bootloader、RTOS、应用固件)、嵌入式文件系统的常见形态,以及主流固件打包格式的底层结构,为后续的静态分析与动态调试建立必要的知识坐标系。

嵌入式固件类型与存储结构总览 Bootloader · 上电最先执行 · 初始化时钟/DDR/外设 · 引导RTOS或App · 常见:U-Boot、Barebox 逆向关注:启动流程、校验逻辑 RTOS内核 · 任务调度/内存管理 · 信号量/消息队列 · 常见:FreeRTOS、RT-Thread · 与App固件混合编译 逆向关注:任务表、调度器入口 应用固件 · 业务逻辑/协议栈 · 外设驱动/用户接口 · 通常体积最大 · 含文件系统镜像 逆向关注:协议处理、加密逻辑 嵌入式文件系统(位于Flash/存储介质之上) SquashFS(只读压缩) JFFS2/UBIFS(NAND Flash) CramFS(老式压缩) ext4/FAT(SD/eMMC) 逆向关注:镜像偏移、超级块、压缩算法、文件恢复 固件打包格式(分发与烧录载体) Bin(裸二进制) Intel HEX ELF(含调试信息) U-Boot FIT/ITS TRX/CHK(路由器) 逆向关注:头部解析、校验和、加密标志、分区表

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 判断方法:如何从二进制中识别固件类型

当拿到一个未知的固件文件时,可以通过以下特征进行初步分类:

特征维度BootloaderRTOS内核应用固件
入口地址通常位于 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 的硬件初始化代码。

经验提示: 在分析 IoT 设备固件时,优先提取 Bootloader 中的串口打印信息(如 "U-Boot 2020.04"),这能快速确定芯片型号和内存布局,为后续分析提供关键参数。

3.1.3 案例拆解:某路由器固件的三层识别

假设我们拿到一个 16MB 的路由器固件 router.bin,通过 binwalk 扫描后,发现偏移 0x0 处为 U-Boot 镜像,偏移 0x40000 处为 Linux 内核(RTOS 的一种),偏移 0x200000 处为 SquashFS 文件系统(应用固件)。分析过程如下:

  1. 初始观察: 使用 binwalk router.bin 输出显示三段明显的文件签名,分别对应 U-Boot、LZMA 压缩内核和 SquashFS。
  2. 提取 Bootloader: dd if=router.bin of=uboot.bin bs=1 skip=0 count=262144,然后用 Ghidra 加载,入口处看到 reset 向量和 DDR 初始化代码。
  3. 定位内核: 提取内核镜像后,使用 vmlinux-to-elf 工具转换为 ELF,发现包含 "Linux version 3.10.14" 字符串,确认是 RTOS 层。
  4. 解析文件系统: 使用 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 进行熵分析,高熵区域通常表示压缩或加密数据。

风险提示: 某些设备会对文件系统进行加密或混淆(如自定义 XOR 或 AES),此时 binwalk 无法直接识别。需要先分析 Bootloader 中的解密例程,提取密钥后再进行解密。

3.2.3 案例拆解:SquashFS 的完整解析流程

以某 IP 摄像头固件为例,binwalk 输出显示偏移 0x2A0000 处存在 SquashFS 文件系统。操作步骤如下:

  1. 提取镜像: dd if=camera.bin of=squashfs.img bs=1 skip=2752512(0x2A0000 的十进制值)。
  2. 验证类型: file squashfs.img 输出 "Squashfs filesystem, little endian, version 4.0, compression: xz"。
  3. 解包: unsquashfs squashfs.img,得到完整的文件目录树。
  4. 分析内容: 在解包后的 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 镜像的长度
0x08CRC324 字节从偏移 0x0C 开始的数据校验
0x0C标志4 字节通常为 0
0x10分区偏移4×4 字节最多 4 个分区的偏移地址

使用 binwalk -R "HDR0" 可以快速定位 TRX 头部,然后根据长度字段提取整个镜像。对于 FIT 镜像,则需要解析 ITS 文件中的描述,找到各子镜像的偏移和大小。

实战技巧: 当遇到未知打包格式时,可以尝试以下步骤:1) 使用 binwalk -E 查看熵分布,找出高熵区域(通常是压缩或加密数据);2) 在头部区域搜索 ASCII 字符串(如 "TRX"、"CHK"、"FIT");3) 使用 hexdump -C 检查头部前 64 字节,寻找长度和校验字段的规律。

3.3.3 案例拆解:从 TRX 固件中提取内核与文件系统

某 Broadcom 路由器固件 router.trx,大小 8MB。分析过程:

  1. 定位头部: binwalk -R "HDR0" router.trx 输出偏移 0x0 处找到魔数。
  2. 解析头部: 使用 hexdump -C -s 0 -n 32 router.trx,得到长度字段为 0x800000,CRC32 为 0x1A2B3C4D。
  3. 提取分区: 头部偏移 0x10 处的分区偏移表显示:分区 0 偏移 0x1C(内核),分区 1 偏移 0x200000(文件系统)。
  4. 分离镜像: dd if=router.trx of=kernel.bin bs=1 skip=28 count=2097124,dd if=router.trx of=rootfs.bin bs=1 skip=2097152。
  5. 验证: file kernel.bin 显示 "Linux kernel ARM boot executable zImage",file rootfs.bin 显示 "Squashfs filesystem"。

该案例展示了从 TRX 容器到内核和文件系统的完整提取流程,整个过程仅需数分钟。

3.3.4 常见误区与复盘

  • 误区一: 忽略头部中的 CRC 校验。修改固件后必须重新计算 CRC,否则设备拒绝升级。
  • 误区二: 认为分区偏移是固定的。某些固件在头部使用相对偏移,需要结合头部基址计算。
  • 误区三: 混淆大小端。TRX 头部在 ARM 平台通常为小端,在 MIPS 平台可能为大端,解析时需注意。

复盘要点: 在解析任何打包格式时,先绘制头部字段的字节序和偏移图,再编写脚本批量提取,避免手动计算错误。

总结: 固件基础知识是逆向分析的基石。掌握 Bootloader、RTOS 和应用固件的分层特征,熟悉 SquashFS、JFFS2 等文件系统的识别方法,理解 TRX、FIT 等打包格式的头部结构,能够帮助分析者快速定位目标代码、提取关键文件,并为后续的漏洞挖掘和固件修改奠定坚实基础。建议在实际分析中,始终从启动链和文件系统两个维度建立固件的整体视图。