在过去的四五年里,围绕加密货币钱包的安全性进行了大量研究。大部分研究都集中在故障注入领域,这是破坏嵌入式系统的常见手段,其目的是找到允许修改设备行为,以授予攻击者升级的访问权限。这方面的示例可能包括跳过指令、破坏内存读取操作等。
故障注入包括引入足够小的错误或修改以在目标上导致未定义的行为,但不足以阻止目标完全运行。这通常涉及注入高压脉冲或暂时从目标系统上的目标电源或“轨道”排出电压。
通过引起瞬间电压调制(高于或低于预期电压),我们可以迫使目标系统进入一个未定义的行为领域。有足够针对性的错误可以绕过各种安全检查或其他可能阻碍攻击者或逆向工程的功能。
关于常见的故障注入方法,我们可以尝试引入几种不同类型的故障:时钟故障和电压故障。
对于时钟故障,我们的目标是跳过或修改指令。这个想法是,通过注入另一个时钟周期,我们可以让处理器跳过一条指令。
当我们尝试修改或操作特定的时钟周期序列以获得我们想要的结果时,这些需要精确。时钟故障以 CPU 或微控制器上的外部时钟为目标,最终目标是在适当的时间注入时钟信号,从而导致指令被跳过。
电压故障涉及针对整个系统的电源。通过短暂切断目标系统的电源,我们可以修改其行为/性能。
对于电压故障,我们的目标是在足够短的时间内降低电压,以便处理器不会完全关闭,而是进入一个未定义状态或导致某种类型的未定义行为。不幸的是,这项任务并不容易,因为许多处理器都是专门为避免这种情况而设计的,有时需要攻击者使用组件移除或注入方法。
如果你想了解更多关于这些方法的信息,可以点击有关教程、论坛和文档。
我们还可以使用PicoEMP或chipshouter等工具向目标注入故障,这些工具以不同于上述两种方法的方式注入故障。
这些工具用于执行 EMFI(电磁故障注入)攻击。这些攻击涉及产生可能导致硬件故障的大电场,从而导致潜在的位翻转和其他未定义的行为。想要了解更多关于EMFI攻击的信息,请查看Colin O’flynn关于钱包的介绍。
本文旨在重现使用故障注入绕过 STM32F2 系列 MCU bootrom 中的 RDP 检查的过程,允许攻击者通过 SWD 访问设备的内部存储器。
目标是 Trezor One 钱包,这是一款流行的低成本钱包,它是基于STM32F2微控制器构建的。Trezor的硬件和软件都是开源的,这很好,因为它让我们能够访问硬件图和固件源,这将有助于逆向工程。
Trezor One 使用 STM32F2 MCU,让我们先回顾一下 CPU的相关特性。
在STM32微处理器上可以启用多种安全功能:
1.RDP 0 ——闪存解锁,闪存/内存可通过调试接口访问;
2.RDP 1——闪存锁定,你可以连接调试器并读取 RAM/外设,但不能读取闪存;
3.RDP 2——闪存锁定、RAM 读取锁定、调试接口锁定;
由于我们需要启用保护级别 RDP2,所以我们需要找到绕过它的方法,为此,我们必须稍微仔细研究一下 STM32 中的电源管理和调节方式。
在任何微控制器中,都有多个电源域(power domain),电源域对于一款芯片来说,其是由许多模块、子系统集成之后才会发挥它的功能,不同模块之间的工作速度和性能要求不同,它们为各种芯片外设和内部操作和比较器供电。我们将以内部电压调节器为目标。下面是STM32电源域的简要概述:
该图显示 VCAP_1 和 VCAP_2 线为我们提供了通往内部调节器的直接路径,影响内核逻辑、闪存和 IO 逻辑等内容。因此,如果我们可以简单地操作这条线,我们就有希望影响这些外围设备的行为方式!
对于这项工作,我们将通过尝试操作上图中显示的 VCAP 线来瞄准内部稳压器。我们为什么要瞄准这条线?或者更重要的是,当我们转向其他目标进行故障注入时,我们如何才能找到类似的电压轨来定位其他处理器?
VCAP 线路确保所有内部比较器和稳压器都得到适当管理。如果我们可以操作这条线,则有可能改变 CPU 核心存储器和数字外设的行为,并导致未定义/修改的行为。希望这个内部调节器出现的故障(可能在涉及 RDP 设置的特定内存操作期间)允许我们修改设备的 RDP 状态。
综上所述,我们希望尝试将设备的RDP状态从RDP2修改为RDP1,我们希望通过故障或短暂中断VCAP_1/VCAP_2线路上的电压来实现这一点,VCAP_1/VCAP_2线路用于帮助调节内部稳压器。如果我们能改变内部电压调节器的行为,我们就有可能改变处理器的行为。现在我们已经回顾了目标的内部安全特征和我们要攻击的电源轨,接下来让我们谈谈攻击的细节。
这项工作旨在重现 chip.fail 研究中提出的实践路径,该研究导致在 STM32F2 微控制器的 bootrom 中发现错误。对于那些可能不熟悉的人,bootrom 负责处理微控制器的许多早期启动功能(类似于现代计算机上的 BIOS)。bootrom 负责执行基本的外设初始化、安全检查、启动模式检查,最后将主应用程序加载到内存中并执行。引导过程的概要如下所示:
如果攻击者可以在处理器开始执行其 bootrom 大约 170 微秒后注入故障,那么RDP检查就可以被绕过。这将允许攻击者将 STM32 从 RDP2 释放到 RDP1,从而允许通过 SWD 访问 SRAM。此外,可以通过读取 SRAM 来提取恢复密钥,从而可以访问钱包的内容。注意:Trezor 已修复了此漏洞。
攻击过程如下:
1.开启钱包;
2.当断言(assert)RESET线时,开始故障倒计时;
3.在 170 微秒时,将 VCAP 拉低;
4.通过 SWD 测试 RDP 绕过;
5.从目标设备读取 SRAM。
如果步骤 4 成功并且钱包继续启动,则故障成功,并且可以从目标中读取内部 SRAM。
那么像这样的攻击在信号层面是什么样的呢?我们如何知道处理器何时开始执行引导 ROM?如果分析新的目标/电源轨迹,将如何进行?让我们首先从目标中移除一些组件并查看电源轨迹。
为了确保我们的故障尽可能有效,我们需要移除连接到 VCAP 线和复位线的外部电容器。这些电容器(在下面的示意图中突出显示)用于确保电压保持稳定,这是我们在进行故障注入时不想要的。
以下是 Trezor One 的默认示意图:
下方红色突出显示的组件勾勒出了需要移除的电容器。
下面是钱包的图片,被移除的电容用红色标出:
除了已经确定的需要移除的组件和我们关心的线路,我们还需要捕获一些示例电源跟踪数据。为此,我们将使用示波器。我们在本文中使用了 Siglent SDS1104X-E 100Mhz 数字示波器和该示波器附带的标准直流测量探头。
在执行这样的功率捕获时,必须确保正确设置示波器。这意味着示波器将在检测到复位线上升时开始捕获。
在拨动示波器的触发器时,花一些时间是必要的。虽然使用连续捕获或“滚动”模式可能很有效,但这会大大降低你的捕获率并导致更细粒度的电源跟踪。对于我们稍后将查看的样本,我们的采样率为 500MSa/s。
还应该注意的是,当我们捕获踪迹时,我们没有使用分流电阻,关于如何正确利用分流电阻进行功率测量的文章可以点击这里。
接下来,让我们回顾一些示例电源轨迹。下面是带有外部电容器的 VCAP 线上电压的示例视图:
移除电容器时也有同样的反应:
现在线路噪音更大且更不稳定,这正是我们在尝试将故障或故障注入电源轨时想要的。移除电容器并焊接测试焊盘后,就可以开始从与复位线相关的目标线(VCAP)开始进行一些初始功率分析。当系统复位线达到 3.3V 阈值时,bootrom 开始执行。因此,通过监控复位线,我们可以确定引导 ROM 何时开始执行,我们将使用它作为我们的故障触发器。
粉线代表VCAP线上的电压,而黄线是RESET线上的电压:
我们可以在下面的 gif 中看到突出显示的各种活动区域:注意这些区域的电压波动。基于这个MCU的启动方式,我们可以对这些多重波动的含义做出一些假设。
可以查看大约 170 微秒的跟踪,可以在主应用程序开始执行之前看到 flash 活动。
现在我们已经移除了相关的电容器,接下来我们需要连接到 SWD 端口。这可以通过 PCB 右侧的导通孔访问,如下图所示:
在将这些行插入到breadboard上之后,就可以重现攻击了。
下图是一些要准备的装置:
STL 文件可在此处的 GitHub 存储库中找到。
在设置中,我们使用了 Raspberry Pi、ChipWhisperer 和 STLink。STLink 连接到 Trezor 的 SWD 端口,我们使用它来检测 RDP 绕过是否已成功执行。ChipWhisperer 用于为钱包供电、触发复位线和干扰 VCAP 线。下面是一个简单的接线表:
完成所有适当的连接后,是时候与 ChipWhisperer连接,并输入我们想要的故障参数。如前所述,NewAE 教程是一个很好的起点,可以作为攻击的模板。
在使用ChipWhisperer时,需要输入一些关键的东西,我们现在将介绍其中一些。参考代码可以在这里找到。
整个程序的流程很简单:
从连接到我们的 CW 开始;这可以通过以下代码完成:
我们需要确保我们设置了CW的内部时钟频率以及输出模式和触发源,可以使用以下代码行来完成此操作:
接下来,我们来谈谈触发。我们之前提到过,将使用复位行来指示引导ROM何时开始执行。如果故障不成功并且需要重新运行,此行也将用于重置目标。
reboot_flush 函数负责完全重置设备并解除故障。每当我们想要重置 STM32 并测试新的故障参数时,我们都会调用此函数:
接下来,我们将定义我们的故障参数,我们将使用如下所示的GlitchController类来完成。
最后三行对于我们故障注入至关重要。
故障形成的三个变量是宽度、重复和偏移变量。请注意,这些定义来自 Newaetech 教程。还应该注意的是,除了电线长度和质量等变量之外,还有许多因素会影响故障的形状。根据一般经验,使用SMA连接器的短屏蔽线是最佳做法。
宽度:产生故障的宽度。这是一个周期的百分比。对于我们的示例,我们将在进行电压故障而不是时钟故障时使用最大值。
重复:重复故障的时钟周期数。较高的值会增加可能出现故障的指令数量,但通常会增加目标崩溃的风险。但较高的重复次数通常会导致更强的故障。
偏移:在输出时钟中放置故障的位置。
最终的范围定义ext_offset定义了FPGA在触发后执行故障之前等待的时钟周期。由于我们之前指定了100 MHz的时钟速率,15000个时钟周期相当于大约150微秒。这意味着在Trezor上的RESET线升高后,我们将开始从ext_offset值开始倒计时,当它达到零时,我们将干扰 VCAP 线!
另一件需要注意的事情是,GlitchController类将遍历所有提供的glitch参数。因此,对于每个可能的参数组合,我们将重新设置钱包,尝试gitch,然后尝试通过SWD连接到STM32。这意味着,对于测试大范围的值,我们可能需要几天来完成一系列测试。
我们在监控范围的同时运行了故障的第一次迭代,并看到我们正确地生成了故障。
在运行攻击一段时间没有结果后,就开始对我们的设置进行故障排除。我们想要调试的第一件事是STLink SWD枚举代码,用于检测一个成功的故障:
首先,我们在RDP级别0使用STM32开发板测试了成功的故障检测,这在第一次迭代中立即有效。
这是一个好迹象,但是我们想测试多次调用swd_check函数时会发生什么。
在发现 swd 库无法枚举设备后(这将在故障尝试失败时发生),直到通过 USB 重新枚举 STLink 后,它才能再次运行!
这意味着对于我们所有的测试,只有我们的第一次迭代使用 STLink 正确测试了 SWD。如果一次失败,SWD 将无法再次工作,直到探针被拔出并通过USB插入!
有了这些新的知识和信心,我们发现了实践中的问题,并修改了一个简单的c程序,在每次故障尝试后重置STLink设备。
不过,经过几天的测试和调整故障参数后,我们仍然没有看到结果。
最后,我们决定取出示波器并检查故障。我们用 16000、17000 和 18000 的 ext_offset 检查了故障,虽然看起来是合理的,但是,当 ext_offset 为 0 时,我们看到以下内容:
在上面的截图中,黄线是我们的 RST 线,紫线是 VCAP 线。注意故障和复位线达到其目标电压之间的间隙。ChipWhisperer 在复位线达到 3.3V 之前触发。这个早期的触发导致我们的故障在复位线完全被断言之前开始倒计时。这导致故障提前了大约20微秒触发。
我们可以使用示波器上的光标计算准确的延迟,如下图所示:
我们确定使用示波器提前了大约 24 微秒触发。有了这些新信息,我们将 ext_offset 范围修改为 17000 到 20000 之间,并在周末继续运行。
过了一段时间,STLink LED 是绿色的,这意味着它已经通过 SWD 成功访问了设备!
更有趣的是,我们的故障在 ChipWhisperer 触发后大约 197 微秒发生。回想一下在chip.fail 中的工作,它们的偏移量大约为 170 微秒。我们的延迟为 24 微秒,这与之前的实验相差无几(197-24 = 173)。这个偏移范围是可重复的,我们可以在194-197微秒范围内持续触发故障。
我们现在可以使用 OpenOCD 读取有故障的 SRAM 区域。
运行 OpenOCD 后,我们可以通过 telnet 连接到它,并读取 SRAM:
RAM 转储中存在一个结构,可以在以下代码行中找到该结构:
上图显示了 SRAM 转储中突出显示的结构。我们已成功重现故障并重新启用 SWD 外围设备。
本文介绍了重现故障注入攻击时可能出现的困难和技术障碍,详细介绍了使用故障注入绕过 bootrom 中的 RDP 检查的过程,从而允许攻击者获得特权访问。上述的许多细节可能会根据你的硬件设置而不同。所有代码和资源都可以在这里找到。
参考及来源:https://voidstarsec.com/blog/replicant-part-1