4. BootROM 安全机制:签名校验、抗回滚与调试锁定

BootROM 是整个安全启动链的“根”。它不可更改,出厂即固化。一旦 BootROM 被攻破,后面所有环节都是白搭。所以芯片厂商在 BootROM 里塞了三道硬关卡:签名校验、抗回滚、调试接口锁定。咱们一个一个拆开看。

4.1 签名校验:BootROM 如何确认下一级镜像可信?

BootROM 上电后第一件事,不是跳转到 Flash 执行代码,而是先验证镜像的签名。这个过程说白了就是:用公钥解签名,比对哈希。

具体流程我画了个图,你看一眼就明白了:

BootROM 签名校验流程 BootROM 上电 读取 Flash 头部 提取签名 + 公钥索引 计算镜像哈希 H1 公钥解密签名得 H2 H1 == H2 ? ✅ 跳转执行 ❌ 死循环/复位

这里有个关键点:公钥存在哪? 我见过两种做法:

  • 熔丝(eFuse):一次性写入,物理不可逆。适合量产锁定。
  • ROM 硬编码:公钥直接刻在 BootROM 里。适合开放生态。

核心原则:BootROM 只信任它自己携带的公钥。任何外部传入的“公钥”都是不可信的。我曾经在审计一个 IoT 芯片时发现,某厂商把公钥放在 Flash 头部让 BootROM 读取——这等于把保险柜钥匙挂在门上。

4.2 签名算法选型:RSA 还是 ECC?

BootROM 里能塞的算法有限,ROM 空间寸土寸金。主流选择就两个:

特性 RSA-2048 ECC-256 (secp256r1)
签名长度 256 字节 64 字节
验签速度 较慢(指数运算) 较快(标量乘法)
ROM 代码量 ~8 KB ~4 KB
抗量子攻击 弱 弱(但密钥更短)

我个人习惯选 ECC。原因很简单:省空间、省时间。BootROM 每多 1 KB 代码,芯片成本就涨一截。而且 ECC 的签名短,Flash 头部开销也小。

避坑指南:我曾经遇到一个项目,选了 RSA-4096,结果 BootROM 验签花了 800 毫秒。客户要求冷启动 500 毫秒以内。最后只能切到 ECC-384 才过关。所以选算法时一定要算时间预算。

4.3 抗回滚机制:为什么不能刷旧版本?

签名校验只能保证镜像“是谁签的”,不能保证镜像“是不是最新版”。攻击者可以拿一个有漏洞的旧版本镜像,重新签名(如果私钥泄露)或者直接利用未修复的漏洞。

抗回滚机制就是解决这个问题的。常见做法有两种:

  1. 版本号熔丝:每次升级把版本号写入 eFuse,BootROM 比较镜像版本号 ≥ 熔丝值才放行。
  2. 单调计数器:芯片内部维护一个只增不减的计数器,镜像头部带版本号,BootROM 比较。

你想想看,如果熔丝烧了版本 5,你想刷版本 3 的镜像,BootROM 直接拒绝。这就是抗回滚的本质。

⚠️ 注意:版本号熔丝是一次性编程的。如果生产时烧错了版本号,芯片就永远无法升级到更低版本。我见过一个团队在产线测试时烧了 0xFF,结果后续所有正式版本都无法部署——因为版本号不可能比 255 大。最后只能报废一批芯片。

4.4 调试接口锁定:JTAG/SWD 的安全管理

调试接口是开发者的好朋友,也是攻击者的后门。通过 JTAG 或 SWD,攻击者可以:

  • 读取 Flash 内容
  • 单步执行 BootROM 代码
  • 修改寄存器绕过校验

所以 BootROM 必须在启动早期就把调试接口锁死。锁定策略分三级:

等级 描述 适用场景
Level 0 完全开放,无限制 开发板、EVK
Level 1 需要密码/挑战应答 预量产、小批量
Level 2 永久锁定,不可恢复 量产设备

锁定时机也很讲究。我建议在 BootROM 完成签名校验后、跳转到下一级镜像前,立即锁定调试接口。为什么?因为校验通过说明镜像可信,此时锁定不会影响后续调试(如果镜像本身支持调试)。

实战经验:有一次客户反馈设备变砖,远程升级后无法启动。我查了日志,发现 BootROM 在锁定调试接口后,下一级镜像的校验失败了。但调试口已经锁死,无法通过 JTAG 读取任何信息。最后只能拆机用 SPI Flash 编程器读镜像——费了三天。所以我的建议是:调试接口锁定前,留一个“安全窗口”,比如等待 5 秒,期间可以通过特定 GPIO 电平阻止锁定。

4.5 三者的协同关系

签名校验、抗回滚、调试锁定不是孤立的。它们共同构成 BootROM 的安全三角:

  • 签名校验:保证镜像来源可信
  • 抗回滚:保证镜像版本最新
  • 调试锁定:保证攻击者无法旁路前两者

缺一个都不行。没有抗回滚,攻击者可以刷旧版利用已知漏洞。没有调试锁定,攻击者可以直接 halt CPU 修改校验结果。没有签名校验……那前面两个都是摆设。

我的习惯:在做安全启动方案评审时,我会先检查 BootROM 是否同时实现了这三条。少一条就直接打回。这不是苛刻,是吃过亏后的本能反应。

4.6 常见故障排查清单

如果你遇到 BootROM 启动失败,按这个顺序排查:

  1. 检查公钥是否匹配:熔丝值是否烧录正确?ROM 代码是否对应正确的公钥索引?
  2. 检查签名格式:签名长度、填充方式(PKCS#1 v1.5 还是 PSS)是否与 BootROM 一致?
  3. 检查版本号:镜像版本号是否 ≥ 熔丝中的版本号?熔丝是否被意外烧写?
  4. 检查调试接口状态:是否被意外锁定?锁定等级是否符合预期?
  5. 检查 Flash 头部布局:签名、版本号、镜像起始地址是否在正确偏移?

嗯,这五步走完,90% 的问题都能定位。剩下的 10%,往往是芯片硬件 bug 或者 BootROM 本身的设计缺陷——那种情况就只能找芯片原厂了。