rea-reverse

从固件 .bin 里提取 SquashFS 根文件系统:unsquashfs、Unblob 与 Binwalk 实操

基于 REA 6.4.0更新于 约 7 分钟读完
本页目录

结论先行:大多数基于 Linux 的路由器固件,结构都是“内核 + SquashFS 根文件系统”。用 binwalk 找到 SquashFS 的魔数 hsqs(68 73 71 73,小端),用 dd 从这个偏移把它切出来,再交给 unsquashfs 解包即可。不想自己算字节的话,binwalk -e 和 unblob 能一步做完,但这两个工具解 SquashFS 时都要调用外部程序 sasquatch,需要你另外安装。下面每一步都在真实的 OpenWrt 镜像上跑过,输出原样贴出。

适合谁读:手里有自己设备的固件 .bin,或者开源项目发布的镜像,想看看里面的默认配置、启动脚本、BusyBox 版本、Web 服务程序的开发者和家庭实验室玩家。

你会做的事:

  1. 下载一个 OpenWrt sysupgrade 镜像并校验。
  2. 找到 SquashFS 所在区域,读出它的超级块。
  3. 用 dd 切出来,用 unsquashfs 解包。
  4. 换成 Binwalk 3、Unblob 和 REA 再做一遍,看看各自卡在哪里。
  5. 解出来以后先看哪些东西。

只解包你有权分析的固件:自己设备的官方镜像、OpenWrt 这类开源构建,或者 CTF 题目。本文的操作全部是静态的,不刷机、不挂载、不运行。参见 REA 的 DISCLAIMER.md。

示例:OpenWrt 的 sysupgrade 镜像

我用的是 OpenWrt 25.12.5 给 TP-Link Archer C7 v5 出的镜像,这是一款常见的 MIPS 路由器。OpenWrt 每个 target 目录下都有 sha256sums,先校验再动手:

$ curl -LO https://downloads.openwrt.org/releases/25.12.5/targets/ath79/generic/openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin
$ curl -LO https://downloads.openwrt.org/releases/25.12.5/targets/ath79/generic/sha256sums
$ grep c7-v5-squashfs-sysupgrade sha256sums
f15b16b36df7fce8a5cf1302320fdb428c3bb6fbc2949a4858afa257f4f364a2 *openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin
$ shasum -a 256 openwrt-25.12.5-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin
f15b16b36df7fce8a5cf1302320fdb428c3bb6fbc2949a4858afa257f4f364a2  openwrt-25.12.5-...-sysupgrade.bin

文件大小 7,405,862 字节,下文统一叫它 fw.bin(想直接复制命令,可以先 cp 成这个名字)。file 只会描述它看到的第一个头:

$ file fw.bin
fw.bin: u-boot legacy uImage, MIPS OpenWrt Linux-6.12.94, Linux/MIPS, OS Kernel Image (lzma), 2741921 bytes, ...

这句话没错,但只说了一半:uImage 头只覆盖内核,根文件系统紧跟在后面。如果连文件是什么类型都还不确定,可以先看 用魔数识别未知文件类型。

第 1 步:找到 SquashFS 的偏移

签名扫描器会读完整个文件,把认得的头全部列出来。Binwalk 3.1.0(这里是 macOS 上用 Homebrew 装的,安装细节见 Binwalk 3 使用指南)找到两个区域:

$ binwalk fw.bin
DECIMAL     HEXADECIMAL   DESCRIPTION
0           0x0           uImage firmware image, header size: 64 bytes, data size: 2741921 bytes,
                          compression: lzma, CPU: MIPS32, OS: Linux, image type: OS Kernel Image, ...
2741985     0x29D6E1      SquashFS file system, little endian, version: 4.0, compression: xz,
                          inode count: 1214, block size: 262144, image size: 4626494 bytes, ...

(列宽我压缩过。)要记下两个数:偏移 2,741,985 和 镜像大小 4,626,494 字节。这个偏移正好等于 64 字节的 uImage 头加上 2,741,921 字节的内核,说明解析是对的。

不装 Binwalk 也能确认。SquashFS 4 在小端镜像上以 hsqs 开头(大端是 sqsh),后面紧跟超级块字段:

$ xxd -s 2741985 -l 32 fw.bin
0029d6e1: 6873 7173 be04 0000 286c 426a 0000 0400  hsqs....(lBj....
0029d6f1: 2200 0000 0400 1200 c002 0100 0400 0000  "...............

$ LC_ALL=C grep -obUa hsqs fw.bin
2741985:hsqs

be04 0000 是 inode 数(0x4be = 1214),0000 0400 是块大小(0x40000 = 262,144),和 Binwalk 一致。grep 只命中一次,说明后面没有藏着第二个文件系统。

第 2 步:用 dd 切出来

把 SquashFS 那一段原样复制成单独的文件。dd 加 bs=1 好理解,但它一次只拷一个字节,4.6 MB 在我的 Mac 上花了约 8 秒。用 tail/head 结果一样,快得多:

dd if=fw.bin of=rootfs.squashfs bs=1 skip=2741985 count=4626494
# same bytes, faster:
tail -c +2741986 fw.bin | head -c 4626494 > rootfs.squashfs

tail -c +N 从 1 开始计数,所以要加 1。两种方法得到的文件 SHA-256 都是 23003e29…41095bd,后面第 5 步里 REA 对同一区间给出的摘要也是这个值。

为什么要指定长度:文件系统后面的字节不属于它。这个镜像的尾部是 0xFF 填充,外加一小段 JSON 元数据,里面写着 "supported_devices":["tplink,archer-c7-v5","archer-c7-v5"] 和 OpenWrt 版本,sysupgrade 刷机前会检查它。我试过不带 count,unsquashfs 照样能解,但切得干净,之后算哈希、做对比都方便。

第 3 步:用 unsquashfs 解包

unsquashfs 属于 squashfs-tools,Debian/Ubuntu 用 apt install squashfs-tools,macOS 用 brew install squashfs。报 “unsquashfs: command not found” 就是缺这个包。解包之前先读超级块:

$ unsquashfs -s rootfs.squashfs
Found a valid SQUASHFS 4:0 superblock on rootfs.squashfs.
Creation or last append time Mon Jun 29 20:59:20 2026
Filesystem size 4626494 bytes (4518.06 Kbytes / 4.41 Mbytes)
Compression xz
Block size 262144
...
Number of fragments 34
Number of inodes 1214
Number of ids 1

重点看压缩方式。标准 squashfs-tools 只要编译时带上,就能处理 gzip、xz、lzo、lz4、zstd;但有些厂商用了改过的 LZMA 或者改过的头,标准工具不认(见第 6 步)。然后解到一个新目录:

$ unsquashfs -d rootfs rootfs.squashfs
create_inode: could not create character device rootfs/dev/console, because you're not superuser!
...
created 870 files
created 129 directories
created 214 symlinks
created 0 devices

普通用户建不了设备节点,这条警告是预期内的,不影响静态阅读。如果只想找某个路径,unsquashfs -ll rootfs.squashfs 能列出权限和属主,不往磁盘写任何东西。

第 4 步:交给 Binwalk 或 Unblob

这两个工具会自己找区域、自己切,但遇到 SquashFS 都要调用 sasquatch(打过补丁的 unsquashfs)。我的 Mac 上没有 sasquatch:它没有 Homebrew 包,源码编译又需要 Linux 专有的头文件 sys/sysinfo.h。于是 Binwalk 3 解出了内核,文件系统却失败了:

$ binwalk -e -C bw fw.bin
ERROR binwalk::extractors::common] Failed to execute command sasquatch[".../bw/fw.bin.extracted/29D6E1/squashfs_29D6E1.sqsh"]: No such file or directory (os error 2)
[+] Extraction of uimage data at offset 0x0 completed successfully
[-] Extraction of squashfs data at offset 0x29D6E1 failed!

所以我换到了 Linux:在 macOS 上用 Podman 起一个 Debian 13 arm64 容器,Binwalk 3.1.0 用 cargo install binwalk --version 3.1.0 编译,Unblob 26.6.4 从 PyPI 装,sasquatch 用 onekey-sec 发布的 v4.5.1-6 版 sasquatch_1.0_arm64.deb,也就是两个项目文档里写的安装方式。sasquatch 在 PATH 上之后,两个工具对同一个文件都成功了:

$ binwalk -e -C bw openwrt-...-sysupgrade.bin
[+] Extraction of uimage data at offset 0x0 completed successfully
[+] Extraction of squashfs data at offset 0x29D6E1 completed successfully
$ ls bw/openwrt-...-sysupgrade.bin.extracted/29D6E1/squashfs-root
bin  dev  etc  init  lib  mnt  overlay  proc  rom  root ...

$ unblob -e unb openwrt-...-sysupgrade.bin
$ ls unb/openwrt-...-sysupgrade.bin_extract/
0-64.unknown  2741985-7368479.squashfs_v4_le_extract  64-2741985.lzma_extract  7368479-7405862.unknown

Binwalk 用十六进制偏移给目录命名(29D6E1),Unblob 用十进制的起止偏移(2741985-7368479)。Unblob 还会把认不出来的字节单独存成 .unknown 块,这里是 64 字节的 uImage 头和 37,383 字节的尾部;它对整次运行给出的块识别率是 66.37%。两者更细的对比见 Binwalk 3 使用指南。

第 5 步:用 REA 做一遍

REA(morluto 的开源项目)会包装你自备的 Binwalk 和 Unblob,把结果变成结构化的 Evidence,而不是终端里的表格。6.4.0 的这两条固件命令只支持 Linux,在 macOS 上直接拒绝:

{"code":"capability_unavailable","message":"This integration requires Linux and an absolute REA_BINWALK_COMMAND path to the caller-supplied tool", ...}

在 Linux 容器里,我用 npm 包装了 [email protected],用绝对路径指向两个工具,然后运行:

export REA_BINWALK_COMMAND=/usr/local/cargo/bin/binwalk
export REA_UNBLOB_COMMAND=/opt/unblob/bin/unblob
rea inspect-firmware-regions /root/fw/openwrt-...-sysupgrade.bin --format json
rea extract-firmware /root/fw/openwrt-...-sysupgrade.bin /root/fw/sq --offset 2741985 --length 4626494 --format json

inspect-firmware-regions 返回同样两个区域,reported_size 分别是 2,741,985 和 4,626,494,并且都标着 size_basis: provider_reported_validation_unknown,提醒你“上报的大小”不等于“验证过的分区边界”。带区间的 extract-firmware 返回 coverage: "partial":1,186 个普通文件,214 条 unpublished_entries(BusyBox 的符号链接,记录了 link_target,但不写到磁盘),1 条触到深度上限的路径,7 条诊断,全是 OpenWrt 软件包数据库里的 “Duplicate tar entry.”。selection 的摘要是 23003e29…41095bd,和我手动 dd 切出来的一样;解出的 bin/busybox SHA-256(458f75d0…db585d5e3)也和 Mac 上 unsquashfs 解出的完全相同。

有三处我踩了坑,你多半也会遇到:

  • 在 Linux 上,REA 需要 PATH 里有 procps 版的 ps。精简版 Debian 镜像里没有,两条命令都报 “Linux process ownership inspection requires a procps-compatible ps on REA’s PATH”,装上 procps 才好。
  • 最开始我让输入输出都放在从 macOS 共享进虚拟机的目录里,结果报清理错误(“Extraction directory identity changed”)。把镜像复制到容器自己的文件系统里就正常了。
  • 输出目录必须不存在。对同一目录再跑一次,会返回 “Extraction output root already exists”。

REA 文档把 SquashFS 列为未验证格式,验证环境是 Linux x64;我跑的是 Linux arm64,6.4.0 的代码只检查是不是 Linux,所以能跑。预算、深度和交给 Ghidra 的完整流程见 REA 固件分析:Binwalk、Unblob 与 Ghidra。

第 6 步:标准工具不认的时候

有些厂商会改 SquashFS 的魔数、超级块布局或 LZMA 参数。为了看看会是什么样子,我把自己切出来的副本前 4 个字节改成了 abcd:

$ unsquashfs -s patched.sqsh
FATAL ERROR: Can't find a valid SQUASHFS superblock on patched.sqsh
$ binwalk patched.sqsh
4626120     0x4696C8      XZ compressed data, total size: 344 bytes

Binwalk 不再报告文件系统,只剩靠近末尾的一段零散 XZ 数据。出现这种情况,就该试 sasquatch 了。它最初的 README 说,它是一组 unsquashfs 补丁,目标是支持“尽可能多的厂商魔改 SquashFS 实现”,包括用暴力尝试的方式识别改过的 LZMA 参数。我没有拿真实的厂商镜像测过,所以把它当成下一步可以试的工具,而不是保证能解。如果那一段熵值很高、又没有任何可识别的头,很可能是加密的,换什么解包工具都没用。

固件也不全是 SquashFS。Binwalk 3 自带的提取器清单(binwalk -L)对常见格式是这样对应的:

文件系统 Binwalk 3 调用 说明
SquashFS sasquatch 标准镜像用普通 unsquashfs 也行
JFFS2 jefferson NOR flash 设备常见
UBI / UBIFS ubireader_extract_images / ubireader_extract_files NAND 设备,来自 ubi_reader 项目
CramFS 7zz 老设备

Unblob 用的也是这些辅助程序,unblob --show-external-dependencies 会列出它找到了哪些。

第 7 步:解出来先看什么

先看身份信息,再看二进制:

$ cat rootfs/etc/openwrt_release
DISTRIB_RELEASE='25.12.5'
DISTRIB_REVISION='r33051-f5dae5ece4'
DISTRIB_TARGET='ath79/generic'
DISTRIB_ARCH='mips_24kc'
$ strings rootfs/bin/busybox | grep -m1 "BusyBox v"
BusyBox v1.37.0 (2026-06-29 12:59:20 UTC)
$ file rootfs/bin/busybox
rootfs/bin/busybox: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-mips-sf.so.1, no section header

/etc 里是默认配置和启动脚本,/usr/sbin 里是各个守护进程,这个镜像里有 uhttpd、dnsmasq、dropbear、hostapd。不装工具链也想看二进制的头,可以把它拖进 原生二进制概览。busybox 显示为 ELF32、MIPS、大端、ET_EXEC,解释器是 musl,依赖 libgcc_s.so.1 和 libc.so,并且标着已 strip(没有节头)。uhttpd 是 PIE(ET_DYN, DF_1_PIE),链接了 libubox 和 json-c。这个工具在浏览器里运行,不上传文件;各字段的含义见 ELF / PE / Mach-O 在线查看。

常见问题

为什么 file 只说是 uImage? file 只描述偏移 0 处的头。要找后面的内容,得用 Binwalk 这样的扫描器,或者直接搜 hsqs。

需要 root 吗? 不需要。只有 /dev/console 这类设备节点要 root,读文件用不到它们。

Windows 上能做吗? squashfs-tools 和 sasquatch 都是 Linux 工具,用 WSL 或 Linux 虚拟机。macOS 上 Homebrew 的 squashfs 提供 unsquashfs,解这个标准镜像没问题。

解出来的目录树就是设备上运行的样子吗? 不是。它只是只读的基础层。OpenWrt 的改动存在 overlay 分区里,镜像里没有。

授权声明:只从自己的设备、开源项目或 CTF 题目中提取固件,并遵守各厂商的许可条款。

下一步