4. 固件文件系统分析:SquashFS、CramFS、JFFS2 解析与挂载
拿到一个固件,解包之后看到一堆二进制文件,下一步干什么?我个人习惯,先看文件系统。因为文件系统决定了你怎么挂载、怎么提取、怎么分析。说白了,它就是固件的“骨架”——目录结构、配置文件、可执行程序全藏在里面。
嵌入式固件里最常见的三种文件系统:SquashFS、CramFS 和 JFFS2。它们各有各的脾气,挂载方式也不一样。今天我就带你一个一个过一遍。
核心观点: 文件系统分析是固件逆向的“入口”。选错挂载方式,轻则提取失败,重则损坏固件镜像。我建议你先把下面这张图看明白,再动手操作。
4.1 SquashFS:只读文件系统的“扛把子”
SquashFS 是嵌入式 Linux 里最常见的只读文件系统。为什么?因为它压缩率高,而且支持随机读取。你想想看,一个 64MB 的 Flash 芯片,用 SquashFS 能塞进 200MB 的根文件系统——这对空间受限的设备来说太重要了。
我在项目中遇到过一台路由器,固件解包后看到一堆 .squashfs 后缀的文件。当时我直接用 mount 去挂,结果报错。后来才发现,SquashFS 有多个版本(v3、v4),挂载参数不一样。
4.1.1 识别 SquashFS 版本
先用 file 命令看一眼:
$ file firmware.bin
firmware.bin: Squashfs filesystem, little endian, version 4.0, ...
如果输出里没有版本号,可以用 binwalk 扫描:
$ binwalk -Me firmware.bin
嗯,这里要注意:binwalk -Me 会自动提取所有已知文件系统。但有时候它会漏掉偏移量不对的镜像。我个人习惯先用 binwalk -A 看特征码,再手动指定偏移。
4.1.2 挂载 SquashFS
挂载 SquashFS 需要内核支持。如果你的分析机是 Ubuntu/Debian,先装工具:
$ sudo apt install squashfs-tools
然后挂载:
$ sudo mount -t squashfs -o loop,ro firmware.squashfs /mnt/squashfs
如果挂载失败,八成是版本不匹配。试试用 unsquashfs 直接解包:
$ unsquashfs -d ./extracted firmware.squashfs
小技巧: 我曾经遇到一个 Squashfs v3 的镜像,Ubuntu 20.04 默认的 squashfs-tools 只支持 v4。解决办法是装旧版工具,或者用 sasquatch——一个专门解旧版 SquashFS 的补丁版工具。
4.2 CramFS:轻量级但“过时”的选择
CramFS 是更早的只读文件系统。它比 SquashFS 更简单,但压缩率低,而且最大只支持 256MB 的文件系统。现在新设备基本不用了,但老固件里经常碰到——尤其是那些基于 Linux 2.4/2.6 内核的嵌入式设备。
我记得有一次分析一个工业控制器的固件,解包后看到一堆 .cramfs 文件。当时我心想:“这年头还有人用 CramFS?” 但没办法,该分析还得分析。
4.2.1 挂载 CramFS
CramFS 的挂载比 SquashFS 简单,因为它不需要额外工具——内核自带支持。直接 mount:
$ sudo mount -t cramfs -o loop,ro firmware.cramfs /mnt/cramfs
如果 mount 失败,检查一下内核是否启用了 CramFS 支持:
$ cat /proc/filesystems | grep cramfs
没输出?那就得重新编译内核,或者换一台机器。我个人建议用 cramfsck 工具先验证镜像完整性:
$ cramfsck firmware.cramfs
警告: CramFS 不支持写操作,也不支持硬链接。如果你在解包后修改了文件,想重新打包成 CramFS——别想了。老老实实转成 SquashFS 或者用其他方式处理。
4.3 JFFS2:NAND Flash 的“亲儿子”
JFFS2 是专门为 NAND Flash 设计的日志型文件系统。它支持写操作、磨损均衡、坏块管理。你想想看,路由器、摄像头这些设备,运行时需要写日志、保存配置,用只读文件系统肯定不行。JFFS2 就是干这个的。
但 JFFS2 有个问题:它没有标准的“镜像文件”格式。固件里的 JFFS2 分区通常是直接从 Flash 里 dump 出来的裸数据,包含 OOB(Out-of-Band)数据。这就导致挂载时经常遇到“找不到超级块”的错误。
4.3.1 挂载 JFFS2 镜像
先装工具:
$ sudo apt install mtd-utils
然后挂载(注意:需要模拟 MTD 设备):
$ sudo modprobe mtdblock
$ sudo modprobe mtdram total_size=65536 erase_size=128
$ sudo dd if=firmware.jffs2 of=/dev/mtdblock0
$ sudo mount -t jffs2 /dev/mtdblock0 /mnt/jffs2
如果镜像包含 OOB 数据,得先清理:
$ nanddump --noecc --omitoob -f clean.jffs2 firmware.jffs2
避坑指南: 我曾经花了一下午挂一个 JFFS2 镜像,一直报 CRC 错误。后来发现是镜像里混了 UBI 头部。用 ubinfo 和 ubiattach 挂 UBI 卷,而不是直接挂 JFFS2。记住:JFFS2 和 UBI/UBIFS 是两回事。
4.4 三种文件系统对比
下面这张表,我建议你收藏。做固件分析时拿出来对照,能省不少时间。
| 特性 | SquashFS | CramFS | JFFS2 |
|---|---|---|---|
| 读写属性 | 只读 | 只读 | 可读写 |
| 压缩算法 | gzip/lzma/lzo/zstd | zlib | zlib/rtime |
| 最大文件系统 | 16TB(v4) | 256MB | 取决于 Flash 大小 |
| 适用场景 | 根文件系统、只读分区 | 老旧嵌入式设备 | NAND Flash 运行时分区 |
| 挂载工具 | mount -t squashfs / unsquashfs | mount -t cramfs / cramfsck | mount -t jffs2 / jefferson |
| 常见问题 | 版本不匹配、偏移量错误 | 内核不支持、镜像损坏 | OOB 数据、UBI 头部干扰 |
4.5 实战:从固件中提取文件系统
光说不练假把式。我们拿一个实际固件走一遍流程。
4.5.1 步骤一:扫描固件
$ binwalk -Me router_firmware.bin
输出会显示所有文件系统的偏移量和类型。比如:
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 uImage header
64 0x40 LZMA compressed data
1310720 0x140000 Squashfs filesystem, little endian, version 4.0
4.5.2 步骤二:提取 SquashFS
$ dd if=router_firmware.bin of=rootfs.squashfs bs=1 skip=1310720
$ unsquashfs -d ./rootfs rootfs.squashfs
4.5.3 步骤三:分析文件系统
解包后,重点关注这些目录:
- /etc/ —— 配置文件、密码哈希、证书
- /bin/、/sbin/、/usr/bin/ —— 可执行程序
- /lib/ —— 动态链接库
- /www/、/web/ —— Web 管理界面(常有漏洞)
- /var/ —— 运行时数据(可能包含敏感信息)
经验之谈: 我每次拿到解包后的文件系统,第一件事就是 grep 搜索密码、密钥、token。命令很简单:grep -r "password" ./rootfs/etc/。你猜怎么着?十次里有八次能搜到硬编码密码。
4.6 常见问题与排错
挂载文件系统时,你可能会遇到下面这些问题。我把我的排错思路写出来,供你参考。
- 挂载时报“wrong fs type” —— 内核没编译对应文件系统支持。装工具或换内核。
- 挂载成功但目录为空 —— 偏移量不对。用
binwalk -A重新确认偏移。 - JFFS2 报 CRC 错误 —— 镜像包含 OOB 或 UBI 头部。用
nanddump清理。 - SquashFS 解包报“unsupported version” —— 版本太老。用
sasquatch或找旧版工具。 - CramFS 挂载后文件不全 —— 镜像可能被截断。检查固件大小是否完整。
重要提醒: 永远不要在原始固件镜像上直接操作。先备份,再分析。我曾经见过有人用 dd 写错了偏移量,直接把 Flash 芯片的 bootloader 覆盖了——设备直接变砖。