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在固件中的位置:

SquashFS 在思科固件中的位置与结构 固件镜像(Firmware Image) Bootloader | 内核 | SquashFS根文件系统 | JFFS2配置分区 SquashFS 内部结构 超级块 inode表 数据块 碎片块 按需解压:访问文件 → 定位inode → 读取数据块 → 解压到内存 常见压缩算法:gzip(默认)、lzma(高压缩比)、lzo(快速解压) 思科常用:gzip + 块大小 65536 字节

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