3. 文件系统深度解析:SquashFS与JFFS2
好,咱们进入第三章。这一章我打算聊聊思科固件里最核心的两个文件系统——SquashFS和JFFS2。说实话,搞嵌入式Linux逆向,不懂这两个东西,你连固件都解不开。
我最早接触思科设备时,第一反应就是:这玩意儿怎么启动的?文件系统怎么组织的?后来拆了几个固件,才慢慢摸清门道。今天我把这些经验整理出来,希望能帮你少走弯路。
3.1 SquashFS:只读文件系统的硬核设计
SquashFS,说白了就是一个高度压缩的只读文件系统。思科为什么用它?因为固件需要尽可能小,而且运行时不需要修改。你想想看,一个路由器固件动辄几十MB,不用压缩根本塞不进Flash里。
它的核心特点就三个:
- 只读:写入后就不能改了,适合存放内核、驱动、基础库
- 高压缩比:支持gzip、lzma、lzo等多种算法,我见过压缩比能到3:1甚至4:1
- 按需解压:只有访问到某个文件时,才解压对应的数据块,节省内存
我记得有一次分析一个Cisco 2900系列的路由器固件,用binwalk拆开后发现根文件系统就是SquashFS。当时我直接用unsquashfs解压,结果报错——版本不匹配。嗯,这里要注意,SquashFS有多个版本,思科常用的是v4.0。
SquashFS的磁盘布局:
- 超级块(Superblock):文件系统元数据,包括魔数、版本、块大小等
- inode表:每个文件/目录的inode信息
- 数据块:实际的文件内容,按块压缩存储
- 碎片块(Fragment blocks):小文件合并存储,减少空间浪费
- 导出表(Export table):NFS导出时使用,思科固件里基本用不到
我画了一张结构图,帮你理解SquashFS在固件中的位置:
3.2 JFFS2:日志型文件系统的特点
JFFS2(Journalling Flash File System version 2)是思科设备里另一个重要的文件系统。它跟SquashFS正好互补——SquashFS只读,JFFS2可写。思科用它来存放配置、日志、临时文件等需要修改的数据。
JFFS2的设计初衷是解决Flash存储的两个痛点:
- 磨损均衡:Flash有擦写次数限制,JFFS2会尽量均匀分布写入
- 掉电安全:日志型结构,即使突然断电也不会丢失数据
它的工作原理很有意思。所有数据变更都先写到一个日志区,然后定期清理。我打个比方:就像你写日记,先记在草稿本上,等有空了再誊写到正式本子里。JFFS2的垃圾回收机制就是干这个的。
我个人经验:分析思科固件时,JFFS2分区通常存放的是/etc、/var、/tmp这些目录。如果你在固件里看到jffs2.img这样的文件,别犹豫,直接解出来看看。我曾经在一个JFFS2分区里找到了设备的默认密码和SNMP社区字符串。
JFFS2的几个关键特性:
| 特性 | 说明 | 实际影响 |
|---|---|---|
| 日志结构 | 所有写入先追加到日志区 | 掉电不丢数据,但占用额外空间 |
| 压缩支持 | 支持zlib、lzo等压缩算法 | 节省Flash空间,但增加CPU开销 |
| 垃圾回收 | 后台线程清理无效节点 | 长时间运行后性能可能下降 |
| 挂载选项 | 支持noatime、nodiratime等 | 减少不必要的写入,延长Flash寿命 |
避坑指南:我曾经在分析一个老款思科交换机固件时,发现JFFS2分区挂载失败。后来查了半天,原来是内核里JFFS2模块没编译进去。所以,如果你解压固件后看不到JFFS2内容,先检查内核配置,别急着怀疑工具。
3.3 unsquashfs与mkfs.jffs2工具使用
好,理论说完了,咱们来点实操。这两个工具是分析思科固件时最常用的。
3.4.1 unsquashfs:解压SquashFS镜像
用法很简单:
# 基本用法
unsquashfs firmware.squashfs
# 指定解压目录
unsquashfs -d ./output firmware.squashfs
# 查看文件系统信息(不解压)
unsquashfs -s firmware.squashfs
# 只解压特定文件
unsquashfs -f /etc/passwd firmware.squashfs
我常用的几个参数:
-s:显示超级块信息,包括压缩算法、块大小、inode数量-d:指定输出目录,避免污染当前目录-f:提取单个文件,适合快速查看关键配置-ll:列出文件系统内容,类似ls -l
实战技巧:如果你遇到unsquashfs: unknown compression错误,别慌。这通常是因为固件用了非标准压缩算法。试试用-comp xz或-comp lzma指定算法。我遇到过几次,最后发现是思科魔改过的SquashFS,需要自己编译带补丁的unsquashfs。
3.4.2 mkfs.jffs2:制作JFFS2镜像
这个工具主要用于创建JFFS2文件系统镜像。虽然逆向时用得少,但如果你想修改固件后重新打包,就离不开它。
# 基本用法
mkfs.jffs2 -r ./rootfs -o rootfs.jffs2
# 指定擦除块大小(重要!)
mkfs.jffs2 -r ./rootfs -o rootfs.jffs2 -e 0x20000
# 启用压缩
mkfs.jffs2 -r ./rootfs -o rootfs.jffs2 -c -l
# 添加pad对齐
mkfs.jffs2 -r ./rootfs -o rootfs.jffs2 -p
参数说明:
| 参数 | 作用 | 注意事项 |
|---|---|---|
-r |
指定根目录 | 目录结构会被完整保留 |
-o |
输出文件路径 | 通常以.jffs2结尾 |
-e |
擦除块大小 | 必须与Flash芯片匹配,思科常用0x20000(128KB) |
-c |
启用压缩 | 默认使用zlib,可加-l启用lzo |
-p |
填充对齐 | 确保镜像大小是擦除块的整数倍 |
我的习惯:制作JFFS2镜像时,我总会先查一下目标设备的Flash芯片手册。擦除块大小搞错了,镜像写进去也启动不了。另外,思科有些设备对JFFS2的压缩算法有特殊要求,我建议先用-c开启压缩,如果不行再试-l。
3.4 实战:从思科固件中提取文件系统
最后,我分享一个完整的操作流程。假设你手头有一个思科路由器的固件文件c2900-universalk9-mz.SPA.157-3.M8.bin:
# 第一步:用binwalk分析固件结构
binwalk c2900-universalk9-mz.SPA.157-3.M8.bin
# 第二步:提取SquashFS分区
binwalk -e c2900-universalk9-mz.SPA.157-3.M8.bin
# 第三步:查看提取出的SquashFS文件
ls -la _c2900-universalk9-mz.SPA.157-3.M8.bin.extracted/
# 通常会有 rootfs.squashfs 或类似文件
# 第四步:解压SquashFS
unsquashfs -d ./rootfs rootfs.squashfs
# 第五步:查看JFFS2分区(如果有)
# 通常JFFS2分区是单独的文件,或者嵌在固件末尾
dd if=c2900-universalk9-mz.SPA.157-3.M8.bin of=jffs2.img bs=1 skip=0x123456
# 然后用mount挂载
mount -t jffs2 jffs2.img /mnt/jffs2 -o loop
嗯,这套流程我用了不下百次。刚开始时经常卡在第二步,因为binwalk有时候识别不出SquashFS的偏移量。后来我学乖了,先用binwalk -Me递归提取,基本能搞定大部分情况。
好了,这一章的内容就到这里。SquashFS和JFFS2是思科固件的两大支柱,搞懂了它们,你就掌握了固件逆向的钥匙。下一章咱们聊聊内核启动参数和初始化脚本,那才是真正的好戏。
公众号:蓝海资料掘金营,微信deep3321