3. 电压毛刺注入:原理、参数与实战

电压毛刺注入,说白了就是给芯片的供电线上「使绊子」。正常工作时,芯片的电压是平稳的。我们突然插入一个极窄的电压跌落或过冲,让芯片内部某个逻辑门在关键瞬间判断出错。

这个错误可能很小——比如一个寄存器没锁住数据。但就是这一下,可能绕过密码校验、跳过安全启动的某个检查点。我在项目中见过太多「固若金汤」的软件防护,被一个精心定位的毛刺直接击穿。

3.1 电压毛刺的核心原理

芯片内部的数字逻辑靠时钟沿采样数据。每个时钟周期里,组合逻辑需要时间稳定下来。如果供电电压在建立时间(setup time)窗口内突然跌落,晶体管的驱动能力会下降,信号传播延迟变长。

结果就是:数据还没传到,时钟沿就来了。寄存器采到了错误值,或者根本没采到。

关键洞察: 毛刺攻击不是破坏芯片,而是利用芯片在电压异常时的「非标准行为」。芯片不会烧毁,只是逻辑出错——这正是我们想要的。

我习惯把毛刺攻击分为两类:

  • 瞬态故障注入: 毛刺宽度在纳秒级,只影响一个或几个时钟周期。用于绕过单条指令,比如跳转检查。
  • 持续扰动注入: 毛刺宽度覆盖多个周期,影响一段连续逻辑。用于破坏循环计数或状态机。

实际项目中,90% 的场景用瞬态注入就够了。你想想看,安全芯片的防护再强,最终也要靠一条「比较指令」决定是否放行。我们只需要让那条指令的结果出错。

3.2 毛刺的三个核心参数

毛刺不是随便插一下就行的。三个参数决定了成败:宽度、强度、偏移。我刚开始做毛刺测试时,调这三个参数调了整整两周。

参数 英文 单位 作用
宽度 Glitch Width ns(纳秒) 毛刺持续的时间长度
强度 Glitch Intensity % 或 mV 电压跌落的深度或过冲幅度
偏移 Glitch Offset ns 或 时钟周期数 毛刺相对于触发事件的延迟

3.2.1 宽度(Width)

宽度决定了毛刺影响多少个逻辑门级。太窄,芯片内部的去耦电容直接滤掉了,毛刺根本传不到核心逻辑。太宽,芯片直接复位或进入欠压锁定(BOD),攻击失败。

我个人的经验是:从目标芯片主时钟周期的 1/4 开始试。比如芯片跑 16 MHz,周期约 62.5 ns,那就从 15 ns 宽度起步。向上逐步增加,直到观察到异常行为。

实战技巧: 用示波器先抓一下芯片 VDD 引脚上的实际噪声底。如果电源本身就有 10 ns 的纹波,你的毛刺宽度至少要比这个宽 2-3 倍才能生效。

3.2.2 强度(Intensity)

强度是毛刺的「杀伤力」。通常用正常电压的百分比表示。比如 3.3V 供电,10% 强度就是跌落 0.33V,实际电压到 2.97V。

强度太低,逻辑门还能正常工作。强度太高,芯片直接掉电复位。我见过有人一上来就设 50% 强度,结果芯片反复复位,连通信都建立不起来。

安全芯片通常有电压检测电路(BOD),低于某个阈值就强制复位。所以强度必须控制在 BOD 阈值之上、但又能让逻辑出错的范围。这个窗口通常只有 5%-15%。

3.2.3 偏移(Offset)

偏移是最难调的参数。它决定了毛刺在时间轴上的位置。同一个宽度和强度,偏移差 5 ns,结果可能从「绕过校验」变成「系统崩溃」。

为什么会这样?因为芯片内部不同指令、不同数据路径的时序窗口不一样。你需要在目标指令执行的那个时钟周期内,精确地把毛刺打进去。

我记得有一次调试一个 ARM Cortex-M0 的 bootloader 绕过,偏移从 0 扫到 1000 ns,只在 372-378 ns 这个 6 ns 的窗口内攻击成功。前后差一点,结果完全不同。

3.3 使用 ChipWhisperer 进行电压毛刺攻击

ChipWhisperer 是目前做电压毛刺最顺手的工具,没有之一。它把毛刺生成、触发控制、目标通信集成在一个平台上。我这些年做过的故障注入项目,八成以上都用它。

3.3.1 硬件连接

ChipWhisperer 的电压毛刺通过一个 MOSFET 开关电路实现。正常供电走旁路,毛刺触发时 MOSFET 瞬间关断,让目标芯片的 VDD 跌落。

标准连接方式:

  1. ChipWhisperer 的 VOUT 接目标板 VDD
  2. ChipWhisperer 的 GND 接目标板 GND(共地必须可靠)
  3. 目标板上的原供电断开,由 ChipWhisperer 供电
  4. 触发信号线(Trigger)接目标板的某个 GPIO 或串口 TX

警告: 共地不良是毛刺攻击失败的头号原因。我曾经花了两天排查一个「毛刺打不进去」的问题,最后发现是杜邦线接触电阻太大。建议用粗短的多股线,直接焊在目标板的电源入口。

3.3.2 软件配置

ChipWhisperer 的 Python 库提供了完整的毛刺控制接口。下面是我常用的配置模板:

import chipwhisperer as cw

# 初始化 ChipWhisperer
scope = cw.scope()
scope.default_setup()

# 配置毛刺参数
scope.glitch.clk_src = "clkgen"      # 毛刺时钟源
scope.glitch.output = "clock_xor"    # 毛刺与时钟异或输出
scope.glitch.trigger_src = "ext"     # 外部触发

# 设置毛刺宽度(以时钟周期为单位)
scope.glitch.width = 1               # 1 个时钟周期宽度

# 设置毛刺偏移(以时钟周期为单位)
scope.glitch.offset = 45             # 触发后第 45 个周期插入毛刺

# 设置毛刺强度(百分比)
scope.glitch.ext_offset = 0          # 额外偏移(ns 级微调)
scope.glitch.repeat = 10             # 重复次数

# 使能毛刺
scope.glitch.manual_trigger()

这段代码的核心逻辑是:等待外部触发信号,然后在第 45 个时钟周期插入一个宽度为 1 周期的毛刺。强度通过硬件电位器或 DAC 控制,不在代码里直接设。

3.3.3 参数扫描策略

毛刺攻击不是一次就成的。你需要系统地扫描参数空间。我总结了一个三步走的策略:

  • 粗扫: 固定强度为 10%,偏移从 0 到 1000 ns,步进 50 ns。宽度从 10 ns 到 100 ns,步进 10 ns。记录所有导致异常(复位、卡死、输出错误)的参数组合。
  • 精扫: 在粗扫发现的异常区域,缩小步进到 1-2 ns,宽度步进到 1-2 ns。找到能稳定复现「安全绕过」的参数。
  • 验证: 用找到的参数重复攻击 100 次以上,统计成功率。成功率低于 10% 的参数在实际攻击中基本不可用。

我的经验: 成功率 30% 以上的参数组合才是好参数。低于这个数,说明毛刺刚好打在时序窗口边缘,稍微有点温度变化或电压波动就失效了。

3.4 电压毛刺攻击的知识体系

下面这张图梳理了电压毛刺注入的核心脉络,从原理到参数再到工具实战,一目了然。

电压毛刺注入知识体系 核心原理 三个核心参数 ChipWhisperer 实战 建立时间违规 逻辑门延迟变化 寄存器采样错误 宽度 强度 偏移 宽度:影响逻辑门级数 强度:电压跌落深度 偏移:时间轴上的位置 硬件连接 软件配置 参数扫描 攻击流程:触发 → 偏移扫描 → 宽度扫描 → 强度扫描 → 成功率验证 实战要点 • 共地必须可靠,建议焊接电源线 • 粗扫步进 50 ns,精扫步进 1-2 ns • 成功率低于 10% 的参数不可用 • 温度变化会影响毛刺窗口位置

3.5 常见问题与避坑

做电压毛刺攻击,有几个坑我几乎每次培训都会提到。都是我自己踩过的。

避坑 1: 我曾经以为毛刺宽度越小越好,结果调了三天没效果。后来发现目标芯片的电源滤波电容太大,窄毛刺根本传不进去。解决办法:在目标板 VDD 引脚附近并联一个 100 nF 的电容,反而让毛刺更容易耦合进去。

避坑 2: 偏移参数不要只扫整数周期。有些芯片的内部时钟有相位偏移,整数周期位置可能正好是时序裕量最大的地方。我习惯在整数周期前后各扫 5 ns,经常能发现更脆弱的窗口。

小技巧: 如果目标芯片有多个供电引脚,可以只对核心逻辑供电(如 VDD_CORE)注入毛刺,I/O 供电保持稳定。这样芯片不会因为 I/O 掉电而通信中断,攻击成功率会高很多。

电压毛刺注入是故障注入攻击的「基本功」。参数不多,但每个参数背后都是对芯片时序行为的深刻理解。调参的过程,其实就是和芯片设计者「博弈」的过程——他在时序约束里留了多少裕量,我们就在那个裕量里找突破口。