2. 安全启动链基础:信任根、启动流程与签名验证
各位同学,今天我们聊安全启动链。这是整个防回滚攻击的基石。说白了,如果启动链都不安全,后面所有防护都是空中楼阁。我做了这么多年嵌入式安全,见过太多产品因为启动链没锁死,被攻击者轻松降级固件、植入后门。嗯,咱们一步步来拆解。
2.1 信任根(Root of Trust, RoT)
信任根是什么?它是整个安全体系的「第一块砖」。你想想看,一个链条要可靠,第一个环节必须绝对可信。在硬件安全里,这个环节通常固化在芯片内部,无法被外部篡改。
核心原则:信任根必须是不可变的、物理隔离的、且只能由硬件强制执行的。
我个人习惯把信任根分为三类:
- 硬件信任根(HW RoT):比如芯片内部的 Boot ROM,出厂后只读,无法修改。我在一个 IoT 项目里遇到过,厂商把启动代码放在 SPI Flash 里,结果攻击者直接替换 Flash 芯片就绕过了校验。这就是典型的没有硬件信任根。
- 软件信任根(SW RoT):通常指经过签名的 Bootloader。它依赖硬件信任根来验证自身完整性。但要注意,软件信任根本身是可更新的,所以更新机制必须同样安全。
- 密码学信任根(Crypto RoT):比如芯片内置的密钥存储区(eFuse、OTP),用于存储签名验证的公钥哈希。这些密钥一旦写入,就无法被软件读取或修改。
我的经验:设计信任根时,一定要考虑「密钥的祖先」。我曾经见过一个方案,公钥存储在 Flash 里,结果攻击者通过电压故障注入,让 CPU 跳过了公钥校验。后来我们改用 eFuse 存储公钥哈希,才彻底堵住这个漏洞。
2.2 安全启动流程
安全启动,本质上是一个「链式验证」的过程。每一级在加载下一级之前,都要先验证它的签名。如果验证失败,就拒绝执行。这个流程我画了一张图,大家看看。
这个流程看起来简单,但实际落地时坑很多。我举几个例子:
- Boot ROM 必须不可绕过:有些芯片支持从外部存储器直接启动,如果这个路径没锁死,攻击者可以跳过 Boot ROM。我记得有个车规级芯片就出过这种问题,后来打了补丁。
- 每一级都要验证:不能只验证第一级,后面就放行。攻击者会替换中间环节。我曾经审计过一个方案,厂商只验证了 BL1,结果 BL2 被替换后直接加载了恶意 OS。
- 失败处理要硬:验证失败时,不能只是打印一条日志然后继续。应该直接死循环或触发硬件复位。我见过一个产品,验证失败后只是报了个警告,系统照样跑,等于没防护。
注意:安全启动不是「可选项」,而是「强制项」。如果芯片支持安全启动,必须启用,并且要锁定配置寄存器,防止攻击者通过 JTAG 或调试接口关闭它。
2.3 签名验证机制
签名验证是安全启动的核心技术。没有它,信任根和启动流程都是空谈。我们常用的签名算法是 RSA 或 ECDSA,配合哈希算法(如 SHA-256)。
基本流程是这样的:
- 签名阶段(在开发环境完成):对固件镜像计算哈希,然后用私钥对哈希加密,生成签名。签名和固件一起发布。
- 验证阶段(在设备上完成):Boot ROM 读取固件,计算哈希,然后用公钥解密签名,得到原始哈希。两个哈希比对,一致则通过。
这里我贴一段伪代码,展示验证逻辑:
// 伪代码:安全启动验证
bool verify_image(uint8_t *image, uint32_t size, uint8_t *signature) {
// 1. 计算固件哈希
uint8_t hash[32];
sha256(image, size, hash);
// 2. 从 eFuse 读取公钥
uint8_t public_key[64];
read_efuse(PUBLIC_KEY_REGION, public_key);
// 3. 用公钥解密签名,得到原始哈希
uint8_t decrypted_hash[32];
ecdsa_verify(public_key, signature, decrypted_hash);
// 4. 比对哈希
if (memcmp(hash, decrypted_hash, 32) == 0) {
return true; // 验证通过
} else {
// 验证失败,进入死循环
while(1);
}
}
这段代码看起来简单,但有几个细节要注意:
| 环节 | 常见错误 | 正确做法 |
|---|---|---|
| 哈希计算 | 只计算部分区域,忽略头部 | 必须覆盖整个固件镜像,包括头部和负载 |
| 公钥存储 | 放在可读写的 Flash 中 | 必须放在 eFuse 或 OTP,且写死后锁定 |
| 签名算法 | 使用弱算法(如 MD5、SHA-1) | 至少 SHA-256 + RSA-2048 或 ECDSA P-256 |
| 失败处理 | 返回错误码,让上层决定 | 硬件强制停机,不给攻击者任何机会 |
我的建议:公钥的哈希值(而不是公钥本身)存储在 eFuse 中。验证时,先加载公钥,计算它的哈希,与 eFuse 中的值比对。这样即使攻击者替换了公钥,哈希比对也会失败。这个技巧我在一个金融终端项目里用过,效果很好。
2.4 防回滚与安全启动的关系
你可能会问:安全启动和防回滚有什么关系?关系大了。安全启动保证「只有签名的固件才能运行」,而防回滚保证「只有新版本的固件才能运行」。两者结合,才能防止攻击者用旧版本固件(包含已知漏洞)来降级攻击。
实现方式很简单:在固件镜像中加入版本号字段,并在签名验证时检查版本号是否大于等于当前版本。如果版本号更低,直接拒绝启动。
一句话总结:安全启动是「能不能跑」,防回滚是「哪个版本能跑」。两者缺一不可。