3. 文件系统解析:Squashfs、Cramfs、JFFS2 文件系统解析、文件系统挂载与只读问题处理
拿到固件之后,第一件事是什么?
我个人习惯,不是急着去翻代码,而是先看看它的文件系统是什么类型。你想想看,固件里藏着路由器的配置、Web 界面、二进制程序,甚至后门。如果连文件系统都挂不上,后面全是白搭。
今天我们就来聊聊固件里最常见的三种文件系统:Squashfs、Cramfs 和 JFFS2。我会结合我踩过的坑,告诉你它们怎么解析、怎么挂载,以及遇到只读问题该怎么处理。
3.1 为什么固件爱用这些文件系统?
说白了,嵌入式设备资源有限。Flash 空间就那么几兆到几十兆,RAM 更是抠着用。所以文件系统必须满足两个条件:
- 压缩率高:能塞更多东西进去
- 只读或日志型:防止意外断电搞坏系统
Squashfs 和 Cramfs 是只读压缩文件系统,适合存放固件本体。JFFS2 是日志型文件系统,适合存放配置、日志等需要频繁写入的数据。
核心思路:拿到固件后,先用 binwalk 或 file 命令识别文件系统类型,再选择对应的工具进行解析。
3.2 Squashfs 解析:最常用的固件文件系统
Squashfs 是目前思科、TP-Link、小米等路由器固件最常用的文件系统。它支持高压缩比,而且解压速度快。
3.2.1 识别 Squashfs
用 binwalk 扫描固件,你会看到类似这样的输出:
$ binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 uImage header, ...
...
1048576 0x100000 Squashfs filesystem, little endian, version 4.0, ...
看到 "Squashfs filesystem" 字样,就说明找到了。注意看版本号,Squashfs 4.0 和 3.0 的压缩算法不同,解压工具也不一样。
3.2.2 解压 Squashfs
我推荐用 unsquashfs 工具,它是 squashfs-tools 包里的。用法很简单:
# 先提取出 Squashfs 分区
dd if=firmware.bin of=squashfs_part.bin bs=1 skip=1048576
# 解压
unsquashfs -d output_dir squashfs_part.bin
如果遇到 "Unknown compression" 错误,别慌。这通常是因为固件用了 LZMA 或 XZ 压缩,而你的工具版本太老。升级 squashfs-tools 到最新版就行。
我的经验:有一次我解压一个思科交换机固件,unsquashfs 报错说 "unsupported compression type 3"。查了半天,发现是用了 LZMA 压缩,而系统自带的 squashfs-tools 不支持。后来我编译了最新版,问题解决。所以,工具版本很重要。
3.2.3 重新打包 Squashfs
分析完文件系统,你可能想修改某些文件(比如加个后门),然后重新打包。用 mksquashfs 命令:
mksquashfs modified_dir new_squashfs.bin -comp xz -b 131072
注意,压缩算法和块大小要和原固件一致,否则设备可能不认。怎么知道原固件的参数?用 unsquashfs -s squashfs_part.bin 查看。
3.3 Cramfs 解析:老牌只读文件系统
Cramfs 比 Squashfs 更老,现在用得少了,但在一些老旧思科设备上还能见到。它的特点是简单、轻量,但压缩率不如 Squashfs。
3.3.1 识别与解压
Cramfs 的魔数是 0x28CD3D45(小端序)。binwalk 也能识别:
$ binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
...
2097152 0x200000 Cramfs filesystem, little endian, size: 4194304
解压用 cramfsck 或 uncramfs:
# 方法一:cramfsck(推荐)
cramfsck -x output_dir cramfs_part.bin
# 方法二:uncramfs(老工具)
uncramfs cramfs_part.bin output_dir
注意:Cramfs 不支持大于 256MB 的文件系统,而且单个文件不能超过 16MB。如果你解压时遇到 "File too large" 错误,说明固件可能用了 Cramfs 的变种,或者根本不是 Cramfs。
3.3.2 重新打包 Cramfs
用 mkcramfs 命令:
mkcramfs modified_dir new_cramfs.bin
嗯,这里要注意:mkcramfs 默认会使用 zlib 压缩,但有些老设备只支持未压缩的 Cramfs。你可以加 -n 参数禁用压缩。
3.4 JFFS2 解析:日志型文件系统
JFFS2 是专门为 Flash 设计的日志型文件系统。它支持磨损均衡和掉电保护,通常用于存放配置、日志等动态数据。
3.4.1 识别 JFFS2
JFFS2 的魔数是 0x1985(小端序)。binwalk 输出示例:
$ binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
...
3145728 0x300000 JFFS2 filesystem, little endian, size: 2097152
3.4.2 挂载 JFFS2
JFFS2 不能直接用 unsquashfs 解压,需要挂载到 Linux 上。但有个前提:你的内核必须支持 JFFS2 和 MTD 模拟。
# 加载内核模块
modprobe mtdblock
modprobe jffs2
# 创建 MTD 设备
modprobe mtdram total_size=2097152 erase_size=65536
dd if=jffs2_part.bin of=/dev/mtdblock0
# 挂载
mount -t jffs2 /dev/mtdblock0 /mnt/jffs2
关键点:total_size 和 erase_size 必须和原固件一致。怎么知道?用 binwalk -Me 或 jefferson 工具分析。
3.4.3 使用 jefferson 工具
如果你觉得挂载太麻烦,可以用 jefferson 这个 Python 工具,它可以直接解压 JFFS2 镜像:
pip install jefferson
jefferson -d output_dir jffs2_part.bin
jefferson 会自动处理擦除块大小、压缩等问题。我在项目中用过很多次,基本没出过问题。
3.5 只读问题处理:挂载后无法写入
这是新手最容易卡住的地方。你辛辛苦苦挂载了文件系统,想修改某个文件,结果发现是只读的。
3.5.1 为什么是只读?
原因很简单:Squashfs 和 Cramfs 本身就是只读文件系统。JFFS2 虽然是可写的,但如果挂载时没指定读写权限,也会变成只读。
3.5.2 解决方案
针对不同情况,我有三种处理方式:
| 文件系统类型 | 问题 | 解决方案 |
|---|---|---|
| Squashfs | 只读,无法直接修改 | 解压 -> 修改文件 -> 重新打包 |
| Cramfs | 只读,无法直接修改 | 解压 -> 修改文件 -> 重新打包 |
| JFFS2 | 挂载后只读 | 检查挂载参数,加 -o rw |
对于 JFFS2,我曾经犯过一个低级错误:挂载时忘了加 -o rw,结果折腾了半天。正确的挂载命令是:
mount -t jffs2 -o rw /dev/mtdblock0 /mnt/jffs2
避坑指南:我曾经在分析一个思科路由器固件时,发现 JFFS2 分区挂载后是只读的。查了半天,发现是内核没加载 mtdblock 模块。加载之后,问题解决。所以,先检查内核模块。
3.6 知识体系结构图
下面这张图总结了本章的核心逻辑,帮你快速理清思路:
3.7 本章小结
文件系统解析是固件逆向的第一步,也是最关键的一步。我见过太多人卡在挂载阶段,然后放弃。其实说白了,就是三个步骤:
- 识别:用 binwalk 找出文件系统类型和偏移
- 提取:用 dd 或 binwalk -e 提取分区
- 解压/挂载:根据类型选择对应工具
遇到只读问题,别慌。Squashfs 和 Cramfs 本来就是只读的,解压修改后重新打包就行。JFFS2 的话,检查一下挂载参数和内核模块。
下一章,我们会深入探讨如何从文件系统中提取关键信息,比如配置文件、Web 源码和二进制程序。到时候你会发现,文件系统解析只是开胃菜,真正的挑战还在后面。