在过去的四五年里,围绕加密货币钱包的安全性,已经有很多公共工作。这些研究大部分都属于故障注入领域,故障注入是破坏嵌入式系统的艺术/科学,足以导致未定义的行为发生。目标是找到一个故障,允许修改设备的行为,以授予攻击者升级的访问权限级别。这方面的示例可能包括跳过指令、损坏内存读取操作等。
物联网安全-5.ZigBee协议物联网安全-6.ZigBee安全物联网安全-7.物联网固件逆向
物联网安全 – 21通过 UBoot 发现 UART 和固件提取
这篇文章旨在提供一个路线图和示例,说明如何复制故障注入攻击,以及尝试这样做时可能出现的障碍和缺点。此外,通过概述复制这些攻击之一的过程,我们希望读者在阅读这篇文章后更有信心生成自己的故障注入攻击或复制预先存在的工作。
但在深入探讨所有这些之前,让我们简要回顾一下什么是故障注入 (FI),并概述一些可用于开始该领域的资源。
故障注入涉及引入一个错误/修改,该错误/修改足够小,足以在目标上导致未定义的行为,但不足以阻止目标完全运行。这通常涉及注入高压脉冲或暂时从目标电源或目标系统上的“电源轨”中释放电压。
通过引起瞬时电压调制(高于或低于预期电压),我们可以迫使目标系统进入未定义行为的领域。针对性充分的故障可以绕过各种安全检查或其他可能阻碍攻击者或逆向工程的功能。
关于常见的故障注入方法,我们可以尝试引入几种不同类型的故障:时钟毛刺和电压毛刺。
对于时钟毛刺,我们的目标是跳过或修改指令。这个想法是,通过注入另一个时钟周期,我们可以使处理器跳过一条指令。
正如您可能想象的那样,当我们尝试修改或操纵特定的时钟周期序列以获得我们想要的结果时,这些需要精确。时钟毛刺以 CPU 或微控制器上的外部时钟为目标,最终目标是在适当的时间注入时钟信号,从而导致指令被跳过。
电压毛刺涉及针对整个系统的电源。通过短暂切断目标系统的电源,我们可以修改其行为/性能。
对于电压毛刺,我们的目标是在足够短的时间内降低电压,使处理器不会完全关闭,而是进入未定义的状态或导致某种类型的未定义行为。不幸的是,这项任务并不容易,因为许多处理器都是专门为避免这种情况而设计的,有时需要攻击者创造性地使用组件删除或注入方法。
如果你想了解更多关于这些方法的信息,我不能强烈推荐NewAE材料。即使您家里没有芯片耳语者,他们的教程、论坛和文档也非常适合任何想要了解有关故障注入的更多信息的人。
我们还可以使用 PicoEMP 或 chipshouter 之类的工具将故障注入到我们的目标中,这些工具以与上述两种方法不同的方式注入故障。
这些工具用于执行EMFI(电磁故障注入)攻击。这些攻击涉及产生大电场,该电场可能导致硬件故障,从而导致潜在的位翻转和其他未定义的行为。有关EMFI攻击的更多信息,请查看Colin O'Flynn关于同一钱包的精彩演讲,我们将在这篇文章中回顾。
现在我们对故障注入背后的想法有了更多的了解,让我们来谈谈这篇文章中复制的攻击。这篇文章旨在复制 chip.fail 演示文稿中介绍的工作。这项工作概述了使用故障注入绕过 STM32F2 系列 MCU bootrom 中的 RDP 检查的过程,允许攻击者通过 SWD 访问设备的内部存储器。这与乔·格兰德(Joe Grand)最近复制的攻击相同,以从钱包中恢复大量货币。在我们深入了解攻击的细节之前,让我们首先检查我们的目标设备并了解有关其安全功能的更多信息。
这项工作的目标是Trezor One钱包。这是一个流行的低成本钱包,围绕STM32F2微控制器构建。Trezor的硬件和软件都是开源的,这很棒,因为它使我们能够访问硬件图和固件源,这将有助于消除大量的逆向工程工作。
Trezor One 采用STM32F2 MCU;在继续之前,让我们回顾一下一些相关的 CPU 功能。
STM32微处理器上可以启用多种安全功能;简要说明可以在下面找到。
RDP 0 - 闪存解锁,可通过调试接口访问全闪存/RAM
RDP 1 - 闪存锁定;您可以连接调试器并读出 RAM/外设,但不能读取闪存。
RDP 2 - 闪存锁定,RAM读取锁定,调试接口锁定
我们的目标启用了保护级别 RDP2,因此我们需要找到一种方法来解决此问题;为此,我们必须仔细研究一下STM32内部的电源管理和调节方式。
在任何微控制器中,都有多个电源域;这些器件用于为各种芯片外设以及内部操作和比较器供电。我们将针对内部电压调节器。STM32电源域的简要概述如下:
该图显示,和线为我们提供了通往内部稳压器的直接路径,影响了内核逻辑、闪存和 IO 逻辑等内容。因此,如果我们能短暂地操纵这条线,我们有望影响这些外围设备的行为方式!VCAP_1
VCAP_2
在这项工作中,我们将通过尝试操纵上图所示的线路来瞄准内部电压调节器。我们为什么要瞄准这条线?或者更重要的是,当我们转向其他目标进行故障注入时,我们如何找到类似的电压轨来针对其他处理器?VCAP
这些生产线确保所有内部比较器和稳压器都得到适当管理。如果我们能够操纵这条线,我们就有可能改变CPU核心存储器和数字外设的行为,并导致未定义/修改的行为。希望这个内部调节器出现故障(可能在涉及 RDP 设置的特定内存操作期间)将允许我们修改设备的 RDP 状态。VCAP
总而言之,我们想尝试将器件的 RDP 状态从 RDP2 修改为 RDP1,我们希望通过毛刺或短暂中断 / 线路上提供的电压来实现,以帮助调节内部电压调节器。如果我们能够修改内部电压调节器的行为,我们就有可能改变处理器的行为。现在我们已经回顾了目标的内部安全功能和我们将要针对的电源轨,让我们来谈谈攻击的细节。VCAP_1
VCAP_2
这项工作旨在复制chip.fail研究中提出的研究,该研究导致在STM32F2微控制器的bootrom中发现了一个错误。对于那些可能不熟悉的人来说,bootrom 负责处理微控制器的许多早期启动功能(类似于现代计算机上的 BIOS)。bootrom 负责执行基本的外设初始化、安全检查、启动模式检查,最后将主应用程序加载到内存中并执行它。下面可以看到启动过程的高级概述:
与chip.fail研究;已发现,如果攻击者可以在处理器开始执行其 bootrom 后大约 170 微秒内注入故障,则可以绕过 RDP 检查。这将允许攻击者将 STM32 从 RDP2 丢弃到 RDP1,从而允许通过 SWD 进行 SRAM 访问。此外,可以通过读取SRAM来提取恢复密钥,从而允许访问钱包的内容。(注意:Trezor 通过在更新的固件版本中从 RAM 中删除恢复密钥来缓解此错误)。
攻击:
打开钱包电源
当RESET线被置位时,开始对毛刺进行倒计时
在 170 微秒时,将 VCAP 拉低
通过 SWD 测试 RDP 旁路
从目标器件读取SRAM
如果步骤4成功并且钱包继续启动,则毛刺成功,并且可以从目标中读出内部SRAM。
那么,像这样的攻击在信号层面是什么样子的呢?我们如何知道处理器何时开始执行引导 ROM?如果分析新的目标/功率迹线,将如何进行?首先,让我们从目标中移除一些组件,并查看电源走线。
为了确保我们的毛刺尽可能有效,我们需要移除连接到VCAP线和复位线的外部电容。这可以通过标准烙铁和一些耐心来完成。这些电容器(在下面的原理图中突出显示)用于确保电压保持稳定,这是我们在进行故障注入时不希望看到的。
以下是Trezor One的默认原理图:
下面突出显示的红色组件概述了需要移除的电容器。
下面是钱包的图片,移除的电容器以红色突出显示:
现在我们已经确定了需要移除的组件和我们关心的线路,我们需要捕获一些示例电源跟踪数据。为此,我们将使用示波器。在我们的研究中,我们使用了 Siglent SDS1104X-E 100Mhz 数字示波器和示波器随附的标准直流测量探头。
在执行此类功率捕获时,必须确保正确设置示波器。我们将示波器配置为使用上升沿触发器在复位线上触发。这意味着示波器将在检测到复位线上升时开始捕获。
在拨入示波器的触发器时,必须花一些时间。虽然使用连续捕获或“滚动”模式等功能可能很诱人,但这会大大降低捕获速率并导致不太精细的电源迹线。对于我们稍后将要审查的样本,我们的采样率为 500MSa/s。
还应该注意的是,当我们捕获走线时,我们没有使用分流电阻器;有关如何正确使用分流电阻器进行功率测量的文章,请参见此处。
接下来,让我们回顾一些示例电源走线。以下是外部电容器线路电压的示例视图:VCAP
这是移除电容器的同一行:
请注意,线路现在更嘈杂且更不稳定;这就是我们在尝试将故障或毛刺注入电源轨时想要的。移除电容器并焊接测试焊盘后,让我们进行一些初始功率分析,从与线路相关的目标线 () 开始。当系统复位线达到3.3V阈值时,bootrom开始执行。因此,通过监控复位线,我们可以确定引导ROM何时开始执行;我们将以此作为故障的触发器。VCAP
RESET
粉色线代表线上的电压,而黄线是线上的电压:VCAP
RESET
我们可以看到下面的 gif 中突出显示的各种活动区域;注意这些区域的电压波动。根据该 MCU 的启动方式,我们可以对这些多重波动的含义做出一些假设。
虽然我们的迹线可能看起来与原始研究中突出显示的迹线不同,但我们可以看到,我们的捕获在功率迹线的结构方面是相似的。例如,如果我们在大约 170 微秒处查看跟踪,我们可以看到主应用程序开始执行之前的闪存活动。
现在我们已经移除了相关的电容器,接下来我们需要连接到 SWD 端口。这可以通过PCB右侧的过孔访问,如下图所示:
将这些线分解为面包板后,是时候复制攻击了。
在这项工作中,我们3D打印了一个组件的夹具,如下图所示:
STL 文件可在此处的 GitHub 存储库中找到
在我们的设置中,我们使用了 Raspberry Pi、ChipWhisperer 和 STLink。STLink 已连接到 Trezor 的 SWD 端口,我们用它来检测 RDP 绕过是否已成功执行。ChipWhisperer用于为钱包供电,触发复位线,并对VCAP线进行毛刺。下面可以看到我们设置的简单接线表:
ChipWhisperer 引脚号/用途 | 特雷佐尔销 |
---|---|
(14) /FPGA-TARG4 | RST |
(5) /PROG-RESET | RST |
Glitch Out | VCAP1 |
(3) /+3.3V | VCC |
(2) /GND | GND |
STLink 标头 | 特雷佐尔销 |
---|---|
GND | VDD |
SWCLK | PA14 |
SWDIO | PA13 |
VTREF | VCC |
完成所有适当的连接后,是时候与 ChipWhisperer 交互并拨入我们想要的故障参数了。如前所述,NewAE 教程是一个很好的起点,我们使用此笔记本作为攻击的模板。
在使用芯片耳语器时,需要注意一些关键事项,我们现在将介绍其中的一些事项。可以在此处找到引用的代码。
程序的整体流程很简单:
让我们从连接到我们的 CW 开始;这可以通过以下代码完成:
import chipwhisperer as cw
scope = cw.scope()
我们需要确保设置CW的内部时钟频率以及输出模式和触发源;我们通过以下行来执行此操作:
scope.glitch.clk_src = "clkgen" # set glitch input clock
scope.glitch.output = "enable_only" # glitch_out = clk ^ glitch
scope.glitch.trigger_src = "ext_single" # glitch only after scope.arm() called
接下来,我们来谈谈触发。我们之前提到过,我们将使用重置行来指示引导 ROM 何时开始执行。如果故障不成功并且需要重新运行,此行也将用于重置目标。
# Trigger on IO4. This is connected to our reset line
scope.trigger.triggers = 'tio4'
该功能负责完全重置设备并布防故障。每当我们想要重置 STM32 并测试新的毛刺参数时,我们都会调用此函数:reboot_flush
def reboot_flush():
global scope
# Cut power to target device
scope.io.target_pwr = False
# Pull reset low
scope.io.nrst = False
# Set up CW for glitching
scope.arm()
# Put reset in high impedance mode (we are triggering off of it)
scope.io.nrst = "high_z"
# Power the target and wait for the glitch to trigger
scope.io.target_pwr = True
接下来,我们将定义毛刺参数;我们将使用 GlitchController 类来执行此操作,如下所示:
gc = glitch.GlitchController(groups=["success", "failure"], parameters=["width", "offset", "ext_offset"])
gc.set_global_step(g_step)
gc.set_range("width", 40, 40)
gc.set_range("offset", -45, -45)
gc.set_range("ext_offset", 15000, 18000)
scope.glitch.repeat=20
最后三条线对于我们的故障时间和形状至关重要。
我们用来帮助调整故障的三个变量是 、 和 变量。请注意,这些定义是从 Newaetech 教程中提取的。还应该注意的是,除了电线长度和质量等这些变量之外,许多因素都会影响故障的形状。作为一般经验法则,最佳做法是将短屏蔽线与 SMA 连接器一起使用。width
repeat
offset
width
:使故障的宽度。这是一个周期的百分比。在我们的示例中,我们将使用最大值,因为我们正在进行电压毛刺,而不是时钟毛刺。
repeat
:重复毛刺的时钟周期数。较高的值会增加可能出现故障的指令数量,但通常会增加目标崩溃的风险。注意:重复次数越高,毛刺越强
offset
:在输出时钟的哪个位置放置毛刺。
这三个变量可以纵来塑造我们的故障;我们可以更改毛刺的宽度和整体强度。接下来,我们必须确定何时触发相对于目标上的某些外部信号的毛刺,在我们的例子中,这将是线。 RESET
最终范围定义定义了FPGA在触发后在执行毛刺之前将等待多少个时钟周期。由于我们之前指定的时钟速率为 100 MHz,因此 15000 个时钟周期相当于大约 150 微秒。这意味着,在Trezor上的线变高后,我们将开始从我们的值开始倒计时,当它达到零时 - 我们将出现故障!ext_offset
RESET
ext_offset
VCAP
另一件需要注意的事情是,GlitchController 类将遍历提供的所有毛刺参数。因此,对于每个可能的参数组合,我们将重置钱包,尝试 gitch,然后尝试通过 SWD 连接到 STM32。这意味着,对于大范围的值,我们可能需要几天才能完成一系列测试。
我们在使用示波器进行监视时运行了故障的第一次迭代,并看到我们的故障已正确生成。我们让故障在周末运行,并期待在我们的 Trezor 上使用活跃的 SWD 端口胜利地返回办公室。
在运行攻击一段时间没有结果后,我们开始对设置进行故障排除。我们首先要调试的是用于检测成功故障的 STLink SWD 枚举代码:
'''
swd_check
Use the link to attempt to connect via SWD
'''
def swd_check():
global dev
import swd
pc = 0
try:
dev = swd.Swd()
pc = dev.get_version().str
except:
del swd
pass
return pc
首先,我们使用 RDP 级别为 0 的 STM32 开发板测试了成功毛刺的检测,该开发板在第一次迭代中立即起作用。
这是一个好兆头,但我们想测试当我们多次调用该函数时会发生什么。 swd_check
这样做后,我们发现在 swd 库无法枚举设备后(这将在故障尝试失败时发生),直到通过 USB 重新枚举 STLink 之前,它才会再次工作!
这意味着对于我们所有的测试,只有我们的第一次迭代使用 STLink 正确测试了 SWD。如果失败一次,SWD 将无法再次工作,直到探头被拔下并通过 USB 重新插入!
有了这些新知识,并确信我们发现了问题,我们修改了一个简单的 c 程序,以便在每次故障尝试后重置 STLink 设备。
'''
swd_check
Use the link to attempt to connect via SWD
'''
def swd_check():
global dev
import swd
pc = 0
try:
# Reset the STLink
os.system(f"sudo /home/pi/glitch/replicant/python/usbreset {usb_path}")
dev = swd.Swd()
pc = dev.get_version().str
except:
del swd
pass
return pc
有了这个,我们确信我们已经找到了我们的问题。其他一切看起来都很棒,并与公共文献保持一致;还有什么地方会出错?但不幸的是,经过几天的测试和调整故障参数后,我们仍然没有看到结果。
最后,我们决定把示波器拿出来,检查一下我们的故障。我们检查了 16000、17000 和 18000 的毛刺,肉眼看起来很合理。但是,如果为 0,则我们看到以下内容:ext_offset
ext_offset
在上面的屏幕截图中,黄线是我们的RST线,紫色是VCAP线。注意毛刺与复位线达到其目标电压之间的间隙。ChipWhisperer 在复位线达到 3.3V 之前触发。这种早期触发导致我们的故障在复位线完全置位之前就开始倒计时。这导致故障过早触发了大约 20 微秒。
我们可以使用示波器上的光标计算确切的延迟,如下图所示:
我们确定使用示波器触发的时间过早了大约 24 微秒。有了这些新信息,我们将范围修改为17000到20000之间,并在周末继续运行。ext_offset
当我们周一回到办公室时,STLink LED 是绿色的,这意味着它已成功通过 SWD 访问设备!
pi@voidstar:~/glitch/replicant $ sudo -E python3 python/replicant.py /dev/bus/usb/001/004
Found CW!
Success! -- offset = -44.921875, width = 39.84375, ext_offset = 19752
successes = 1, failures = 0, offset = -44.921875, width = 39.84375, ext_offset = 19752
Done glitching
更有趣的是,在ChipWhisperer触发后,我们的故障发生在大约197微秒。回想一下,在 chip.fail 工作;它们的偏移量约为 170 微秒。我们的延迟为 24 微秒,使其处于与先前研究相似的范围内(197-24 = 173)。这个偏移范围是可重复的,我们可以在194-197微秒范围内持续触发毛刺。
现在,我们可以使用OpenOCD来读取有故障的SRAM区域。
pi@voidstar:~/glitch/replicant $ ./run_openocd.sh
Open On-Chip Debugger 0.10.0+dev-01514-ga8edbd020-dirty (2022-03-01-19:24)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info: auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD.
Info : Listening on port 6666 for tcl connections
Info : Listening on port 11111 for telnet connections
Info : clock speed 1000 kHz
Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.259749
Info : stm32f2x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f2x.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : accepting 'telnet' connection on tcp/11111
随着OpenOCD的运行,我们可以通过telnet连接到它并读出SRAM:
pi@voidstar:~/glitch/replicant $ telnet localhost 11111
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> dump_image sram2.bin 0x20000000 0x1FFFFFFF> exit
Connection closed by foreign host.
pi@voidstar:~/glitch/replicant $ hexdump -n512 -C sram2.bin
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000090 78 77 00 08 90 77 00 08 ff ff 00 00 13 70 00 08 |xw...w.......p..|
000000a0 1f 70 00 08 26 70 00 08 24 00 00 00 28 00 00 00 |.p..&p..$...(...|
000000b0 00 01 04 00 01 00 00 00 00 00 00 00 00 01 57 49 |..............WI|
000000c0 4e 55 53 42 00 00 00 00 00 00 00 00 00 00 00 00 |NUSB............|
000000d0 00 00 00 00 80 c3 c9 01 00 87 93 03 00 00 00 00 |................|
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200
RAM 转储中存在一个结构,可以在以下代码行中找到:
if (req->wIndex ==
USB_WINUSB_REQ_GET_COMPATIBLE_ID_FEATURE_DESCRIPTOR) {
static const uint8_t winusb_wcid[] = {
// header
0x28, 0x00, 0x00, 0x00, // dwLength
0x00, 0x01, // bcdVersion
0x04, 0x00, // wIndex
0x01, // bNumSections
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// functions
0x00, // bInterfaceNumber - HACK: we present only interface 0 as
// WinUSB
0x01, // reserved
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatibleId
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // subCompatibleId
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
}; wait_random();
USBD_CtlSendData(dev, UNCONST(winusb_wcid),
MIN_8bits(req->wLength, sizeof(winusb_wcid)));
return USBD_OK;
} else {
wait_random();
USBD_CtlError(dev, req);
return USBD_FAIL;
}
上图显示了SRAM转储中突出显示的结构。我们已经成功复制了故障并重新启用了 SWD 外围设备。
这项工作和帖子旨在演示在尝试复制故障注入攻击时可能出现的困难和技术障碍。我们演示了使用错误注入绕过 bootrom 中的 RDP 检查的过程,从而允许攻击者获得特权访问。这篇文章演示了复制 FI 攻击时的小细节很重要,这些细节可能会根据您的硬件设置而变化。可以在此处找到为此工作生成的所有代码和资源
您可能会问自己,有没有办法在不连接到 VCAP 线路并移除这些额外电容器的情况下做到这一点?请留意本文的第二部分,我们将以从本文中获得的知识为基础,并使用 PicoEMP 演示相同的攻击:
如果您对这种类型的硬件级逆向工程感兴趣,请查看我们的培训课程或联系我们了解任何咨询需求。如果您想在发布新的博客文章、课程或工具时收到通知,请考虑注册邮件列表。我只在有实际帖子或课程更新时发送电子邮件。最后,您可以在 twitter 上关注我,以获取有关副业和课程的各种更新。
这项工作是与我的朋友科迪·加拉格尔(Cody Gallagher)和亚伦·菲尔普斯(Aaron Phelps)一起完成的,我们三个人之间有很多乐趣来复制这次攻击。我们还必须向@stacksmashing和@colinoflynn大声疾呼,他们非常有帮助,回答了我们的许多早期问题。
要复制此攻击,需要满足以下条件:
Chipwhisperer 精简版
能够安装 chipwhisperer 软件包的 Python 环境
pip install chipwhisperer
特雷佐一号
STLink 编程器
示波器
我们使用了Siglent Technologies SDS1104X-E
用于元件去除等的焊接设备
其它课程
windows网络安全一防火墙
windows文件过滤(更新完成)
USB过滤(更新完成)
游戏安全(更新中)
二进制漏洞(更新中)
ios逆向
windbg
恶意软件开发
还有很多免费教程(限学员)
更多详细内容添加作者微信