ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ChipWhisperer-Husky侧信道分析与故障注入平台构建实战

ChipWhisperer-Husky侧信道分析与故障注入平台构建实战 我这些年做嵌入式安全评估侧信道分析SCA和故障注入FI这两块基本占了工作的大半。ChipWhisperer 系列从早期的 CW-Lite、CW-Pro 一路用过来说实话每一代都有明显的取舍直到 ChipWhisperer-Husky 出现才感觉有一台能把高速采集、故障注入、灵活触发和脚本化控制整合得比较舒服的设备。这篇就完整记录一下我这次基于 Husky 构建侧信道分析平台的全过程包括硬件链路怎么理解、固件怎么烧、软件环境怎么搭、第一次采集怎么做以及后来在 CPA 和故障注入实验里踩过的那些坑。如果你正准备上手 Husky或者正在犹豫要不要从旧平台迁移这篇文章应该能帮你省不少时间。1. 构建目标与整体设计思路1.1 这套平台到底解决什么问题先聊清楚一个前提Husky 不是一块普通的开发板它是用来做芯片安全测试的专业设备。主要干三件事功耗侧信道采集以极高的采样率抓取目标芯片运行加密算法时的功耗波动然后通过差分功耗分析DPA、相关功耗分析CPA等手段恢复密钥。电磁辐射采集配合近场探头采集芯片电磁泄漏信号原理与功耗分析相同但物理形态更灵活。故障注入在目标芯片运行到敏感指令时精确插入电压毛刺让芯片计算出现错误再结合错误输出反推密钥或绕过安全校验。传统方案里这三件事往往需要三套不同的设备示波器负责采集、信号发生器负责触发、电压毛刺器负责故障注入连起来一堆线不说时序同步还容易出问题。Husky 的思路是把它们整合到一套软硬件体系里通过 Python API 统一控制实验重复性好了很多。1.2 为什么选 Husky 而不是其他方案我之前的日常组合是 CW-Pro 加一台高速示波器。CW-Pro 的集成度不错但采样率只有 240 MS/s处理一些高频时钟的目标芯片时功耗轨迹的细节明显不够。如果换用通用示波器采样率确实上去了但捕获深度、触发灵活性、与 ChipWhisperer 软件生态的配合又是个问题。Husky 的定位正好补上了这个中间地带。它把采样率拉到了 1 GS/s相比 CW-Pro 提升了四倍多同时保留了 ChipWhisperer 系列一贯的开源软件栈和模块化目标板设计。对于 AES-128、RSA、ECC 这类常见算法以及大部分 8 位、32 位 MCU 和 FPGA 原型这套平台的采集带宽和存储深度都够用。1.3 构建层级硬件、固件、软件、实验这次构建我分成四个层面来做顺序不能乱硬件层确认板卡版本、电源、目标板接口、时钟与触发连接。固件层编译并烧录 FPGA bitstream 和 bootloader让板卡被主机识别。软件层安装 Python 环境、ChipWhisperer 库、Jupyter 笔记本跑通cw.husky的 API 调用。实验层接上目标板完成功耗轨迹采集、CPA 攻击并扩展故障注入实验。每一层都有独立的验证点。我习惯的做法是固件烧完先跑一遍自检脚本软件装完再跑一个小例子确认链路最后才上真实目标板。这样出问题的时候能快速定位是硬件、固件还是软件的问题。2. 硬件平台与核心链路解析2.1 高速采样链路1 GS/s ADCHusky 最核心的升级就是这条采样链路。它用的 ADC 支持 1 GS/s 采样率10-bit 分辨率这意味着你可以在 1 ns 的时间间隔上连续观察目标芯片的功耗变化。很多人不理解 1 GS/s 到底意味着什么。打个比方如果目标芯片跑在 100 MHz 主频那么一个时钟周期是 10 ns1 GS/s 的采样率相当于在一个时钟周期里采集 10 个点。对于 CPA 这类依赖“功耗模型与真实轨迹相关性”的分析方法单个时钟周期内的采样点越多你越能捕捉到数据渡越瞬间的功耗尖峰攻击成功率也越高。如果你之前用 240 MS/s 的设备升级到 1 GS/s 后最直观的感受是同一个 AES 加密过程采集到的轨迹细节丰富了很多尤其是 S-box 查表操作附近那几条特征明显的尖峰肉眼都能看出差异来。2.2 FPGA 与存储架构采样链路后面是 FPGAHusky 使用的是 Xilinx Artix-7 系列。FPGA 在这里承担的任务很多控制 ADC 采样、管理触发逻辑、缓存轨迹数据、与 USB 控制器交互同时还要生成故障注入的精确毛刺信号。在构建过程中FPGA 固件是通过 VHDL/Verilog 编译得到 bitstream再通过 USB 下载到板卡。如果你只使用现成固件可以跳过编译细节但如果你跟我一样想修改触发条件或者自定义采样模式那熟悉一下源码结构和 Vivado 的编译流程是必需的。存储方面Husky 板载的缓存空间允许你连续捕获大量轨迹。实际操作中我一次抓 10 万条 AES 功耗轨迹是常态如果每一条轨迹包含 5000 个采样点、每个点 2 字节那么单条轨迹就是 10 KB10 万条就是 1 GB 左右的数据量。这个量级如果靠 USB 2.0 传会等得让人抓狂Husky 支持 USB 3.0实测传输速度稳定在一个比较理想的范围。2.3 故障注入通道与触发同步故障注入是 Husky 的另一大卖点。它能在精确的时间点产生电压毛刺并输出到目标板的电源引脚使目标芯片执行出错。这里最关键的是“精确”两个字。故障注入要生效毛刺必须打在目标指令执行的特定窗口内提前或滞后几个时钟周期都可能无效。Husky 的 FPGA 负责生成与采样时钟同步的毛刺信号所以你可以通过 API 设置毛刺的起始位置、宽度和极性与目标时钟对齐。我在实操中常用的方式是先用功耗采集模式记录目标芯片执行加密的完整轨迹找到写返回结果的那个操作点然后以这个点为参考设置故障注入的偏移再逐渐扫描偏移范围寻找故障窗口。Husky 的触发同步机制保证了采集和注入都是在同一个时钟域里完成这就比用外置信号源做故障注入要稳定得多。2.4 电源与接口布局再聊聊板卡本身的物理接口。Husky 的布局大致是USB 3.0 接口连接主机负责数据传输和供电。20-pin 目标板接口连接 ChipWhisperer 系列目标板包含电源、时钟、触发、IO 和 ADC 输入信号。SMA 连接器用于外部时钟输入/输出、触发输入/输出。可编程电源输出为目标板提供可调电压支持电压毛刺注入。构建时要注意供电问题。Husky 的功耗比前代更高尤其是采样率和故障注入同时开启时。建议使用原装电源适配器或者至少是 5V/2A 以上的 USB 供电同时尽量避免用笔记本前置 USB 口供电容易因为电压不稳导致目标板复位。3. 固件烧录与软件环境搭建3.1 搭建固件编译环境ChipWhisperer-Husky 的 FPGA 固件源码在官方的 hardware 仓库里编译需要 Vivado。我用的版本是 2021.2 和 2022.1实测都能正常编译出目标 bitstream。如果只是日常实验和攻击测试直接用官方预编译固件就够了不一定非要从源码重编。但如果你做二次开发比如自定义 FPGA 逻辑那就需要有下面这些准备安装 Vivado并保证许可证包含目标 FPGA 型号。克隆newaetech/chipwhisperer仓库查看hardware/capture/chipwhisperer-husky目录下的工程文件。准备一个 JTAG 调试器Husky 板上有 JTAG 接口连接起来方便排查。首次编译完整工程会比较久我印象里跑完综合、布局布线差不多要三十分钟到一个小时具体取决于机器性能。建议第一次编译时不要动不动改代码先把原版工程完整跑通一遍确认工具链没问题再动逻辑。3.2 Bootloader 与 FPGA 固件烧录Husky 板卡的固件分两层一层是 USB bootloader负责最基础的枚举和固件升级另一层是 FPGA bitstream通过 bootloader 写入。正常使用的话官方发布新固件时你用cw库里的更新脚本就能完成升级不需要额外操作。但我这次是从一个较早版本升级到最新固件踩了一个坑bootloader 版本太老怎么都无法进入升级模式。解决办法是手动进入 DFU 模式按住板卡上的升级按键同时插入 USB 线然后运行官方脚本重新烧写 bootloader再烧写 FPGA 固件。这个过程需要 Linux 环境下安装dfu-util工具Windows 上也可以用但驱动会麻烦一些。烧写完成后重新插拔 USB 线用lsusb或设备管理器确认设备识别为 ChipWhisperer 设备这一步就算过了。3.3 Python 环境与 ChipWhisperer 库安装软件层推荐用 Python 3.9 以上版本然后通过 pip 安装 chipwhisperer 库pip install chipwhisperer这里有两个注意点。第一建议在虚拟环境里装避免和系统 Python 包冲突。第二如果要用 Jupyter 做交互实验建议也把jupyterlab装好。ChipWhisperer 官方文档里的示例好多都是.ipynb格式Jupyter 环境下运行很方便。安装完成后可以通过下面的代码确认库能否正常识别 Huskyimport chipwhisperer as cw scope cw.scope() scope.default_setup() print(scope)如果打印出设备的型号、序列号、固件版本等信息说明软件环境已经通了。如果报错找不到设备优先检查 USB 线是不是数据线有些线只能充电以及驱动和权限问题。Linux 下如果权限不足需要添加 udev 规则。3.4 硬件自检跑通第一个连接软件环境配好之后我建议先跑一个最简单、不需要外接目标板的自检脚本import chipwhisperer as cw scope cw.scope() scope.default_setup() scope.clock.adc_src clkgen scope.clock.adc_freq 10e6 scope.adc.timeout 0.1 trace scope.capture_trace() print(len(trace))这个脚本的作用是让 FPGA 内部时钟直接驱动 ADC不接任何目标板直接采集一段空轨迹。如果能返回一个数组说明 ADC、存储和 USB 传输链路都是通的。我第一次跑的时候遇到timeout报错排查下来是旧固件 bug升级固件后就正常了。4. 首次功耗采集与目标板实验4.1 接线与目标板选择ChipWhisperer 系列支持多种目标板最常用的是 CW308 UFO 底板加不同的 CPU 子卡以及集成的 XMEGA 目标板。我这里用了 CW308 STM32F3 子卡跑一个软件实现的 AES-128。接线要确认这几条目标板正确地插在 20-pin 接口上方向不要反。目标板电压选择正确。CW308 上有跳线或拨码开关STM32F3 是 3.3V 供电如果误设成 5V芯片直接就烧了。如果有外部触发需求SMA 线接到对应的触发接口。打开设备电源前最好用万用表量一下供电引脚确认电压正常这一步能避免很多后面才会暴露出来的灵异问题。4.2 采集功耗轨迹的完整流程接下来是完整的采集流程。以下是我整理好的一个标准脚本跑过一次之后后面的实验基本就围绕这个框架做修改import chipwhisperer as cw import numpy as np scope cw.scope() scope.default_setup() # 目标板型号与固件 target cw.target(scope, cw.targets.SimpleSerial) fw_path stm32f3_aes_simple_serial.hex target.program(fw_path) # 时钟配置 scope.clock.adc_src clkgen scope.clock.adc_freq 20e6 scope.clock.clkgen_freq 8e6 # ADC采样参数 scope.adc.samples 5000 scope.adc.offset 0 scope.adc.bits 10 # 触发设置 scope.trigger.triggers tio4 scope.io.tio1 serial_rx scope.io.tio2 serial_tx # 采集N条轨迹 N 1000 traces [] plaintexts [] for i in range(N): pt np.random.randint(0, 256, 16, dtypenp.uint8) scope.arm() target.simpleserial_write(p, pt) ret scope.capture() if ret: print(fcapture failed at trace {i}) continue traces.append(scope.get_last_trace()) plaintexts.append(pt) # 保存数据 np.save(traces.npy, np.array(traces)) np.save(plaintexts.npy, np.array(plaintexts))这段代码基本还原了 ChipWhisperer 官方教程的采集流程但有几个参数需要根据实际情况调整adc_freq是 ADC 采样频率不是说越大越好而是要和目标芯片主频匹配。如果目标芯片跑 8 MHz采样率取 20 MHz 到 40 MHz 都合理太高反而让单条轨迹的字节数变大存储和计算压力都增加。samples是每次采样的点数需要覆盖整个 AES 加密过程。可以先从 5000 开始观察轨迹后调整。offset是相对触发点的采样偏移。如果触发点太靠前或者太靠后可以通过它把有效信号移到窗口中间。4.3 从轨迹到 CPA 攻击一个 AES-128 实战数据采完之后接下来就是 CPA 攻击。这里我用的是基于汉明重量模型的 CPA用明文第一个字节和密钥第一个字节做假设计算所有 256 种猜测密钥的功耗模型然后与真实轨迹做相关分析。关键代码如下from chipwhisperer.analyzer import attacks, preprocessing from chipwhisperer.analyzer.attacks.models import AES128_8bit attack attacks.CPA(attacks.models.AES128_8bit) attack.set_trace_source(traces, plaintexts) attack.analysis(1) key_guess attack.keys() print(key_guess)这里的key_guess会输出一个 256x16 的数组每一列对应一个密钥字节每个元素是该候选密钥的排序得分。得分最高或排名第一的那个候选密钥就是 CPA 恢复出来的密钥字节。我第一次在 Husky 上完整跑通 CPA 时用的是直接从官方教程抄的代码但攻击结果并不理想密钥恢复不出来。后来排查发现是两个原因未对齐问题目标芯片的加密执行时间不稳定导致轨迹之间在时间轴上没有对齐。解决办法是在采集前先做一次简单的对齐检查或者用预处理做重采样。触发点选得不好SimpleSerial 的触发信号是目标芯片在收到明文后拉高的但这个信号到 FPGA 捕获之间有一段延迟导致有效信号出了采样窗口。调整过后几千条轨迹就能稳定恢复出全部 16 字节密钥。这个结果和官方文档里说的量级差不多说明 Husky 的采集性能是正常的。4.4 影响采样质量的关键参数几个影响轨迹质量和攻击成功率的参数这里做一个横向对比参数推荐设置说明ADC 采样频率2~5 倍目标主频太低丢细节太高增加存储和计算开销采样点数覆盖整个加密过程多出 10%~20%不够只能临时重采浪费时间触发源TIO4默认也可用外部时钟或手动触发目标板供电3.3V 或 5V 按芯片规格电压不稳会引入噪声轨迹数量初次尝试 1000 条再逐步增加CPA 不是越多越好但太少确实不够另外还有一个容易被忽视的点目标板电源噪声。如果目标板上有数码管、LED、无线模块这类大电流负载采样出来的功耗轨迹会有严重的周期性干扰。实验时最好把无关外设断掉或者至少保证它们不处于工作状态。5. 故障注入实验扩展5.1 Glitch 参数模型Husky 支持故障注入和功耗采集用的是同一套触发与时钟体系。故障注入的几个关键参数如下glitch_offset毛刺起始位置相对于触发点的时间偏移。glitch_width毛刺宽度也就是电压被拉低或拉高的持续时间。glitch_ext_offset扩展模块的额外偏移用于更精细的时间调整。glitch_repeat毛刺重复次数有些攻击需要连续多个毛刺才能生效。这里面的逻辑是目标芯片在运行到特定指令时电源电压突然出现一个短暂的跌落导致内部逻辑亚稳态或错误翻转从而产生错误的计算结果。毛刺太宽芯片直接复位毛刺太窄又不足以干扰逻辑。所以实验时需要在一定范围内扫描偏移和宽度两个维度。5.2 电压毛刺实验的完整配置下面是一个通过 Husky 进行电压毛刺尝试的脚本骨架scope.glitch.clk_src clkgen scope.glitch.output glitch_only scope.glitch.trigger_src ext_single scope.glitch.width 3 scope.glitch.offset 100 scope.glitch.repeat 1 for offset in range(50, 200, 5): scope.glitch.offset offset scope.arm() target.simpleserial_write(p, plaintext) ret scope.capture() output target.simpleserial_read(r, 16) if is_faulty(output): print(ffault at offset {offset})每次改变偏移后都重新给目标板发送一次明文然后读取返回结果。如果返回的密文既不是正确结果也不是全零、全一等明显错误模式那大概率就是故障注入生效了。找到故障窗口后你可以在那个窗口附近细化扫描宽度把成功率提上去。5.3 故障注入常见坑故障注入比功耗采集更容易出问题因为它是直接“动电源电压”的操作。我遇到过的几个典型坑目标板复位毛刺过强导致目标芯片直接掉电复位。表现是通信中断SimpleSerial 无响应。这一般是毛刺宽度太大要调小。毛刺不生效偏移设置完全不对毛刺打在了目标指令执行窗口之外。表现为目标芯片行为完全正常密文正确。需要扩大偏移扫描范围。供电不稳如果毛刺注入频率比较高目标板电压会被拉到一个较低水平导致整个系统不稳定。解决办法是降低实验频率每次注入之间留足时间休息。对电源电路要求高Husky 的 glitch 输出到目标板前需要目标板的电源电路能够承受这种快速电压变化。CW308 UFO 底板在设计上做了优化但如果你用的是自制的目标板一定要确认供电路径足够宽、电容布局合理。还有一点值得提醒故障注入实验要把供电保护做好。建议在目标板电源输入端串一个可恢复保险丝或者使用带限流的可编程电源。一旦毛刺导致短路或过流限流会保护芯片不被烧掉。6. 常见问题与排查技巧实录6.1 高频问题速查表把我在构建和实验路上遇到的高频问题整理成了一个表方便以后快速排查现象可能原因解决思路电脑无法识别设备USB 线是充电线换数据线设备识别但scope()报错固件版本太老升级固件采集超时触发信号没到达检查目标板固件、触发跳线轨迹全是平线目标板没供电检查 20-pin 连接和电压选择轨迹有周期性毛刺外部干扰或目标板外设断开无关外设检查接地CPA 恢复不出密钥轨迹未对齐或模型不匹配做对齐预处理检查功耗模型故障注入导致目标板复位毛刺太宽减小宽度降低注入范围多次注入后设备变慢缓存问题或 USB 带宽耗尽适当降低采样率或减少轨迹数6.2 几个不写在文档里的心得真正上手之后有些经验和文档或者教程里写得不太一样我单独拿出来说第一USB 3.0 线材很关键。我一开始用的是一根普通 USB 3.0 延长线结果高速传输老是断流。排查了很久最后换了一根质量好的短线1 米以内才彻底解决。Husky 对线材比较敏感建议直接用板卡附带的线。第二采集大数据量时优先保存为 NumPy 的.npy格式而不是 CSV。十万条轨迹用 CSV 存文件体积可能是.npy的三到五倍而且后续读取也慢。用.npy格式我能把一次完整的 CPA 攻击在几分钟内完成数据加载和分析。第三PCR 攻击或者模板攻击时先别急着上 Husky 的高采样率。采样率太高意味着每条轨迹的数据量更大处理时间更长但攻击成功率不一定同步提升。如果你只是验证攻击流程先用 20 MHz 采样率跑通再用更高的采样率提升效果。第四借助示波器来排查硬件问题是最高效的。当目标板不工作、轨迹异常时不要盲目改代码。可以用普通示波器量一下目标板的电源、时钟和触发引脚看看信号到底有没有问题在哪一层一目了然。第五Jupyter 里跑长采集任务时建议加上进度条和断点续采。我一般会把采集任务分成多次每次 1000 条保存一部分防止中途 USB 断连导致全部数据丢失。尤其是一跑就是几小时的大规模数据采集这个习惯非常重要。7. 后续扩展与个人体会Husky 已经在我这边跑了大半个月从最开始的硬件自检到现在的常态使用整体的稳定性比我预期的要好。和我之前用的平台对比最明显的变化是功耗轨迹的细节明显增多CPA 攻击所需的轨迹数量下降了一个量级。之前用 CW-Pro 跑 AES-128可能需要上万条轨迹才能稳定恢复密钥现在 Husky 上几千条就能做到。故障注入的扫描速度也快了很多因为整套流程用脚本驱动可以自动化扫描偏移和宽度把原本需要人工操作很久的实验压缩到了分钟级别。如果你准备拿 Husky 做更深度的研究我建议后续从这几个方向继续扩展一是做模板攻击利用 Husky 的采样性能建立高精度功耗模板二是做更复杂的故障注入比如差分故障分析DFA在 AES 最后一轮注入错误通过错误密文恢复密钥三是结合机器学习做一些自动化的侧信道特征提取这些在 Husky 的平台下都有足够的带宽和数据质量支撑。最后再分享一个小技巧Husky 的 FPGA 固件记得养成定期升级的习惯。官方每隔一段时间会更新修复一些特定场景下的 bug同时可能增加新的采样模式或者触发功能。升级固件之前先备份好你当前版本的配置和实验数据避免升级后 API 变化带来的兼容性问题。我自己的体会是侧信道分析和故障注入这类工作工具链的稳定性和可复现性太重要了。Husky 的价值不在于某一个指标有多强而在于它把采集、注入、分析和自动化整合成了一个相对顺滑的工作流。这一套构建下来踩了不少坑但这些坑踩过之后后面做实验的效率和信心都会提升很多。希望这篇记录能帮你少走一些弯路。
返回列表