4、固件解包与文件系统分析:squashfs、cramfs、jffs2 文件系统处理

拿到一个固件 bin 文件,第一件事是什么?

不是直接上逆向工具,也不是扔进模拟器跑。你得先把它拆开,看看里面到底装了些什么。说白了,固件解包是后续所有分析工作的基础。如果这步卡住了,后面全是白搭。

我刚开始做固件分析时,就吃过这个亏。拿到一个路由器固件,直接扔进 binwalk 就跑,结果解出来一堆乱码。后来才发现,是文件系统类型判断错了。嗯,今天我们就来把这几个常见的嵌入式文件系统彻底讲清楚。

4.1 固件解包三板斧:binwalk、dd、jefferson

解包工具不少,但真正好用的就那么几个。我个人习惯按这个顺序来:

  1. binwalk 扫描 —— 先看看固件里有什么
  2. binwalk -e 自动提取 —— 能解就解
  3. 手动 dd 切割 —— 自动不行就手动
  4. 专用工具处理 —— 针对特定文件系统

我的经验:binwalk 不是万能的。遇到加密固件或非标准偏移,它经常翻车。这时候别慌,用 dd 手动切,配合 strings 和 hexdump 定位文件系统头,反而更靠谱。

先看一个典型的 binwalk 扫描结果:

$ binwalk firmware.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             TRX firmware header, little endian, image size: 8388608
196           0xC4            LZMA compressed data, properties: 0x5D
1310720       0x140000        Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 7077888 bytes

看到 Squashfs filesystem 了吗?这就是我们要找的文件系统起始位置。偏移量是 0x140000,大小约 7MB。

自动提取很简单:

$ binwalk -e firmware.bin

它会生成一个 _firmware.bin.extracted 目录,里面就是解出来的东西。但有时候 binwalk 会报错,或者解出来的文件不完整。这时候就得手动来了。

4.2 Squashfs:最常用的只读文件系统

Squashfs 是嵌入式设备里最常见的文件系统,没有之一。它只读、压缩率高、支持多种压缩算法。我见过的路由器、摄像头、IoT 设备,十有八九都用它。

Squashfs 的特点:

  • 只读文件系统,启动时直接解压到内存
  • 支持 gzip、lzma、lzo、xz、zstd 等多种压缩
  • 版本 3.0 和 4.0 差异很大,工具不通用
  • 文件系统头有魔数 hsqs(小端)或 shsq(大端)

注意:Squashfs 4.0 和 3.0 的 unsquashfs 工具不兼容。我曾经用 4.0 的工具去解 3.0 的固件,结果直接 segfault。一定要先确认版本。

查看 Squashfs 版本和信息的命令:

$ unsquashfs -s squashfs.bin
Found a valid SQUASHFS 4:0 superblock on squashfs.bin.
Creation or last modification time: Tue Jan 10 10:30:45 2023
Compression: lzma
Block size: 131072
Filesystem size: 7077888 bytes
Inodes count: 1234

解包命令:

$ unsquashfs -d ./output_dir squashfs.bin

如果遇到 unsquashfs: 无法识别的文件系统,大概率是版本不对。试试用 sasquatch(一个支持更多变种的 unsquashfs 分支):

$ sasquatch -d ./output_dir squashfs.bin

4.3 Cramfs:老牌嵌入式文件系统

Cramfs 比 Squashfs 更老,现在用得少了,但一些老设备(尤其是 2010 年以前的)还在用。它的压缩率不如 Squashfs,但解压速度快,适合资源极度受限的设备。

Cramfs 的特点:

  • 只读,最大支持 256MB 文件系统
  • 单个文件最大 16MB
  • 魔数 CramFS 或 0x28CD3D45
  • 不支持压缩(或者说压缩率很低)

解包 Cramfs 用 cramfsck 或 uncramfs:

$ cramfsck -x ./output_dir cramfs.bin

或者:

$ uncramfs cramfs.bin ./output_dir

避坑指南:我曾经遇到一个 Cramfs 固件,用 cramfsck 解出来全是空文件。后来发现是文件系统被修改过,魔数被改了。用 hexdump 看了下,发现前 4 个字节被改成了 0xDEADBEEF。改回来就正常了。所以,遇到解不开的,先看看魔数对不对。

4.4 JFFS2:NAND Flash 上的日志型文件系统

JFFS2 和上面两个不一样。它是可写的,专门为 NAND Flash 设计。你会在一些高端路由器、工业设备上看到它。

JFFS2 的特点:

  • 可读写,支持磨损均衡
  • 压缩(默认 zlib,可选 lzo)
  • 魔数 0x1985(小端)
  • 解包需要指定擦除块大小(erase block size)

解包 JFFS2 用 jefferson 工具:

$ jefferson -d ./output_dir jffs2.bin

如果 jefferson 报错,通常是因为擦除块大小不对。试试手动指定:

$ jefferson -d ./output_dir -e 0x20000 jffs2.bin

常见的擦除块大小:64KB(0x10000)、128KB(0x20000)、256KB(0x40000)。

注意:JFFS2 解包时,如果擦除块大小不对,解出来的文件可能乱码或缺失。我建议先用 strings 看看固件里有没有内核日志,里面经常会有擦除块大小的信息。

4.5 三种文件系统对比

为了方便你快速选择工具和方法,我整理了一个对比表:

特性 Squashfs Cramfs JFFS2
读写属性 只读 只读 可读写
压缩支持 gzip/lzma/lzo/xz/zstd 无(或极低) zlib/lzo
最大文件系统 无限制(4.0) 256MB 取决于 Flash 大小
魔数 hsqs / shsq CramFS / 0x28CD3D45 0x1985
解包工具 unsquashfs / sasquatch cramfsck / uncramfs jefferson
常见设备 路由器、摄像头、IoT 老设备、工控设备 高端路由器、NAS

4.6 文件系统分析流程

下面这张图是我自己总结的分析流程,你可以照着走:

固件文件系统分析流程 获取固件 bin 文件 binwalk 扫描 + 自动提取 判断文件系统类型 Squashfs Cramfs JFFS2 unsquashfs / sasquatch cramfsck / uncramfs jefferson 分析文件系统内容

4.7 实战:一个完整的解包案例

假设你拿到一个路由器固件 router_fw.bin,我们来走一遍完整流程。

第一步:扫描

$ binwalk router_fw.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             TRX firmware header, little endian, image size: 16777216
256           0x100           uImage header, header size: 64 bytes, load address: 0x80000000
320           0x140           LZMA compressed data, properties: 0x5D
4194304       0x400000        Squashfs filesystem, little endian, version 4.0, compression:lzma

第二步:自动提取

$ binwalk -e router_fw.bin
$ ls _router_fw.bin.extracted/
0.trx  100.uimage  140.lzma  400000.squashfs

第三步:解包 Squashfs

$ unsquashfs -d ./rootfs 400000.squashfs
$ ls rootfs/
bin  dev  etc  lib  mnt  proc  sbin  tmp  usr  var

看到熟悉的 Linux 目录结构了。接下来就可以分析文件了——检查启动脚本、找硬编码密码、看有没有后门,这些都是后续章节的内容。

核心要点:解包只是第一步,但也是最关键的一步。工具用对、版本匹配、偏移准确,这三个条件缺一不可。如果解出来的文件系统不完整,或者文件内容乱码,先检查这三个方面。

4.8 常见问题与解决方案

最后,我把这些年遇到过的坑整理一下:

  • binwalk 报错 "unknown filesystem" —— 试试用 -M 参数递归扫描,或者手动用 hexdump 找魔数
  • unsquashfs segfault —— 版本不匹配,换 sasquatch 试试
  • cramfsck 解出空文件 —— 检查魔数是否被修改,用 hexdump 对比标准魔数
  • jefferson 报错 "invalid erase block size" —— 用 -e 参数手动指定,从 0x10000 开始试
  • 解出来的文件乱码 —— 可能是压缩算法不对,或者文件系统被加密了