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 的核心职责包括:

  1. 硬件初始化——配置时钟、电源、基本外设
  2. 安全启动链启动——验证下一级 Bootloader 的签名
  3. 生命周期管理——检查芯片处于哪个安全状态(比如生产模式、调试模式、RMA 模式)
  4. 调试接口控制——根据生命周期状态决定是否开放调试端口

我个人习惯把 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 信任链的建立过程

我画了一张图,帮你理清整个信任链的建立过程:

TrustZone-M 安全启动信任链流程图 TrustZone-M 安全启动信任链 BootROM(信任根) Primary Bootloader Secondary Bootloader Secure Firmware (TF-M) Non-Secure OS / App 验证方式 • 非对称签名验证 • RSA-2048 / ECDSA-P256 • 公钥固化在上一级 失败处理 • 验签失败 → 死循环 • 或触发安全复位 • 调试端口自动锁定

从图上你能看到,整个链条是单向的、不可逆的。每一级只信任上一级,上一级只信任自己的公钥。这就是「信任链」的本质——信任不是凭空产生的,而是逐级传递的。

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 镜像,目标是从中提取公钥并分析验签流程。我会这样操作:

  1. 加载镜像——用 Ghidra 加载,基地址设为 0x00000000
  2. 定位入口——查看复位向量(地址 0x00000004),找到 Reset_Handler
  3. 追踪调用链——在 Reset_Handler 里找到对验签函数的调用
  4. 提取公钥——在验签函数附近查找固定数据区,导出公钥
  5. 分析算法——通过指令特征判断是 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 的公钥提取入手。找到了公钥,就等于拿到了整个安全启动链的「钥匙」——虽然你不能用它签名,但你可以用它来验证自己的分析是否正确。

嗯,这一章的内容就到这里。记住:安全启动不是「有就行」,而是「对才行」。一个看似微小的实现缺陷,就可能让整个安全体系形同虚设。