2、固件架构基础:常见嵌入式架构(ARM、MIPS、RISC-V)、启动流程与Bootloader分析
做固件安全,说白了就是跟芯片底层打交道。你连CPU长什么样、怎么启动的都不知道,那漏洞挖掘基本就是瞎蒙。这一章,我带你把嵌入式世界里最常见的三个架构——ARM、MIPS、RISC-V,以及它们的启动流程和Bootloader,彻底捋一遍。
2.1 三大嵌入式架构:ARM、MIPS、RISC-V
我个人习惯把嵌入式架构分成两类:一类是“你绕不开的”,另一类是“你迟早会碰到的”。ARM属于前者,MIPS和RISC-V属于后者。咱们一个一个说。
2.1.1 ARM架构
ARM在嵌入式领域的统治地位,不用我多讲。从智能家居到路由器,从手机到工控设备,ARM几乎无处不在。做固件安全,ARM是你必须啃下的硬骨头。
ARM的几个关键点:
- 两种模式:ARM模式(32位指令)和Thumb模式(16位指令)。Thumb模式代码密度高,但指令功能弱一些。我在逆向分析时,经常遇到混合编译的固件,一会儿ARM模式,一会儿Thumb模式,切换点就是BX指令。嗯,这里要注意,IDA Pro有时候会自动识别错,你得手动调。
- 寄存器:R0-R15共16个通用寄存器。其中R13是栈指针SP,R14是链接寄存器LR,R15是程序计数器PC。PC可读可写,这意味着你可以通过修改PC来控制程序流——这是很多漏洞利用的基础。
- 大小端:ARM默认小端,但支持大端模式。我遇到过一款国产路由器,它的大端ARM固件让我折腾了两天,因为反汇编出来的指令全是反的。
避坑指南:我曾经在分析一个ARM固件时,发现函数开头不是常见的PUSH {LR},而是PUSH {R4-R7, LR}。后来才意识到,这是编译器优化后的结果。所以,别死记硬背函数序言,多看几种编译器的输出。
2.1.2 MIPS架构
MIPS在路由器、光猫、机顶盒里非常常见。说实话,MIPS的指令集比ARM规整得多,但它的延迟槽(Delay Slot)是个坑。
MIPS的几个关键点:
- 延迟槽:分支指令后面的那条指令,无论分支是否跳转,都会被执行。你想想看,这有多反直觉?我在逆向MIPS固件时,经常因为忽略延迟槽而算错跳转地址。我的习惯是,看到分支指令,先看它后面那条指令是什么。
- 寄存器:32个通用寄存器,$0固定为0,$31是返回地址寄存器RA。MIPS没有专门的栈指针,$29通常被用作SP,但这不是强制的。
- 大小端:MIPS既有大端也有小端。Broadcom的芯片喜欢用大端,Qualcomm的芯片喜欢用小端。拿到固件先别急着分析,看一眼文件头或者字符串,确认一下字节序。
个人经验:MIPS的延迟槽在漏洞利用时反而能帮上忙。比如,你可以在延迟槽里放一条无害指令,让ROP链更稳定。我曾在一次CTF比赛中用这个技巧绕过了ASLR。
2.1.3 RISC-V架构
RISC-V是后起之秀,开源、简洁、可扩展。虽然目前市场份额还不大,但势头很猛。我预测,未来五年,RISC-V会在IoT设备里大量出现。
RISC-V的几个关键点:
- 模块化:RISC-V的指令集是模块化的。基础指令集RV32I/RV64I是必选的,然后可以选配乘除法(M)、原子操作(A)、单精度浮点(F)、双精度浮点(D)等扩展。你看到的RISC-V芯片,可能只实现了部分扩展。
- 寄存器:32个通用寄存器,x0固定为0,x1是返回地址RA。和MIPS有点像,但更规整。
- 无延迟槽:RISC-V没有延迟槽,这让逆向分析简单了不少。我第一次分析RISC-V固件时,还习惯性地去找延迟槽,结果发现根本没有,白费了半天劲。
注意:RISC-V的指令编码是变长的。基础指令是32位,但压缩指令集(RVC)是16位。这意味着,你不能像ARM那样,简单地按4字节对齐来解析指令。我建议用专门的RISC-V反汇编工具,比如riscv64-unknown-elf-objdump。
2.2 启动流程:从复位到操作系统
芯片上电后,到底发生了什么?这个问题,我建议每个做固件安全的人都搞清楚。因为启动流程中的每一个环节,都可能成为攻击面。
典型的嵌入式启动流程:
- 复位:CPU从复位向量地址取第一条指令。ARM一般是0x00000000或0xFFFF0000,MIPS是0xBFC00000,RISC-V是0x00000000或0x80000000(取决于具体实现)。
- BootROM:芯片内部固化的只读代码,负责初始化硬件、检测启动介质(NAND、NOR、SD卡等)、加载Bootloader到RAM。BootROM一般不可修改,但它的漏洞也不少。我记得有个著名的漏洞,就是通过USB烧录接口绕过BootROM的签名校验。
- Bootloader:比如U-Boot、RedBoot、Barebox。它负责初始化DDR、加载内核镜像到内存、跳转到内核入口。Bootloader是固件安全分析的重点,因为它有网络、文件系统、命令行等复杂功能。
- 内核:Linux内核或RTOS。内核启动后,会挂载根文件系统、启动init进程。
为什么会这样设计?说白了,就是分阶段加载。芯片内部空间有限,BootROM只能放很小的代码。Bootloader可以大一些,但也不能太大。最终,由内核来接管整个系统。
2.3 Bootloader分析:以U-Boot为例
U-Boot是目前最流行的嵌入式Bootloader。我分析过的固件里,十有八九用的都是U-Boot。它的功能很强大,但漏洞也不少。
U-Boot的关键结构:
- SPL(Secondary Program Loader):有些芯片的BootROM只能加载很小的代码,所以U-Boot分成了SPL和U-Boot proper两部分。SPL负责初始化DDR,然后加载U-Boot proper。我在分析一个海思芯片的固件时,发现SPL里有个缓冲区溢出漏洞,可以控制U-Boot proper的加载地址。
- 命令行:U-Boot启动时,如果检测到用户按键(比如按空格键),会进入命令行模式。这个命令行功能很全,可以读写内存、烧写Flash、启动网络。嗯,这里要注意,如果产品在出厂时没有禁用U-Boot命令行,那攻击者就可以通过串口直接控制设备。
- 环境变量:U-Boot的环境变量存储在Flash里,比如bootargs、bootcmd。攻击者如果能够修改环境变量,就可以改变启动参数,甚至加载恶意内核。我曾经在分析一个路由器固件时,发现它的U-Boot环境变量没有做完整性校验,直接通过修改bootargs来禁用了安全模块。
避坑指南:我曾经在分析一个U-Boot固件时,发现它的命令行被密码保护了。但密码是明文存储在Flash里的,而且偏移地址是固定的。我直接dump出Flash,找到密码字符串,然后通过串口登录了U-Boot命令行。所以,别以为加了密码就安全了。
2.4 知识体系结构图
下面这张图,我把本章的核心知识点串起来了。你可以把它当作一个思维导图,快速回顾整个知识体系。
我的建议:学架构,别死记硬背寄存器名字。你只需要记住每个架构最与众不同的那个点——ARM的Thumb切换、MIPS的延迟槽、RISC-V的模块化。剩下的,遇到具体问题时再查手册就行。
好了,这一章的内容就到这里。记住,固件安全的核心是理解底层。你越了解芯片怎么工作,就越能找到它的破绽。