4. 固件文件系统分析:常见文件系统(如JFFS2、SquashFS、CramFS)的提取与挂载方法

固件逆向分析中,文件系统是承载操作系统内核、用户态程序、配置脚本与设备驱动的核心载体。当我们从固件镜像中识别出文件系统类型并成功提取其内容时,就相当于拿到了设备运行时的“完整磁盘”。然而,嵌入式设备使用的文件系统与PC端常见的ext4、NTFS差异极大,JFFS2、SquashFS、CramFS等专为Flash存储设计的格式在结构上有着完全不同的布局逻辑。本章将围绕这三种最常见的嵌入式文件系统,从原理识别、提取工具、挂载方法到实战排障,构建一套完整的分析流程。

固件文件系统分析决策链路 固件镜像 (.bin/.img) 阶段1:类型识别(binwalk / file / hexdump) SquashFS(只读压缩) JFFS2(NOR/NAND可写) CramFS(古老只读) unsquashfs / 7z / binwalk -e sasquatch(处理非标准块大小) jefferson / flashrom + nanddump 需要处理OOB/坏块/擦除块对齐 cramfsck / extract-cramfs binwalk 直接提取 + 手动修复 阶段3:挂载验证(mount -o loop, offset) 输出:可读写的文件树 / 提取的rootfs目录

上图展示了固件文件系统分析的三阶段决策链路:从固件镜像出发,先通过特征识别确定文件系统类型,再选择对应的提取工具,最后通过挂载验证提取结果的完整性。在实际分析中,这三个阶段往往需要反复迭代,因为固件可能包含多层嵌套的文件系统,或者存在非标准的魔数偏移。

4.1 核心原理:Flash存储与文件系统的设计约束

嵌入式文件系统的设计目标与PC文件系统截然不同。Flash芯片(NOR或NAND)具有三个关键物理特性:块擦除(写入前必须将块整体擦除为0xFF)、有限擦写寿命(通常为10万到100万次)、读写不对称(读快写慢)。因此,JFFS2、SquashFS、CramFS在设计时都围绕这些约束展开。

  • SquashFS:只读压缩文件系统,将文件数据按4KB-128KB的块进行gzip/lzma/xz压缩,元数据(inode、目录项)单独存储。其核心优势是压缩率高、随机读取性能好,适合存放只读的rootfs和应用程序。魔数特征为hsqs(小端)或sqsh(大端)。
  • JFFS2:可写的日志型文件系统,专为NOR/NAND Flash设计。数据以节点(node)形式顺序写入,通过垃圾回收机制管理擦除块。其魔数特征为0x1985(节点魔数),但整个镜像没有统一的文件头,识别难度较高。
  • CramFS:古老的只读压缩文件系统,将inode和数据直接压缩存储在镜像中,支持就地解压(无需先解压到RAM)。魔数特征为0x28cd3d45(小端),常见于早期Linux 2.4/2.6内核的设备。

关键认知:文件系统的“挂载”在逆向分析中分为两种含义。第一种是逻辑挂载——通过工具将镜像内容解包到宿主机目录;第二种是内核挂载——通过mount命令将镜像作为块设备挂载到Linux内核。对于SquashFS和CramFS,由于内核原生支持,可以直接挂载;对于JFFS2,则需要模拟MTD设备或使用用户态工具。

4.2 判断方法:从固件镜像中识别文件系统类型

识别文件系统类型是分析的第一步,也是最容易出错的一步。以下是标准化的识别流程:

  1. 使用binwalk进行初步扫描:binwalk firmware.bin会输出所有可识别的文件签名及其偏移地址。重点关注SquashFS、JFFS2、CramFS的标记。
  2. 验证魔数:使用hexdump -C -s 偏移量 -n 16 firmware.bin查看具体字节。SquashFS的hsqs在偏移0处;CramFS的魔数通常出现在块对齐位置;JFFS2没有全局魔数,但节点起始处有0x85 0x19(小端)。
  3. 检查文件系统参数:对于SquashFS,偏移4-8字节是inode数量,偏移8-12是修改时间,偏移12-16是块大小。这些参数能帮助我们判断是否为标准构建。
  4. 使用file命令辅助:file -k firmware.bin有时能直接识别出Linux squashfs filesystem或cramfs filesystem。
文件系统魔数(十六进制)ASCII特征典型偏移内核支持
SquashFS68 73 71 73hsqs通常为4KB对齐Linux 2.6.15+
CramFS45 3d cd 28E=Í(任意位置Linux 2.4+
JFFS285 19 00 00(节点头)无固定ASCII无固定偏移需MTD支持

风险提示:某些固件会对文件系统进行加密或混淆,binwalk可能无法直接识别。此时应检查固件头部是否有自定义引导头,或者尝试使用binwalk -E进行熵分析——高熵区域通常表示加密数据,低熵区域可能是压缩文件系统。

4.3 提取与挂载方法:分文件系统详解

4.3.1 SquashFS的提取与挂载

SquashFS是最常见的嵌入式只读文件系统,其提取方法最为成熟。标准工具为unsquashfs,但许多厂商会修改SquashFS的块大小或压缩算法,导致标准工具无法解析。此时需要使用sasquatch(一个支持非标准块大小的补丁版unsquashfs)。

# 标准提取
unsquashfs -d ./squashfs-root squashfs.img

# 使用sasquatch处理非标准块大小
sasquatch -d ./squashfs-root squashfs.img

# 直接挂载(需要root权限)
mkdir -p /mnt/squash
mount -t squashfs -o loop,offset=0x100000 squashfs.img /mnt/squash

挂载时需要注意offset参数——如果固件中SquashFS前面还有引导头或其他数据,必须指定正确的偏移量。可以通过binwalk输出的偏移地址来确定。

4.3.2 JFFS2的提取与挂载

JFFS2的提取相对复杂,因为它没有统一的镜像头,且数据节点分散在整个Flash区域。常用的提取工具是jefferson,它能够解析JFFS2镜像并还原文件树。对于NAND Flash的JFFS2,还需要处理OOB(Out-of-Band)数据。

# 使用jefferson提取
jefferson jffs2.img -d ./jffs2-root

# 如果镜像包含OOB数据,需要先分离
nanddump --oob raw --skip-bad nand_device.bin -f jffs2_clean.bin

# 挂载JFFS2(需要模拟MTD设备)
modprobe mtdram
modprobe mtdblock
dd if=jffs2.img of=/dev/mtd0 bs=1M
mount -t jffs2 /dev/mtdblock0 /mnt/jffs2

经验之谈:JFFS2镜像经常包含“脏”节点(已删除但未回收的文件),jefferson提取时可能会报错。此时可以添加--ignore-dirty参数跳过这些节点,但提取结果可能不完整。建议同时使用strings命令对原始镜像进行辅助分析,以发现被删除但仍有残留数据的文件。

4.3.3 CramFS的提取与挂载

CramFS虽然古老,但在一些老旧设备(如早期IP摄像头、路由器)中仍然存在。提取工具为cramfsck(用于检查)和extract-cramfs(用于提取)。

# 检查CramFS镜像
cramfsck -x ./cramfs-root cramfs.img

# 直接挂载
mkdir -p /mnt/cram
mount -t cramfs -o loop cramfs.img /mnt/cram

CramFS的挂载相对简单,但需要注意其最大文件大小限制(通常为16MB),且不支持大于256MB的镜像。如果遇到无法挂载的情况,可以尝试使用binwalk -e直接提取,binwalk内置了CramFS的提取逻辑。

4.4 实战案例:某品牌路由器固件的完整分析

本案例展示一个典型的固件分析过程,涉及SquashFS和JFFS2的混合使用场景。

背景与约束:某品牌企业级路由器固件(型号R-2000),固件大小32MB,需要提取其Web管理界面和VPN配置脚本。该设备使用NOR Flash,分区布局为:Bootloader(256KB)+ Kernel(2MB)+ RootFS(SquashFS,8MB)+ Config(JFFS2,1MB)+ 日志(JFFS2,1MB)。

初始现象:使用binwalk扫描固件,在偏移0x100000处发现SquashFS标记,但unsquashfs报错“Unknown compression type”。

分析过程:

  1. 使用hexdump -C -s 0x100000 -n 64查看SquashFS头,发现压缩类型字段为0x05(LZMA),而标准unsquashfs仅支持gzip(0x01)和xz(0x02)。
  2. 安装sasquatch,成功提取RootFS,得到完整的文件树。
  3. 在固件偏移0x900000处发现JFFS2节点特征(0x85 0x19),使用dd截取1MB镜像,通过jefferson提取配置分区。
  4. 对比两个文件系统提取结果,发现JFFS2分区中保存了设备出厂配置和SSH密钥。

操作步骤与结果:

# 提取SquashFS
dd if=firmware.bin of=rootfs.squash bs=1M skip=1 count=8
sasquatch -d ./rootfs rootfs.squash

# 提取JFFS2配置分区
dd if=firmware.bin of=config.jffs2 bs=1M skip=9 count=1
jefferson config.jffs2 -d ./config

# 验证提取结果
find ./rootfs -type f | wc -l   # 输出:1284个文件
cat ./config/etc/passwd         # 发现root密码哈希

风险与代价:JFFS2提取过程中遇到3个坏块,jefferson自动跳过,导致部分日志文件缺失。由于配置分区不是关键目标,此缺失可接受。如果必须完整提取,需要使用nanddump配合硬件编程器读取原始Flash芯片。

复盘经验:混合文件系统固件的分析应遵循“先识别分区表,再逐个提取”的策略。建议使用binwalk -A查看固件的分区布局,或者参考厂商的DTS(Device Tree Source)文件来确认分区偏移。

4.5 常见误区与总结复盘

在固件文件系统分析中,初学者常犯以下错误:

  • 盲目使用binwalk -e:binwalk的自动提取模式会尝试所有已知签名,可能产生大量误报文件,且对于嵌套文件系统(如SquashFS内嵌JFFS2)处理不佳。建议先手动识别,再针对性提取。
  • 忽略字节序:SquashFS的魔数在ARM小端设备上为hsqs,但在MIPS大端设备上为sqsh。使用file命令时需注意架构差异。
  • 挂载时忘记offset:直接mount -o loop整个固件镜像会失败,因为内核无法在偏移处找到文件系统。必须使用offset参数指定正确的起始位置。
  • JFFS2的OOB处理:NAND Flash的JFFS2镜像通常包含OOB数据,直接提取会报错。需要先使用nanddump分离OOB,或者使用支持OOB的专用工具。

总结:固件文件系统分析的核心在于“识别-提取-验证”三步骤的闭环。识别阶段要善用binwalk和hexdump交叉验证;提取阶段要根据文件系统特性选择工具(SquashFS用sasquatch、JFFS2用jefferson、CramFS用cramfsck);验证阶段则通过挂载或文件树对比确保提取完整性。掌握这些方法后,面对任何嵌入式固件都能快速定位关键文件,为后续的漏洞挖掘和逻辑分析奠定基础。