3. 文件系统解析
固件安全评估中,拿到一个 .bin 或 .img 之后,第一件真正有技术含量的事往往不是跑漏洞扫描,而是把固件里的文件系统“拆开”。只有文件系统被正确识别、挂载或提取,后续的密钥、证书、启动脚本、Web 后台、硬编码口令才有机会暴露出来。面试中这一节考察的不是“你会不会用 binwalk”,而是你是否理解每种文件系统的存储结构、适用场景,以及在不同情况下该选择挂载还是提取。
一、四种文件系统的定位差异
SquashFS 是只读压缩文件系统,在嵌入式 Linux 固件中占据绝对主流。它支持 gzip、xz、lzma、lzo、zstd 等多种压缩算法,元数据也经过压缩,因此体积小、挂载快。由于只读,它通常作为根文件系统的基础层,配合 overlayfs 或 tmpfs 提供可写层。CramFS 同样是只读压缩文件系统,出现更早,采用固定大小的块和 zlib 压缩,最大文件系统和单文件尺寸受限(历史上单文件不超过 16MB),如今多见于老设备。JFFS2 是日志型可读写文件系统,直接运行在 MTD 裸闪存之上,自带磨损均衡和掉电保护,适合 NOR/NAND 上需要频繁写入的小分区,例如配置区。UBIFS 则是 JFFS2 的继任者,工作在 UBI 层之上,针对大容量 NAND 优化,挂载更快、扩展性更好,常见于较新的路由器和 IoT 设备。
面试里常被追问的一点是:JFFS2 和 UBIFS 不能像 SquashFS 那样直接 mount -o loop。因为它们依赖 MTD/UBI 子系统,必须先在内存中模拟出 MTD 设备,再挂载。这个区别决定了提取策略的分叉。
二、识别:先判断“是什么”,再决定“怎么拆”
识别阶段建议先看魔数,再用工具交叉验证。SquashFS 的小端镜像以 hsqs(0x73717368)开头,大端为 sqsh;JFFS2 的节点魔数通常是 0x1985(小端字节序表现为 85 19);UBIFS 本身没有简单文件头,需要先识别 UBI 的 UBI# 魔数;CramFS 则以 0x28cd3d45 开头,ASCII 可见为 E=\xcd( 之类的形态。
# 查看固件整体结构,定位各分区偏移
binwalk firmware.bin
# 只识别文件系统类型,不自动提取
binwalk -y squashfs -y jffs2 -y cramfs -y ubi firmware.bin
# 用 file 辅助判断
file firmware.bin
# 十六进制查看疑似偏移处的魔数
xxd -s 0x200000 -l 64 firmware.bin
假设示例中 binwalk 输出显示偏移 0x200000 处为 SquashFS、0x600000 处为 JFFS2。这里的偏移和大小都属于教学假设,实际值必须以工具输出为准。识别到类型后,按下面的策略处理。
三、SquashFS 与 CramFS:直接挂载或解包
SquashFS 最省事的做法是挂载。先按偏移和长度切出分区,再 loop 挂载:
# 从 0x200000 开始截取 4MB 的 SquashFS 分区
dd if=firmware.bin of=rootfs.squashfs bs=1 skip=$((0x200000)) count=$((0x400000))
# 查看文件系统信息
unsquashfs -s rootfs.squashfs
# 提取到指定目录
unsquashfs -d ./rootfs_extracted rootfs.squashfs
# 或者直接挂载(需要 root)
mkdir -p /mnt/rootfs
mount -t squashfs -o loop,ro rootfs.squashfs /mnt/rootfs
如果 unsquashfs 报“不支持某压缩算法”,说明该镜像用了较新的 zstd 或 lz4,需要更新 squashfs-tools 或安装对应库。挂载后重点看 /etc、/etc/init.d、/usr/bin、/www、/lib 等目录。CramFS 的处理类似,使用 cramfsck -x ./out image.cramfs 提取,或 mount -t cramfs -o loop 挂载。CramFS 不支持压缩算法协商,遇到损坏通常只能靠偏移重新定位。
四、JFFS2:用 mtdram 模拟 MTD 后挂载
JFFS2 镜像不能直接 loop 挂载。标准做法是加载 mtdram 和 mtdblock 模块,创建一个足够大的模拟 MTD 设备,把镜像写进去,再挂载对应的 /dev/mtdblockN。
# 加载模块,total_size 单位为 KB,这里假设分区 8MB
modprobe mtdram total_size=8192 erase_size=64
modprobe mtdblock
# 将 JFFS2 镜像写入模拟 MTD 设备
dd if=jffs2_partition.bin of=/dev/mtdblock0 bs=1
# 挂载
mkdir -p /mnt/jffs2
mount -t jffs2 /dev/mtdblock0 /mnt/jffs2
如果内核不支持 jffs2,或环境不允许加载模块,可以改用 jefferson 这类工具直接提取:jefferson -d ./jffs2_out jffs2_partition.bin。挂载后要留意 JFFS2 的日志特性:同一个文件可能存在多个版本节点,工具通常给出最新版本,但被删除文件的残留数据有时仍可恢复,这对寻找历史配置或已删除凭据很有价值。
五、UBIFS:先解 UBI,再挂载 UBI 卷
UBIFS 的处理分两步。第一步把 UBI 镜像拆成各个卷,第二步挂载或提取 UBIFS。常用工具是 ubireader 系列和内核的 ubiattach/ubimount。
# 查看 UBI 镜像中的卷信息
ubireader_display_info ubi.img
# 提取所有卷
ubireader_extract_images -o ./ubi_out ubi.img
# 提取某个 UBIFS 卷中的文件
ubireader_extract_files -o ./ubifs_out ubi_out/ubi_img-0.img
若要在内核层挂载,需要 nandsim 模拟 NAND,再 ubiattach 绑定 MTD,最后 mount -t ubifs。这条路径依赖内核配置,面试中能说清“UBI 是卷管理层、UBIFS 是文件系统层”这一分层关系,比死记命令更重要。
六、敏感文件定位清单
文件系统展开后,按下面的方向系统排查,比漫无目的地翻目录高效得多。以下路径为常见位置,实际以目标固件为准。
| 类别 | 典型路径 | 关注点 |
|---|---|---|
| 账户与口令 | /etc/passwd、/etc/shadow、/etc/config/ | 弱哈希、默认口令、空口令账户 |
| 密钥证书 | /etc/ssl/、/etc/certs/、*.pem、*.key | 私钥硬编码、自签名 CA、过期证书 |
| 启动脚本 | /etc/init.d/、/etc/rc.local、/etc/inittab | 调试后门、telnetd 启动、命令注入点 |
| Web 后台 | /www/、/usr/share/web/、/htdocs/ | 硬编码 token、API 密钥、调试接口 |
| 配置与日志 | /etc/config/、/var/、/tmp/ | 明文凭据、云平台接入信息 |
| 二进制与库 | /bin/、/sbin/、/usr/sbin/、/lib/ | setuid 程序、危险函数、版本信息 |
实操中可以用一条组合命令快速筛出高价值文件:
# 在提取目录中查找私钥、证书和含 password 的配置
grep -rIl -E "BEGIN (RSA |EC )?PRIVATE KEY" ./rootfs_extracted
find ./rootfs_extracted -name "*.pem" -o -name "*.key" -o -name "*.crt"
grep -rIn -E "password|passwd|secret|token" ./rootfs_extracted/etc 2>/dev/null | head -50
找到疑似凭据后,不要停在“看到了”。要判断它是否真正生效:口令哈希能否用 john 或 hashcat 破解、私钥是否对应设备上实际启用的服务、token 是否在固件更新或云通信中被引用。这一步把“提取”升级为“可利用性判断”,也是面试官区分候选人的关键点。
七、常见坑与判断依据
第一,偏移错误。binwalk 给出的偏移有时是文件系统内部结构的偏移,而非分区起点,切出来的镜像挂载失败。此时应结合分区表(如 fdisk -l、parted)或 U-Boot 环境变量中的 mtdparts 交叉确认。第二,压缩算法不匹配。SquashFS 报错时先看 unsquashfs -s 输出的压缩类型,再决定是否升级工具。第三,大小端问题。部分 MIPS 设备使用大端 SquashFS,魔数为 sqsh,需用支持大端的工具处理。第四,JFFS2/UBIFS 的擦除块大小设置错误会导致挂载后文件损坏,erase_size 应与实际闪存一致,常见为 64KB 或 128KB。
判断提取是否成功的依据很直接:目录结构完整、关键文件可读、文件大小非零、脚本内容可解析。若挂载后大量文件为 0 字节或乱码,多半是偏移、压缩或擦除块参数有误,应回到识别阶段重新核对,而不是在错误镜像上继续分析。
把这一节落到面试表达上,核心逻辑是:先识别文件系统类型与分区边界,再根据是否依赖 MTD/UBI 选择挂载或提取路径,最后围绕敏感文件清单做定向排查并判断可利用性。能把这套链路讲清楚,并说得出每一步的输入、命令和失败判断依据,就达到了固件安全工程师在这一知识点上的实战要求。