4、安全启动流程:BootROM 与安全启动链、信任根建立
安全启动,说白了就是给芯片装上一把「从娘胎里就焊死」的锁。你想想看,如果攻击者能随便改你的固件,那 TrustZone 再强也没用——人家直接把你整个系统给换了。
我刚开始接触 TrustZone-M 时,总觉得安全启动就是个「验签名」的流程。后来在项目里被折腾过几次才明白,真正的难点不在于验签算法本身,而在于「谁先信任谁」这个死循环——也就是信任根怎么建立。
4.1 信任根:一切安全的起点
信任根(Root of Trust, RoT)是整个安全启动链的基石。它必须满足两个条件:
- 不可篡改——芯片出厂后就改不了
- 绝对可信——没有任何外部依赖
在 TrustZone-M 体系里,信任根通常固化在 BootROM 中。BootROM 是芯片上电后执行的第一段代码,它被掩膜在芯片内部,物理上无法修改。
核心原则:信任根不能「被信任」,它必须是「天生可信」的。任何需要外部输入才能验证自身完整性的方案,都不配叫信任根。
我记得有个客户问过我:「我把公钥放在 Flash 里,BootROM 去验它不行吗?」我说不行——攻击者可以同时替换公钥和固件,你验了个寂寞。公钥必须固化在 BootROM 里,或者由 BootROM 通过硬件机制安全获取。
4.2 BootROM:第一段代码的使命
BootROM 是芯片上电后 CPU 执行的第一段代码。它通常只有几 KB 到几十 KB,但承担着整个系统的安全重任。
BootROM 的核心职责包括:
- 硬件初始化——配置时钟、电源、基本外设
- 安全启动链启动——验证下一级 Bootloader 的签名
- 生命周期管理——检查芯片处于哪个安全状态(比如生产模式、调试模式、RMA 模式)
- 调试接口控制——根据生命周期状态决定是否开放调试端口
我个人习惯把 BootROM 比作「芯片的 BIOS + 安全监控」——它既要干活,又要盯着别人别乱来。
实战经验:逆向 BootROM 时,我通常会先定位它的入口函数。ARM Cortex-M 的复位向量在 0x00000000,但 BootROM 可能映射到不同的地址区域。用调试器读一下 VTOR(向量表偏移寄存器)就能找到入口。
4.3 安全启动链:逐级验证的信任传递
安全启动链(Secure Boot Chain)的核心思想很简单:每一级代码在运行前,都必须由上一级验证其完整性。
典型的 TrustZone-M 安全启动链如下:
| 层级 | 代码 | 存储位置 | 验证方式 |
|---|---|---|---|
| 0 | BootROM | 芯片内部 ROM(掩膜) | 物理不可篡改(信任根) |
| 1 | Primary Bootloader (PBL) | 内部 Flash 或 OTP | BootROM 验签 |
| 2 | Secondary Bootloader (SBL) | 外部 Flash 或 eMMC | PBL 验签 |
| 3 | Secure Firmware (TF-M / OP-TEE) | 外部 Flash | SBL 验签 |
| 4 | Non-Secure OS / Application | 外部 Flash | Secure Firmware 验签 |
每一级验签通过后,才把执行权交给下一级。如果任何一级验签失败,芯片就会进入安全错误处理流程——通常是死循环或者复位。
嗯,这里要注意:验签不是简单的哈希比对。真正的安全启动用的是非对称签名验证(RSA 或 ECDSA)。BootROM 里固化了公钥,用它来验证 PBL 的签名。PBL 里可能包含下一级公钥,或者继续使用同一套密钥体系。
4.4 信任链的建立过程
我画了一张图,帮你理清整个信任链的建立过程:
从图上你能看到,整个链条是单向的、不可逆的。每一级只信任上一级,上一级只信任自己的公钥。这就是「信任链」的本质——信任不是凭空产生的,而是逐级传递的。
4.5 逆向分析视角:如何定位和提取信任根
做逆向分析时,我们最关心的是:BootROM 里到底固化了什么?公钥放在哪里?签名算法是什么?
我分享几个实战中常用的方法:
4.5.1 提取 BootROM 镜像
BootROM 通常被映射到芯片的特定地址区域。对于 Cortex-M 系列,复位后从 0x00000000 开始执行,但 BootROM 可能通过硬件 remap 映射到其他地址。你可以:
- 用调试器(J-Link / ST-Link)读取整个 Flash 和 ROM 区域
- 查找芯片的 ROM 基地址(通常在 Reference Manual 里能找到)
- 如果芯片有读保护,可能需要先解除保护(或者用侧信道攻击——这个后面章节会讲)
4.5.2 定位公钥
公钥在 BootROM 里通常以固定偏移存放。我常用的方法是:
- 搜索 RSA 公钥的特征——模数 N 通常以
0x00 0x00 0x00 0x01 0x00 0x01开头(即 65537 的编码) - 搜索 ECDSA 公钥的特征——曲线参数(如 P-256 的固定 OID)
- 查找签名验证函数的调用点,反向追踪公钥地址
注意:有些芯片厂商会把公钥进行混淆或加密存储。我曾经遇到过一家厂商把公钥拆成多段,分散在 BootROM 的不同位置,运行时再拼接。逆向时别被这种小把戏骗了。
4.5.3 分析签名验证算法
找到签名验证函数后,你需要确定:
- 用的是 RSA 还是 ECDSA?
- 哈希算法是 SHA-256 还是 SHA-512?
- 签名格式是 PKCS#1 v1.5 还是 PSS?
- 有没有额外的 anti-replay 机制(比如计数器或随机数)?
我个人习惯用 Ghidra 或 IDA Pro 加载 BootROM 镜像,先定位字符串(比如错误信息),然后反向追踪调用链。签名验证函数通常会有明显的循环和模幂运算特征——RSA 的模幂运算在 ARM 上会展开成大量的乘法指令,一眼就能认出来。
4.6 常见的安全启动漏洞
做了这么多年逆向,我见过不少安全启动的「翻车」案例。这里列几个典型的:
| 漏洞类型 | 描述 | 危害 |
|---|---|---|
| 公钥可替换 | BootROM 从外部 Flash 读取公钥,未做完整性校验 | 攻击者可替换公钥,加载任意固件 |
| 签名验证跳过 | 验签函数存在逻辑漏洞,可通过特定输入绕过 | 直接加载未签名固件 |
| 回滚攻击 | 未检查固件版本号,允许加载旧版本有漏洞的固件 | 利用已知漏洞降级攻击 |
| 调试接口未锁定 | BootROM 未在安全启动失败后锁定调试端口 | 攻击者可通过调试器读取内存 |
| 侧信道泄露 | 签名验证过程中功耗/电磁辐射泄露密钥信息 | 通过 SPA/DPA 提取私钥 |
避坑指南:我曾经逆向过一个 IoT 芯片,它的 BootROM 验签函数里有个 if (result == 0) { jump_to_next_stage(); }——你没看错,验签失败返回 0,成功返回 1。攻击者只要把返回值改成 0 就能绕过。这种低级错误在真实产品里并不少见。
4.7 实战:逆向一个简化的 BootROM 验签流程
假设我们拿到一个 Cortex-M33 芯片的 BootROM 镜像,目标是从中提取公钥并分析验签流程。我会这样操作:
- 加载镜像——用 Ghidra 加载,基地址设为 0x00000000
- 定位入口——查看复位向量(地址 0x00000004),找到 Reset_Handler
- 追踪调用链——在 Reset_Handler 里找到对验签函数的调用
- 提取公钥——在验签函数附近查找固定数据区,导出公钥
- 分析算法——通过指令特征判断是 RSA 还是 ECDSA
下面是一个简化的伪代码示例,展示 BootROM 验签的核心逻辑:
// BootROM 验签伪代码
void secure_boot(void) {
// 1. 从固定地址读取公钥
uint8_t* public_key = (uint8_t*)0x1FFF0000;
// 2. 从 Flash 读取 PBL 镜像和签名
uint8_t* pbl_image = (uint8_t*)0x08000000;
uint8_t* signature = (uint8_t*)0x08010000;
uint32_t image_size = *(uint32_t*)0x0800FFFC;
// 3. 计算哈希
uint8_t hash[32];
sha256(pbl_image, image_size, hash);
// 4. 验证签名
int result = rsa_verify(public_key, hash, signature);
// 5. 根据结果跳转
if (result == 0) {
// 验签失败,死循环
while(1);
} else {
// 验签成功,跳转到 PBL
jump_to(0x08000000);
}
}
逆向时,你需要在二进制里找到 sha256 和 rsa_verify 这两个函数的实现。SHA-256 的常量(比如 0x6a09e667, 0xbb67ae85 等)是很好的定位标志。RSA 验签则会有明显的模幂运算循环。
4.8 小结
安全启动的核心就三件事:信任根不可篡改、信任链逐级传递、验签逻辑无漏洞。BootROM 作为信任根,是整个安全体系的基石——它要是出了问题,后面的一切都是空中楼阁。
做逆向分析时,我建议你先从 BootROM 的公钥提取入手。找到了公钥,就等于拿到了整个安全启动链的「钥匙」——虽然你不能用它签名,但你可以用它来验证自己的分析是否正确。
嗯,这一章的内容就到这里。记住:安全启动不是「有就行」,而是「对才行」。一个看似微小的实现缺陷,就可能让整个安全体系形同虚设。