拓冰建站拓冰建站
首页 / 资讯中心 / 正文

电压故障注入实战:突破现代树莓派安全启动

干硬件安全这行的人迟早会碰上一个绕不开的话题故障注入。这几年我拿现代树莓派Raspberry PiRPi做过不少安全研究Voltage Fault Injection电压故障注入是我个人认为最适合入门也最容易出成果的一个方向。因为现代RPi已经不再是那个“裸奔”的开发板了——安全启动、OTP熔丝、独立安全处理器全都有常规firmware改动、启动日志抓拍这些办法基本都不好使想要绕过去就得回到物理层面跟芯片硬碰硬。这篇文章我想完整讲一遍怎么用电压故障注入对一个现代树莓派单板计算机SBC做安全测试。主题就是给供电线上“砸”一个精确的毛刺让CPU在特定时刻算错一步从而绕过签名校验或安全启动的逻辑。这个过程听起来像玄学但只要你理解了目标、参数和时间窗口它其实是一套非常工程化的流程完全可以稳定复现。我会把环境搭建、参数扫描、串口检测、问题排查全部分享出来。适合正在做IoT安全研究、产品安全评估或者单纯想拿RPi练手的人看一眼看完至少能少踩我踩过的八成坑。1. 为什么是电压故障注入为什么是现代RPi1.1 故障注入到底在攻击什么故障注入不是什么新鲜的“黑客大招”本质是利用芯片在物理极限条件下会产生非预期行为的特性去干扰程序执行流。比如一个if判断条件成立才跳转执行关键代码如果我在CPU执行比较指令的瞬间让电压骤降几十纳秒比较单元可能出现“两位输出同时为1”的电路混乱导致条件被错误判断原本不该走的路径就这么走了过去。电压故障注入就是在供电引脚上人为制造一个极短的高精度电压跌落或毛刺让芯片在执行特定指令时“算错一次”。跟电磁故障注入EMFI、时钟毛刺Clock Glitch相比电压注入最大的优势有三个不需要把探头怼到芯片正面的精密位置只碰供电平面就行可重复性好只要电路稳定同一参数多次注入成功率很可观成本低一两百块钱的元件就能搭出一台能用的注入器。当然它也有明显的短板毛刺会顺着电源平面扩散影响的是整个核心域精度不如聚焦的EMFI加上现代SoC内部大量使用降压稳压器DVFS、LDO外部电压毛刺到了核心内部可能被“削掉”不少高频分量所以参数扫描范围必须覆盖得很宽。1.2 现代RPi为什么值得折腾“Modern RPi”我指的是Raspberry Pi 4B和5这两个平台它们已经配备了完整的启动链安全措施。树莓派的启动流程从BootROM开始BootROM只信任用密钥签名的固件下一级bootloader会校验固件签名如果校验失败就直接停在那里。这个特性让入侵变得非常难你不能随手换一个SD卡里的bootcode.bin就实现代码执行所有代码路径都被签名约束住了。这对攻击者来说是好事对安全研究者也是好事。意味着你有一个明确、可测的“攻击目标点”签名校验代码的某个时间窗口。只要在错误时刻注入一次毛刺让CPU跳过“校验失败”分支就有机会从SD卡或USB设备加载未签名固件拿到Next Stage代码执行。而且树莓派官方文档对BootROM、UART日志的公开程度相当好这省去了大量逆向工作板子上也能很方便地引出UART引脚观察注入前后的状态。还有人问为什么不直接烧EEPROM、改eFuse这种永久性改动需要专门的硬件工具而且改一次就烙在芯片里容易把板子搞废。电压故障注入是瞬时的、可重复的每一次“瞎试”的成本极低非常适合做启动链安全评估。2. 先想清楚目标你要攻击的是哪个环节2.1 从Reset到Kernel的整条信任链拿树莓派4B举例上电后大致经过这么几个阶段BootROM片上ROM不可改BootROM加载bootcode.bin从EEPROM或SD卡二级bootloader也就是EEPROM里的固件初始化DDR、加载start4.elfstart4.elf引导device tree、kernel安全启动模式下每一步的关键镜像都要用PKG密钥做RSA签名校验老版本树莓派的BootROM阶段比较多、链上可物理干扰的面也大但到了4B/5很多关键步骤已经被硬件安全模块接管软件层能动的空间很小。所以“硬件攻击者”真正能开枪的位置基本就在签名校验逻辑正在执行的那几百纳秒里。我一开始犯的错误是以为随便在启动的某个阶段打毛刺都行。实际不是。你要是不确定目标窗口可以先把UART接到板子上观察正常启动时每个阶段打印的耗时。比如启动日志从“Raspberry Pi Boot”到“Secure Boot: enabled”再到“DMA32”之类的提示往往就隔了几十到几百毫秒。这时候毛刺注入的时间范围就好框定了大概在BootROM加载bootcode.bin、二级bootloader计算签名的区间。2.2 判断成功条件什么算“打穿了”攻击成不成功不能靠猜。通常两类信号能判断UART日志出现异常比如签名失败的报错没有出现、直接跳到下一个阶段、出现未预期的内存dump。板子进入一个“非正常但可控”的状态比如start4.elf加载了带有明显未签名标识的custom镜像或者kernel启动后我们能拿到root shell且这个root shell是通过未签名固件进入的。我用的是更粗暴的方式准备一个自定义修改过的start4.elf或bootcode.bin只是加了一个可见的UART打印如果注入成功日志里会出现自定义打印。这样就不需要在内存里翻找“有没有执行到shell”靠一条日志就能判断注入点是否命中。另外RPi的secure boot模式开启之后UART默认打印量大减有些版本连启动细节都不显示。这时候可以把HDMI接上接一个摄像头对着屏幕或者用逻辑分析仪监控某个GPIO翻转来辅助判断。逻辑分析仪其实是最可靠的我后续的复现实验基本都用GPIO输出作为“成功标志”。2.3 为什么有时候需要重复上万次电压故障注入是概率性事件。即便你确定了时间窗口、电压宽度、偏移平均成功率也可能只有百分之几。所以工业界做这类测试时都不是搞一枪命中的而是“以量取胜”控制器比如ESP32或RP2040循环给RPi断电、上电在上电后的某个可配置延迟处注入毛刺每次注入前记录注入参数注入后等待UART输出如果UART没有输出期望结果则再循环。这个自动化循环很重要。手动按钮注入一两次玩玩行真要做参数扫描一晚上跑几十万次也不稀奇。我见过有些团队拿FPGA专门做故障注入生成器但RPi平台开发时用便宜的MCU就能起步完全够用。3. 动手搭建必需的硬件和关键参数原理3.1 硬件清单和连接方式下面是我现在一直在用的方案成本不高但效果稳定硬件用途备注树莓派4B或5被测试目标4B的供电点比5更好接ESP32开发板触发控制、自动复位与注入口也可以换成RP2040、STM32逻辑分析仪或USB串口模块采集UART日志建议买支持3.3V电平的MOSFET大功率开关如IRF540N或NCE6005产生电压毛刺导通电流、开关速度要足够直流电源5V/3A以上给RPi供电不要用普通手机充电器纹波太大采样电阻若干、电容若干滤波和电流检测一般用1Ω、10Ω、100µF组合连接方式是这样的ESP32通过GPIO控制MOSFET栅极MOSFET漏极接RPi的5V供电点或3.3V核心供电测试点源极接地。ESP32在RPi上电后延迟指定时间输出一个宽度可控的“低电平脉冲”到栅极MOSFET瞬间导通把电源对地短一下形成一个电压毛刺。有一点要特别注意直接短接电源到地短路电流很大MOSFET和供电网络发热非常快。所以占空比必须控制好毛刺宽度通常只有几百纳秒到几微秒不要长时间导通否则板子会烧。3.2 最关键的三个参数宽度、偏移、电压幅度参数才是电压故障注入的灵魂。就三个毛刺宽度、触发偏移、毛刺电压幅度。毛刺宽度Glitch Width从MOSFET导通到关断的时间。对于RPi这样跑在1.5GHz量级的SoC核心指令周期在几百皮秒到几纳秒。一个几十纳秒的毛刺可能影响几十个时钟周期属于“粗粒度”干扰几百纳秒的毛刺则可能直接导致CPU掉电重启。所以宽度是要从小往大扫的我常从10ns开始扫每个步进5ns或10ns一直到1µs。触发偏移Offset上电开始到毛刺注入之间的延迟。这个时间差要精确因为它决定毛刺落在启动链的哪一步。偏移扫描范围通常是启动总时长的前十分之一比如整体启动到kernel约2秒那偏移扫0~300ms就够了重点看BootROM到bootcode校验那一段。毛刺电压幅度Glitch Voltage这个定义要分清楚。对MOSFET开关短路到地的情况不是“给一个超压”而是“把电压拉低到一个中间值”。实践方法是给MOSFET源极串联一个可调的并联电阻或稳压管限制短路时的最低电压。如果直接短路到底毛刺就是一次深度掉电很多情况下不是最理想的状态很多时候只需要把3.3V拉到1.5~2.5V区间干扰逻辑判断但不至于复位成功率反而更高。给一个我当时扫描范围的参考表参数扫描范围步进说明Glitch Width10ns ~ 5µs5~50ns对数步进先粗扫命中后再细扫Offset0 ~ 500ms0.1ms根据UART日志估算Voltage Level0.8V ~ 2.8V0.1V用可调负载限制最低电压Injection Count每次参数组合跑2000~5000次—统计成功率3.3 为什么“毛刺宽度/偏移/电压”要一起扫单独扫某一个参数成功率往往低得可怜。因为故障注入本质是三维空间里的一个“故障成功区域”三个参数必须同时落在某个范围里才行。比如偏移对了宽度太短则毛刺不产生作用宽度对了偏移跑偏了则毛刺打在别的指令上宽度偏移都对了电压拉得太深直接复位板子重启当然也不会成功。所以我的做法是分阶段第一轮粗扫描锁定一个“成功率有明显凸起”的区间第二轮在凸起附近做三维细扫每次固定电压扫宽度×偏移二维矩阵第三轮把命中率最高的那个参数组合反复验证确认不是偶然。这里有个容易被忽略的参数触发基准。RPi上电后如果直接用“ESP32给RPi上电”作为时间零点它和BootROM开始执行之间的延迟抖动可能高达几十微秒。为了降低抖动我会从UART的TXD引脚抓一个“BootROM启动打印”信号作为触发基准这样从该信号到目标校验点的时间抖动就可以压缩到微秒级以内。实现上可以给ESP32加一个比较器电路检测UART引脚的起始位再延时指定的偏移。早期没有这个基准时成功率经常忽高忽低就是因为时间基准本身在抖。3.4 稳定供电是“能复现”的前提有句话我说了无数次故障注入实验室里最大的敌人不是芯片是电源纹波。如果供电本身就有一两百毫伏的纹波那你扫出来的“成功点”很可能只是纹波叠加后的巧合换个环境就完全失效。建议做三件事用线性稳压电源别用廉价开关电源。起码是实验室用的或高品质的5V/5A线性电源。在RPi供电入口并联足够大的滤波电容100µF钽电容10µF陶瓷电容0.1µF陶瓷电容组合但注意电容太大会吃掉毛刺所以要留一个专用注入点注入点附近不要放太大的滤波电容。所有测试线尽量短、粗接地回路要单点接地。我用的是铜箔胶带铺地尽可能降低回路电感。毛刺宽度到百纳秒级别时回路电感稍微大一点毛刺形状就畸形了成功率直接断崖。4. 完整实操流程跑一次自动化电压注入扫描4.1 第一步准备“可观测目标固件”我先准备了一个修改过的bootcode.bin树莓派4B用在里面加了一行串口输出“CUSTOM_PAYLOAD_LOADED”。因为现代RPi开启了secure boot默认不会加载所有未签名固件所以如果我的注入成功跳过了校验这个自定义bootcode就会被加载执行UART上就会打印这行内容。如果没有这个打印说明要么没注入成功要么镜像被卡在更早的校验阶段。把“观察点”做成一行固定字符串日志比纯靠“它能不能继续启动”判断方便太多因为“继续启动”可能是意外复位不一定就是故障注入导致的。4.2 第二步为ESP32写好注入脚本这里我放一个ESP32的简化伪代码实际工程里加上错误处理和日志统计#define TRIGGER_GPIO 25 // MOSFET栅极控制 #define RESET_GPIO 26 // 控制RPi复位信号 #define UART_RX 34 // 读取RPi UART TX struct glitch_param { long width_ns; long offset_us; float voltage; }; void inject_glitch(glitch_param p) { // 设置MOSFET栅极拉低电平模拟导通到地 gpio_set_level(TRIGGER_GPIO, 0); esp_rom_delay_us(p.offset_us); // 精确控制宽度这里用硬件定时器或esp_timer hw_timer_start(p.width_ns); } void scan_loop() { for (float v 0.8; v 2.8; v 0.1) { set_load_voltage(v); for (long off 0; off 500000; off 100) { for (long w 10; w 1000; w 10) { reset_rpi(); delay(50); // 等RPi稳定上电 inject_glitch({w, off, v}); wait_for_uart(500); // 预期500ms内出现目标日志 if (uart_buffer_contains(CUSTOM_PAYLOAD_LOADED)) { log_success(w, off, v); } } } } }不要一上来就跑完整三维扫描那样可能要跑几天。先用等宽、等电压的二维粗扫把范围往可能区域压然后再细扫。我通常的做法是先固定电压1.8V常用中间值扫offset×width圈出成功率最高的几个点固定这几个点里成功率最高的offset和width扫电压观察最低电压区间缩小电压范围再回扫一遍offset×width找到更精确的窗口。4.3 第三步用UART和逻辑分析仪判断命中UART的3.3V电平可以直接用逻辑分析仪采。我见过不少人在这一步翻车RPi的UART默认可能是关闭的需要在config.txt里开启uart有些版本把miniuart和PL011 UART搞混引脚接错就没输出。我建议先从确认正常启动日志开始接到BootROM打印后再跑攻击。看到一个稳定的“Raspberry Pi Boot”或类似输出说明串口通路是好的这时候再给测试注入器通电。千万别一上来就边注入边查串口出了问题分不清是注入的影响还是串口本身没接好。逻辑分析仪采样率至少25MHz否则几十纳秒宽的毛刺根本抓不到。我常用的做法是在MOSFET栅极和RPi VDD上都挂探头一个通道看注入时刻一个通道看电源电压跌落波形另一个通道看UART TX。这样每次攻击记录下来的不止“成功/失败”还有真实的毛刺波形做复现时能快速排查是不是波形畸变了。4.4 第四步从“一次成功”到“稳定复现”第一次扫出成功点后别高兴太早故障注入的成功率不是100%。通常要连续注入20~50次看成功率是否稳定在10%以上才算真正找到了有效的参数点。我在RPi上最常遇到的情况是扫到一个点连续成功三次然后在同一参数下突然连续失败50次。这种“伪命中”多半是参数选在故障区域的边缘抖动稍大就掉出去了。解决办法是往回找成功率曲线上的“平台区”而不是峰值点。峰值看着高但周围梯度大稍微偏移就失败平台区成功率可能只有20%但很宽抗抖动能力强更适合出成果和写论文。成功复现以后我建议把每个成功参数的完整环境记录写清楚包括RPi型号、固件版本、eFuse状态重定时间boot code version电源型号和输出电压MOS管型号、栅极驱动电阻注入点位置、外部电容值环境温度影响不大但影响复现最好记录。这些细节能让你三个月后看到成果的时候还知道自己到底做了什么。5. 常见问题与排查技巧实录5.1 问题速查表我在跑故障注入时踩过不少坑下面这些比较典型给一张速查表供参考现象常见原因排查办法注入后板子总重启但没有目标日志毛刺深度过大触发复位降低毛刺深度调高限制电压缩窄毛刺宽度某组参数之前命中后来再也不命中供电或环境变化检查电源是否漂移MOSFET是否损坏重新校准触发基准UART完全无输出串口没开或引脚接错先跑一次正常启动确认UART链路检查config.txt成功率忽高忽低触发基准抖动大用UART起始位做触发不要用外部上电信号特定偏移区间成功率凸起但立刻消失可能是目标窗口极窄把该区间步进调到10µs配合多电压细扫毛刺波形严重失真回路电感和电容效应缩短测试线、单点接地、在注入点附近用尽量小的电容注入器发热严重MOS管导通时间过长降低单次注入宽度调大循环间隔5.2 怎么排查“成功率莫名下降”如果你有一个参数点昨天还好好的今天怎么试都不出来最常见的凶手就是供电。电源的热漂移或负载变化会让实际电压偏离0.1V以上而故障注入的成功区对电压非常敏感可能就因为这0.1V整个窗口消失了。所以我每次长时间扫描前都会做一次“参考点测试”固定一组已知成功率高的参数连续注入50次确认成功率在正常范围再开始扫。如果这个参考点都过了那扫描环境就是好的如果没过先别急着扫去找环境原因。另一个隐蔽的原因MOSFET的栅极驱动波形劣化。ESP32的GPIO驱动能力有限如果MOSFET栅极电容比较大上升沿可能拖几微秒导致实际毛刺形状和预设值差很远。解决方法是在栅极加一个专门的门极驱动芯片如TC4420或者换一个Qg更小的MOSFET。5.3 判断“真的绕过校验”还是“只是运气好”在secure boot开启的情况下如果你注入成功后直接跳过了校验那个自定义bootcode或elf会被加载UART会看到你的标记。但如果只是把CPU打进了异常处理状态它也可能继续往下执行一些指令有时候也能打印出一些奇怪的日志容易被误判为成功。我的判断标准是成功标志必须明确且一致比如每次看到同一串“CUSTOM_PAYLOAD_LOADED”而不是随机乱码必须能连续复现至少三次独立注入中成功两次成功时要有可观察的波形逻辑分析仪上能看到毛刺出现在目标窗口有条件的话验证一下注入成功后系统是否真的执行了自定义代码而不是只打印了一行随机字符。如果不满足这些我只能认为“不知道发生了什么”而不会写进结论。6. 从结果反推防护思路6.1 现代系统怎么防电压故障注入你拿RPi做攻击实验看完了成功点其实反过来也是对固件设计的一种压力测试。现代SoC应对故障注入的思路主要有这几层硬件层加固供电监控器检测电压跌落一旦低于阈值立即复位缩小攻击窗口。缺点是窗口再怎么缩也不是零只要毛刺落在那段区间内还有机会。软件层冗余校验关键判断不只看一次而是多次比对。比如签名校验失败分支被跳过一次流程里还会在别处再校验一次哈希或RSA签名。增加一次校验攻击者就要在极短时间窗口内连续打中两个漏洞难度指数级上升。时间随机化启动链每隔一段时间加一个随机延迟让攻击者无法稳定预测目标指令的执行时刻。对固定偏移的扫描来说这非常克制。多源信号交叉验证比如安全子系统独立监控主CPU状态一旦发现主CPU存在未授权跳转直接拉高复位信号。错误计数器检测到异常复位或异常跳转时把计数器累加超过阈值就进入锁定或擦除密钥状态。这样想靠“百万次扫描”碰运气也变得不现实。树莓派这套Secure Boot虽然还不是最顶级的硬件安全方案但已经明显比以前好打很多了我周围不少研究团队都在上面做过类似的测试。随着SBC这类单板机在边缘设备里的地位越来越高它们被针对的频率也会明显上升这就是大家常说的“SBC暴雷”背后的现实不是SBC本身不安全而是过去没人认真打它现在开始被关注了。6.2 对我们自己的项目有什么启发如果你正在做产品固件或硬件设计我建议至少做三件事在启动链的每一个关键校验点设计至少二重校验而且校验之间的代码路径不要紧挨着隔开一段再验证一次启用硬件随机延迟如果芯片支持打散固定的启动时间线把“多次异常复位”视为安全事件触发密钥销毁或进入恢复模式而不是简单重启。从攻击者的角度看一次成功的电压故障注入往往只需要找到一处薄弱校验从防御者角度看你要做的不是让攻击变得不可能而是让攻击成本高到对方不愿意尝试。多一层校验、多一次延迟、多一个监控都可能让攻击成功率从几十分之一降到几十万分之一。6.3 最后再分享一点实操体会这套流程我在树莓派4B和5上都跑通了最大的感受故障注入的成功率虽然低但它是“可测量、可控制、可复现”的远没有很多文章写得那么玄。关键就是把每个参数当成科学实验变量去对待一次只改一个变量并完整记录结果。另外一个小技巧如果你和我一样不是每次都能拿到贵的商用故障注入设备先用ESP32MOS管搭建的方案足够起步。等你在RPi上跑通了整套扫描逻辑再去玩更复杂的EMFI、激光注入那只是换了一个“激励源”而已思路完全是相通的。硬件安全研究的乐趣恰恰在于它逼着你去理解芯片底层的执行细节而不是在软件层面堆工具。电压故障注入只是一个入口但它能让你对“信任边界”有完全不一样的理解。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门