4. 时钟毛刺注入:时钟毛刺原理、时钟抖动与毛刺、利用时钟毛刺绕过安全启动

时钟,是数字系统的“心跳”。

每个寄存器、每个状态机,都依赖这个稳定的节拍来同步运作。但你想过没有——如果这个心跳突然乱跳一下,系统会怎样?

这就是时钟毛刺注入要干的事。说白了,就是在极短的时间内,给芯片的时钟信号里塞进一个“不该出现的脉冲”。这个脉冲可能让寄存器采到错误的数据,让状态机跳到不该去的状态,甚至让安全启动的校验逻辑直接“跳过”。

我最早接触这个技术,是在评估一款车规级 MCU 的安全启动方案时。当时客户说他们的芯片有“硬件安全模块保护”,结果我用一个简单的时钟毛刺发生器,在启动过程中打了三个毛刺……嗯,芯片直接跳过了签名校验,跑起了我自己的代码。从那以后,我再也不敢小看这个“小脉冲”了。

核心观点: 时钟毛刺注入不是破坏芯片,而是利用芯片在时序边界上的“脆弱性”。它不改变代码,不修改数据,只改变“时间”。

4.1 时钟毛刺原理

先看一张图,理解毛刺到底长什么样。

时钟毛刺注入原理示意 正常时钟 毛刺时钟 毛刺 关键说明: • 正常时钟:占空比 50%,周期稳定,边沿清晰 • 毛刺时钟:在正常时钟边沿之间插入一个额外脉冲(宽度通常为 1-5 ns) • 毛刺位置:可以精确控制到目标指令执行的时钟周期 • 毛刺宽度:太宽会直接复位芯片,太窄则无效,需要扫描

你看到上面那个红色波形了吗?在第三个时钟周期里,多了一个窄脉冲。这就是毛刺。

它的宽度通常只有正常时钟周期的 1/10 到 1/5。比如你的芯片跑 100 MHz,周期是 10 ns,毛刺宽度可能只有 1-3 ns。这个窄脉冲,刚好能让寄存器进入“亚稳态”——既不是 0,也不是 1,而是介于两者之间的不确定状态。

我的经验: 毛刺宽度不是越窄越好。太窄了,寄存器根本反应不过来;太宽了,直接触发复位。我一般从正常周期的 1/8 开始扫,逐步加宽,直到看到效果。

4.2 时钟抖动与毛刺

很多人会把时钟抖动和时钟毛刺搞混。我刚开始学的时候也犯过这个错。

时钟抖动,是时钟边沿在时间轴上的随机偏移。它是物理世界的噪声导致的——电源纹波、热噪声、衬底耦合。抖动是随机的,你没法精确控制它什么时候发生。

时钟毛刺,是人为注入的确定性脉冲。它的位置、宽度、幅度都是可控的。我们做故障注入,要的就是这种“可控的不确定性”。

特性 时钟抖动 时钟毛刺
来源 物理噪声(电源、热、衬底) 人为注入(毛刺发生器)
可控性 不可控,随机发生 完全可控(位置、宽度、幅度)
时间精度 皮秒级随机偏移 纳秒级精确注入
对系统影响 时序裕量降低,偶尔出错 确定性故障,可绕过安全逻辑
安全设计应对 时序冗余、时钟树优化 毛刺检测电路、双沿采样

你想想看,如果芯片设计时只考虑了随机抖动,留了 20% 的时序裕量,那我的毛刺只要比这个裕量更窄,就能让寄存器采到错误数据。这就是攻击的本质——利用设计者留下的“安全余量”。

注意: 有些芯片会内置时钟毛刺检测电路。如果检测到时钟周期异常(比如周期突然缩短),会触发复位或报警。但这类检测通常只覆盖“周期异常”,不覆盖“边沿位置异常”。我见过不少芯片,毛刺检测只检查上升沿间隔,不检查下降沿——这就是突破口。

4.3 利用时钟毛刺绕过安全启动

安全启动,是嵌入式系统的第一道防线。它的流程大致是这样的:

  1. BootROM 加载:芯片上电后,BootROM 先运行。
  2. 校验第一级 Bootloader:用公钥验证签名。
  3. 加载并校验下一级:逐级验证,直到操作系统。
  4. 执行:所有校验通过,系统启动。

这个链条里,只要有一级校验被绕过,整个安全体系就崩了。时钟毛刺的目标,就是让“校验通过”这个判断出错。

具体怎么做?我拆解一下:

  • 定位校验点:先找到签名校验函数在 BootROM 中的执行位置。这通常需要反汇编 BootROM,找到那个“比较”指令。
  • 确定注入时机:在比较指令执行的那个时钟周期,注入毛刺。让比较结果从“不相等”变成“相等”。
  • 扫描参数:毛刺宽度、注入位置、电压配合。这三个参数需要组合扫描。
  • 验证效果:如果芯片加载了未签名的固件却没有报错,说明绕过成功。
实战案例: 我曾经测试过一款基于 Cortex-M4 的物联网芯片。它的安全启动流程中,有一个“if (signature_valid == 0) { jump_to_error_handler(); }”的判断。我在“signature_valid”寄存器被写入结果的那个时钟周期,注入了一个 2.3 ns 的毛刺。结果:寄存器写入了 0x01(有效),而不是 0x00(无效)。芯片愉快地跑起了我的恶意固件。

为什么会这样?因为毛刺让寄存器在建立时间(setup time)窗口内采到了不稳定的数据。数据总线上本来应该是“0”,但毛刺导致寄存器采到了“1”。这就是亚稳态的威力。

4.4 攻击流程与关键参数

下面这张图,是我在实际项目中总结的攻击流程。你可以把它当成一个“操作手册”。

时钟毛刺注入攻击流程 1. 目标分析 2. 定位校验点 3. 参数扫描 4. 注入 5. 验证是否绕过 6. 调整参数 7. 重复扫描直到成功 关键参数: 毛刺宽度(1-5 ns) | 注入位置(相对目标指令的时钟周期偏移) | 电压(降低电压可缩小时序裕量)

参数扫描是最耗时的环节。我一般会写一个自动化脚本,让毛刺发生器自动扫描三个参数:

// 伪代码:参数扫描逻辑
for (glitch_width = 1.0; glitch_width <= 5.0; glitch_width += 0.2) {
    for (glitch_offset = 0; glitch_offset <= 100; glitch_offset += 1) {
        for (vcore = 0.9; vcore <= 1.2; vcore += 0.05) {
            set_core_voltage(vcore);
            trigger_glitch(glitch_width, glitch_offset);
            if (check_bypass_success()) {
                log_success(glitch_width, glitch_offset, vcore);
                break;
            }
            reset_target();
        }
    }
}

这个脚本跑一轮,可能需要几个小时。但一旦找到那组“黄金参数”,后面每次注入都能稳定绕过。

避坑指南: 我曾经遇到过一个问题——参数扫描时芯片频繁复位,导致扫描进度极慢。后来发现是毛刺宽度太大,触发了芯片的电源管理单元(PMU)的欠压复位。解决办法:把毛刺宽度上限从 5 ns 降到 3 ns,同时把核心电压降低 0.1 V,让芯片更“脆弱”一些。

4.5 防御思路

讲完了攻击,咱们也得聊聊怎么防。毕竟你可能是做安全设计的工程师,不是黑客。

针对时钟毛刺注入,常见的防御手段有这些:

  • 毛刺检测电路:监控时钟周期,如果检测到周期异常缩短,立即触发复位。但要注意——检测阈值要留够裕量,否则正常抖动也会误触发。
  • 双沿采样:关键寄存器同时用上升沿和下降沿采样,然后比较结果。如果两次采样不一致,说明可能受到了毛刺干扰。
  • 时序冗余:在关键路径上增加额外的时序裕量,让毛刺无法突破建立时间窗口。但代价是性能下降。
  • 冗余校验:签名校验执行两次,如果两次结果不一致,拒绝启动。我见过一个芯片,校验三次,三取二通过——这招对毛刺很有效。
  • 电压-频率联动监控:如果检测到电压下降,同时降低时钟频率,缩小毛刺的“攻击窗口”。
重要提醒: 没有绝对安全的防御。毛刺检测电路本身也可能被毛刺绕过——如果毛刺同时干扰了检测电路和校验逻辑,那检测电路就形同虚设。所以,我建议做“纵深防御”:检测 + 冗余 + 随机化,多层叠加。

好了,这一章的内容就到这里。时钟毛刺注入,说难不难,说简单也不简单。关键就三点:理解时序边界、精确控制参数、耐心扫描。你如果手头有开发板,可以自己搭个毛刺发生器试试——相信我,亲手绕过一次安全启动,比看十遍文档都管用。