3、固件结构剖析:Bootloader、内核、文件系统、配置分区

拿到一个固件,别急着反汇编。我个人的习惯是,先把它拆开看看——就像拆快递一样,看看里面到底装了啥。固件结构说白了就是三件套:Bootloader、内核、文件系统,再加上一个容易被忽略的配置分区。今天咱们就把这四块掰开揉碎了讲。

3.1 Bootloader:固件的“第一口奶”

Bootloader 是硬件上电后第一个跑起来的程序。它的任务很简单:初始化硬件,然后跳转到内核入口。嗯,这里要注意,不同芯片的 Bootloader 差别很大。

常见的 Bootloader 有这些:

  • U-Boot:Linux 世界的标配,功能强大,支持网络启动、Flash 读写
  • Barebox:U-Boot 的现代替代品,代码更干净
  • RedBoot:eCos 生态的,现在用得少了
  • CFE:Broadcom 芯片专用,博通路由器里常见

我在项目中遇到过一台老式路由器,死活刷不进新固件。后来一查,是 Bootloader 里的 Flash 分区表写死了,内核大小超限就直接覆盖了配置分区。那次之后,我养成了习惯——先 dump 出 Bootloader 的配置参数看看。

实战技巧:用 binwalk 提取固件后,先看前 256KB。如果看到 "U-Boot" 字符串,基本就是 U-Boot 了。用 strings 命令扫一下,能发现很多配置信息。

3.2 内核:固件的大脑

内核是 Linux 的核心,负责管理硬件资源、调度进程、提供系统调用。在固件里,内核通常被压缩成 zImage 或 uImage 格式。

内核镜像的典型结构:

+------------------+
|  解压缩代码       |  (自解压头)
+------------------+
|  压缩后的内核     |  (gzip/lzma/lzo)
+------------------+
|  DTB (设备树)     |  (可选,描述硬件)
+------------------+

你想想看,为什么固件里的内核这么小?因为嵌入式设备资源有限,内核都是裁剪过的。我见过最夸张的一个路由器固件,内核只有 1.2MB——去掉了所有不用的驱动和模块。

识别内核版本有个小窍门:用 binwalk -e 解压后,在解压目录里搜 Linux version 字符串。这个字符串会告诉你内核版本、编译时间、编译器版本。这些信息对找漏洞很有用——比如某个版本的内核有已知的 CVE。

避坑指南:我曾经在分析一个摄像头固件时,binwalk 没识别出内核。后来手动看了文件头,发现是 LZMA 压缩的,而且偏移量不对。所以别完全依赖工具,有时候得自己动手算偏移。

3.3 文件系统:固件的“家当”

文件系统里装着所有用户态程序、配置文件、Web 界面、库文件。嵌入式设备常用的文件系统有三种,各有各的脾气。

3.4 SquashFS:只读的压缩文件系统

SquashFS 是嵌入式设备最常用的文件系统。它是只读的,数据压缩存储,解压后挂载。优点是体积小、读取快。

SquashFS 的版本和压缩方式:

版本 压缩算法 最大文件大小 常见场景
v3.0 gzip 2GB 老旧路由器
v4.0 gzip/lzma/lzo/xz 16TB 现代设备

提取 SquashFS 很简单:unsquashfs firmware.squashfs。但要注意,有些厂商会魔改 SquashFS 的魔数(magic number),导致 unsquashfs 认不出来。我遇到过一家厂商把魔数从 hsqs 改成了 hsqS,折腾了我半天。

3.5 JFFS2:针对 NAND Flash 的日志型文件系统

JFFS2 是专门为 NAND Flash 设计的。它支持磨损均衡、掉电保护。但缺点也很明显——挂载慢,内存占用高。

JFFS2 的典型应用场景:

  • 需要频繁写入的配置分区
  • 日志记录分区
  • 用户数据分区

提取 JFFS2 需要点技巧:

# 先找到 JFFS2 镜像的偏移
binwalk firmware.bin | grep JFFS2

# 然后用 dd 切出来
dd if=firmware.bin of=jffs2.img bs=1 skip=0x123456

# 挂载到本地
sudo mount -t jffs2 jffs2.img /mnt/jffs2
核心要点:JFFS2 的挂载需要内核支持。如果你在 Ubuntu 上挂载失败,先检查 modprobe jffs2 是否成功。我踩过这个坑——折腾了半天才发现内核模块没加载。

3.6 YAFFS2:专为 NAND Flash 优化的文件系统

YAFFS2 比 JFFS2 更轻量,挂载速度更快。它没有压缩功能,但读写性能更好。很多 Android 设备的 /data 分区就用 YAFFS2。

YAFFS2 的特点:

  • 没有压缩,占用空间更大
  • 挂载速度极快(毫秒级)
  • 对 NAND Flash 的坏块处理更激进

提取 YAFFS2 可以用 unyaffs2 工具。但要注意,YAFFS2 的镜像通常包含 OOB(Out-of-Band)数据,提取时得带上 -o 参数。

3.7 配置分区:固件的“小本本”

配置分区是很多人容易忽略的部分。它通常是一个独立的小分区,用来存 MAC 地址、WiFi 校准数据、设备序列号等。

配置分区的常见格式:

  • MTD 分区:直接映射到 Flash 的某个区域
  • UBI 卷:在 UBI 层上创建的卷
  • Raw 数据:没有任何文件系统,直接读写

我曾经分析过一个智能家居网关,它的配置分区里存着 WiFi 密码——明文!厂商觉得没人会去读 Flash,所以没加密。嗯,这个漏洞我报给了厂商,后来他们加了 AES 加密。

分析技巧:hexdump 查看配置分区,搜索 "MAC"、"password"、"ssid" 等关键词。很多厂商的配置格式是固定的,找到规律后就能批量提取。

3.8 知识体系总览

下面这张图总结了固件结构的核心逻辑,我建议你保存下来,分析固件时对照着看:

固件结构剖析:核心知识体系 固件镜像 (Firmware Image) Bootloader U-Boot / Barebox / RedBoot 硬件初始化 → 加载内核 常见偏移:0x0 ~ 0x40000 内核 (Kernel) zImage / uImage / DTB 压缩格式:gzip / lzma / lzo 识别:Linux version 字符串 文件系统 (Filesystem) SquashFS (只读, 压缩) JFFS2 (日志型, NAND) YAFFS2 (轻量, 快速) 提取工具:unsquashfs / mount / unyaffs2 配置分区 (Config) MAC地址 / WiFi校准数据 序列号 / 密码 / 密钥 格式:MTD / UBI / Raw 搜索关键词:MAC, password, ssid 分析顺序:Bootloader → 内核 → 文件系统 → 配置分区

这张图把固件拆成了四个模块。你分析的时候,按这个顺序来就行。先看 Bootloader 有没有漏洞,再看内核版本有没有已知 CVE,然后解压文件系统找敏感信息,最后别忘了检查配置分区——那里经常藏着惊喜。

总结一下:固件结构其实不复杂。Bootloader 是入口,内核是核心,文件系统是内容,配置分区是细节。把这四块搞明白,大部分固件你都能分析个七七八八。下次拿到一个陌生固件,先按这个框架拆一遍,心里就有数了。

公众号:蓝海资料掘金营,微信deep3321