3、固件文件系统分析:常见文件系统类型与关键文件识别

做固件逆向分析,拿到一个固件包之后,第一件事是什么?

我个人习惯,不是急着去反汇编,也不是去搜字符串。而是先看看它的文件系统。说白了,文件系统就是固件的骨架。你搞清楚了骨架,里面的血肉(配置、脚本、二进制)自然就清晰了。

今天咱们就聊聊固件里最常见的几种文件系统,以及怎么把它们挂载起来、怎么找到那些关键文件。

3.1 常见文件系统类型

嵌入式设备嘛,资源有限,所以文件系统也得省着点用。我这些年遇到的,主要是下面这四种:

文件系统 特点 常见场景 压缩方式
Squashfs 只读、高压缩比、支持大文件 OpenWrt、LEDE、多数Linux路由器 gzip / lzma / lzo / xz
Cramfs 只读、压缩、单文件最大16MB 老旧嵌入式设备、部分IP Camera zlib
JFFS2 可读写、支持NAND Flash、磨损均衡 早期Linux嵌入式设备、部分工业设备 zlib / lzma
YAFFS2 专为NAND Flash设计、可读写、速度快 Android旧版本、部分IoT设备 无压缩(按页存储)

嗯,这里要注意一点。很多人以为固件里只有一个文件系统,其实不是。我拆过不少设备,经常是多个分区、多种文件系统混着用。比如一个分区是Squashfs放系统文件,另一个分区是JFFS2放用户配置。

核心判断方法:binwalk 扫描固件,看输出里的文件系统签名。Squashfs 的签名是 hsqs,Cramfs 是 0x28cd3d45,JFFS2 有 0x1985 标记。这个我后面会细讲。

3.2 文件系统挂载与浏览

找到文件系统之后,怎么把它打开?

我刚开始做这行的时候,傻乎乎地直接 mount,结果报错一堆。后来才明白,嵌入式文件系统大多需要先解压,或者指定偏移量。

3.2.1 提取与挂载 Squashfs

Squashfs 是最常见的,也是最好处理的。我一般用两种方式:

方式一:直接用 unsquashfs 解压

# 从固件中提取 Squashfs 分区(假设偏移量是 0x100000)
dd if=firmware.bin of=rootfs.squashfs bs=1 skip=$((0x100000))

# 解压
unsquashfs rootfs.squashfs
# 解压后的文件在 squashfs-root/ 目录下

方式二:挂载到本地(适合只读浏览)

sudo mount -t squashfs rootfs.squashfs /mnt/firmware -o loop,ro
ls /mnt/firmware

小技巧:如果你不确定偏移量,用 binwalk -e firmware.bin 自动提取。它会帮你把每个文件系统都拆出来。我曾经遇到一个固件,里面嵌了三个 Squashfs 分区,binwalk 全给我扒出来了。

3.2.2 处理 Cramfs

Cramfs 比较老,但有些工业设备还在用。挂载方式跟 Squashfs 类似:

# 挂载 Cramfs
sudo mount -t cramfs rootfs.cramfs /mnt/cramfs -o loop,ro

# 或者用 cramfsck 检查和解压
cramfsck -x ./extracted rootfs.cramfs

这里有个坑。Cramfs 单个文件不能超过 16MB,所以如果你在固件里看到一个很大的文件,那肯定不是 Cramfs。我遇到过有人把大文件拆成多个小文件,然后硬塞进 Cramfs 里,结果挂载后文件内容全是乱的。

3.2.3 JFFS2 和 YAFFS2 的挂载

这两种文件系统跟 Flash 芯片绑定得很紧。直接挂载镜像文件,有时候会失败。我的经验是:

# JFFS2 挂载(需要 mtd 内核支持)
sudo modprobe mtdblock
sudo modprobe jffs2
sudo dd if=rootfs.jffs2 of=/tmp/mtdblock.img bs=1
sudo losetup /dev/loop0 /tmp/mtdblock.img
# 这里需要模拟 MTD 设备,比较复杂,我一般用 jefferson 工具
jefferson rootfs.jffs2

# YAFFS2 挂载(更麻烦,推荐用 unyaffs2)
unyaffs2 rootfs.yaffs2 ./yaffs_root

注意:JFFS2 和 YAFFS2 的镜像文件,经常带有 OOB(Out-of-Band)数据。如果你直接跳过 OOB 去挂载,文件系统会报错。我曾经花了一整天,才发现是 OOB 没处理好。后来我写了个脚本,先剥离 OOB 再挂载,就稳了。

3.3 关键文件识别

文件系统挂载好了,接下来就是找东西。我一般按这个优先级来翻:

3.3.1 配置文件(优先级最高)

配置文件中藏着设备的所有秘密。密码、密钥、API 地址、调试开关……全在里面。

  • /etc/config/ —— OpenWrt 的 UCI 配置文件,比如 networkwirelessfirewall
  • /etc/passwd, /etc/shadow —— 用户密码哈希,有时候是明文
  • /etc/ssl/ —— 证书和私钥,我找到过硬编码的 HTTPS 证书
  • *.conf, *.cfg, *.ini —— 各种自定义配置文件

我个人习惯,先 grep 一下 passwordsecretkey 这些关键词。十次里有八次能直接找到硬编码的凭据。

3.3.2 脚本文件(第二优先级)

脚本文件里经常有启动逻辑、调试接口、后门。重点关注:

  • /etc/init.d/ —— 启动脚本,看哪些服务开机自启
  • /bin/, /sbin/, /usr/bin/ —— 很多设备用 shell 脚本实现功能
  • *.sh, *.bash —— 自定义脚本,有时候会调用 curl/wget 下载东西

嗯,这里有个经验。很多厂商会在脚本里写死调试 IP 或者 telnet 端口。我曾在 /etc/init.d/rcS 里看到一行 telnetd -l /bin/sh,这就是个典型的调试后门。

3.3.3 二进制文件(第三优先级)

二进制文件是逆向分析的主战场。但一开始,我们只需要知道哪些是关键的:

  • /usr/sbin/httpd, /usr/sbin/lighttpd —— Web 服务器,常有漏洞
  • /usr/bin/upnpd —— UPnP 服务,历史上漏洞很多
  • 自定义命名的二进制 —— 比如 dvrHelperipcamApp,这些往往是厂商自己写的,质量堪忧

怎么快速识别?用 file 命令看类型,用 strings 搜敏感字符串。我一般先跑一遍 strings,看看有没有 debugbackdoortest 这些词。

3.4 知识体系总览

为了让你更直观地理解整个分析流程,我画了一张图:

固件文件系统分析流程 固件镜像 文件系统类型识别(binwalk) Squashfs Cramfs JFFS2 YAFFS2 unsquashfs / mount mount / cramfsck jefferson / mount unyaffs2 关键文件识别:配置 / 脚本 / 二进制

这张图把整个流程串起来了。从拿到固件,到识别文件系统类型,再到挂载解压,最后找到关键文件。你跟着这个流程走,基本不会漏东西。

3.5 实战避坑指南

最后,分享几个我踩过的坑:

  • 偏移量不对: 固件里经常有头部信息,文件系统不是从 0 开始的。用 binwalk 先扫一遍,别自己瞎猜。
  • 文件系统损坏: 有些厂商会魔改文件系统,标准工具挂不上。这时候可以试试 binwalk -Me 递归提取。
  • 符号链接失效: 解压后的文件系统里,符号链接可能指向绝对路径。你最好在 chroot 环境里浏览,或者用 readlink 手动检查。

我曾经遇到一个设备,它的 Squashfs 里所有文件都是 0 字节。后来才发现,厂商把文件内容放在了另一个分区,Squashfs 里只是占位符。这种骚操作,你不仔细看根本发现不了。

好了,文件系统这块就聊到这儿。你先把这些基础打牢,后面分析固件漏洞的时候,才能游刃有余。