从固件 .bin 里提取 SquashFS 根文件系统:unsquashfs、Unblob 与 Binwalk 实操
本页目录
结论先行:大多数基于 Linux 的路由器固件,结构都是“内核 + SquashFS 根文件系统”。用 binwalk 找到 SquashFS 的魔数 hsqs(68 73 71 73,小端),用 dd 从这个偏移把它切出来,再交给 unsquashfs 解包即可。不想自己算字节的话,binwalk -e 和 unblob 能一步做完,但这两个工具解 SquashFS 时都要调用外部程序 sasquatch,需要你另外安装。下面每一步都在真实的 OpenWrt 镜像上跑过,输出原样贴出。
适合谁读:手里有自己设备的固件 .bin,或者开源项目发布的镜像,想看看里面的默认配置、启动脚本、BusyBox 版本、Web 服务程序的开发者和家庭实验室玩家。
你会做的事:
- 下载一个 OpenWrt sysupgrade 镜像并校验。
- 找到 SquashFS 所在区域,读出它的超级块。
- 用
dd切出来,用unsquashfs解包。 - 换成 Binwalk 3、Unblob 和 REA 再做一遍,看看各自卡在哪里。
- 解出来以后先看哪些东西。
只解包你有权分析的固件:自己设备的官方镜像、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 题目中提取固件,并遵守各厂商的许可条款。
下一步
- 正确安装 Binwalk 3,并和 Unblob 比较:Binwalk 3 使用指南
- 跑通 REA 固件工作流:REA 固件分析
- 反编译解出来的 ELF:REA + Ghidra 免费分析原生二进制
- 在浏览器里检查任意解出的二进制:原生二进制概览