2、固件架构基础:常见嵌入式架构(ARM、MIPS、RISC-V)、固件启动流程(Bootloader、Kernel、Rootfs)、固件文件系统(Squashfs、JFFS2、YAFFS2、Cramfs)、固件镜像结构分析。

大家好,我是你们的老朋友。今天咱们聊聊固件架构的基础。说实话,很多新手一上来就急着拿工具去挖漏洞,结果连固件跑在什么芯片上、怎么启动的都没搞清楚。这就像修车不知道发动机型号,对吧?

我个人习惯,拿到一个固件,第一件事不是解包,而是先看架构。为什么?因为后续所有的分析工具、调试手段,都跟架构绑定。你选错了反汇编器,那分析出来的东西就是一堆乱码。

2.1 常见嵌入式架构:ARM、MIPS、RISC-V

嵌入式世界,目前三足鼎立:ARM、MIPS、RISC-V。当然还有x86,但在IoT设备里相对少见。

ARM

ARM 是绝对的霸主。从你的手机到路由器,再到智能灯泡,到处都是。ARM 是精简指令集(RISC),特点是指令长度固定(Thumb模式除外),寄存器多。

我遇到过最坑的事:分析一个路由器固件,用IDA默认的ARM模式反汇编,结果全是乱码。后来才发现,这固件是Thumb模式编译的。切换成Thumb模式后,代码瞬间清晰了。所以,拿到固件先看入口地址是奇数还是偶数——奇数通常是Thumb。

小技巧: ARM 的调用约定是前4个参数通过 R0-R3 传递,返回值在 R0。记住这个,逆向分析时能省不少时间。

MIPS

MIPS 曾经是网络设备(路由器、交换机)的王者。现在虽然被ARM挤压,但存量巨大。MIPS 也是RISC,但它有个特点:延迟槽

什么叫延迟槽?就是分支指令后面的那条指令,无论分支是否跳转,都会被执行。这导致反汇编时,如果你不识别延迟槽,控制流图就是错的。我曾经在分析一个MIPS固件的漏洞时,因为没处理延迟槽,把漏洞路径给看丢了,折腾了两天。

RISC-V

RISC-V 是后起之秀,开源、模块化。这几年在AIoT、MCU领域发展很快。它的指令集非常简洁,基础指令只有40多条。分析RISC-V固件时,要注意它的压缩指令集(RVC),指令长度可以是16位或32位,混合在一起,反汇编器如果支持不好,很容易出错。

架构 指令长度 字节序 常见设备 逆向难点
ARM 32位/16位(Thumb) 小端为主 手机、路由器、摄像头 ARM/Thumb模式切换
MIPS 32位 大小端都有 老路由器、交换机 延迟槽、大小端识别
RISC-V 32位/16位(RVC) 小端 AIoT、MCU 压缩指令、新架构工具链不成熟

2.2 固件启动流程:Bootloader、Kernel、Rootfs

一个典型的嵌入式固件,启动过程就像接力赛:Bootloader → Kernel → Rootfs。每一棒都至关重要。

Bootloader

Bootloader 是硬件上电后运行的第一段代码。它的任务很简单:初始化硬件(时钟、内存、串口),然后加载Kernel到内存,跳转过去。

常见的Bootloader有:U-Boot(最流行)、RedBoot、Barebox。U-Boot 支持交互式命令行,你可以通过串口中断启动过程,修改启动参数。这在漏洞挖掘中很有用——比如你可以通过修改启动参数,让系统以单用户模式启动,绕过一些认证。

注意: 有些厂商会锁定U-Boot,禁止中断启动过程。但别灰心,很多设备可以通过短路Flash引脚、或者利用NVRAM中的漏洞来绕过。

Kernel

Kernel 是操作系统的核心。它管理硬件资源、提供系统调用。在固件分析中,我们最关心的是Kernel的版本和配置。

为什么?因为老版本Kernel有大量已知漏洞。比如,我曾经分析过一个摄像头固件,它的Kernel版本是2.6.39,这个版本存在一个著名的内存破坏漏洞(CVE-2016-5195,脏牛)。通过这个漏洞,我轻松实现了权限提升。

怎么看Kernel版本?解包固件后,找到vmlinuz或zImage文件,用file命令查看,或者用strings搜索"Linux version"。

Rootfs

Rootfs 是根文件系统。它包含了所有用户空间的程序、库、配置文件。启动时,Kernel会挂载Rootfs,然后执行/sbin/init程序,启动所有服务。

Rootfs 通常是一个压缩的镜像文件。常见的格式有Squashfs、JFFS2、YAFFS2、Cramfs。我们接下来详细说。

固件启动流程 Bootloader U-Boot / RedBoot Kernel vmlinuz / zImage Rootfs Squashfs / JFFS2 硬件上电 → Bootloader初始化硬件 → 加载Kernel到内存 → Kernel挂载Rootfs → 执行 /sbin/init → 启动服务

2.3 固件文件系统:Squashfs、JFFS2、YAFFS2、Cramfs

文件系统是固件内容的载体。不同的文件系统有不同的特点,解包工具也不同。

Squashfs

Squashfs 是目前最流行的只读压缩文件系统。它压缩率高,解压速度快。几乎所有新的路由器、摄像头固件都在用。

解包Squashfs,我推荐用unsquashfs工具(来自squashfs-tools包)。但要注意,Squashfs有多个版本(v3.0, v4.0, v4.1等),不同版本可能不兼容。我曾经遇到一个固件,用最新的unsquashfs解包失败,后来发现它是v3.0的老格式,需要指定-s参数。

# 查看Squashfs版本和压缩方式
unsquashfs -s firmware.squashfs

# 解包
unsquashfs -d output_dir firmware.squashfs

JFFS2

JFFS2 是日志结构的文件系统,专为Flash设计。它支持磨损均衡和掉电保护。但它是可读写的,所以固件中通常用于存放配置数据(如/etc/config)。

解包JFFS2比较麻烦,因为它不是块设备。我通常用jefferson工具(Python写的),或者用flash_erase + nanddump从硬件上读取。

YAFFS2

YAFFS2 是另一种专为NAND Flash设计的文件系统。它比JFFS2更简单、更快。在早期的Android手机和一些工业设备中常见。

解包YAFFS2,可以用unyaffs工具。但要注意,YAFFS2的镜像通常包含OOB(Out-of-Band)数据,解包时需要处理。

Cramfs

Cramfs 是古老的只读压缩文件系统。它非常小,但功能有限(最大文件不能超过16MB)。现在基本被Squashfs取代,但在一些老设备中还能见到。

解包Cramfs,用cramfsckcramfsswap。如果遇到字节序问题(比如大端Cramfs在小端系统上解包),需要用cramfsswap转换。

文件系统 类型 压缩 常见场景 解包工具
Squashfs 只读 gzip/lzma/lzo 固件主文件系统 unsquashfs
JFFS2 读写 zlib 配置分区 jefferson
YAFFS2 读写 NAND Flash unyaffs
Cramfs 只读 zlib 老设备 cramfsck

2.4 固件镜像结构分析

一个完整的固件镜像,通常不是单一的文件系统,而是由多个部分拼接而成。我把它比作一个三明治:头部 + Bootloader + Kernel + Rootfs + 尾部

头部包含了固件的元信息:魔数(Magic Number)、版本号、硬件ID、各部分的偏移量和大小、校验和等。分析头部,是固件逆向的第一步。

我常用的方法是:

  1. binwalk扫描binwalk firmware.bin 可以自动识别固件中的各个部分。
  2. 手动分析头部:用hexdumpxxd查看头部字节,寻找魔数。比如,U-Boot的魔数是27051956(小端),Squashfs的魔数是hsqssqsh
  3. 提取各部分:根据binwalk的输出,用dd命令提取出Bootloader、Kernel、Rootfs。
# 用binwalk扫描固件
binwalk firmware.bin

# 手动提取Kernel(假设偏移是0x10000,大小是0x500000)
dd if=firmware.bin of=kernel.bin bs=1 skip=$((0x10000)) count=$((0x500000))
核心经验: 很多固件在头部之后、Bootloader之前,会有一段填充数据(通常是0xFF或0x00)。别急着跳过,有时候厂商会把一些隐藏的配置信息或密钥藏在这里。我就在一个路由器的固件填充区里,找到过硬编码的SSH私钥。

嗯,说到这里,我想起一个案例。有一次分析一个智能门锁的固件,binwalk只识别出了一个Squashfs文件系统。但我总觉得不对劲,因为固件大小有8MB,而Squashfs只有4MB。后来我用hexdump仔细看头部,发现头部里有一个字段标记了“双固件”模式——原来这个固件包含两个完整的系统镜像,一个运行,一个备份。而binwalk只识别出了第一个。所以,永远不要完全信任自动化工具,手动验证是必须的。

好了,这一章的内容就到这里。固件架构是后续所有分析的基础,花时间把这块吃透,后面会顺利很多。


专注资料整理