绕过读取保护,从STM32微控制器中提取固件。
概览
这篇文章是关于STM32F4系列微控制器的电压毛刺攻击的研究报告。旨在提供一个执行电压毛刺攻击的实践案例,同时也讨论了在这个过程中可能遇到的挑战和限制。
Model: STM32F401CCU6.
Core: ARM 32 Cortex-M4 CPU.
Debug mode: SWD.
84MHz work frequency.
256K flash memory, 64K SRAM.
STM32技术细节
Boot Process and Boot ROM
Boot ROM是微控制器内部的初始代码,在启动过程中执行。这些ROM包含负责初始化基本硬件参数和设置的固件代码,如内存配置、安全配置(选项字节)和启动模式。
Boot ROM在微控制器的初始引导过程中起着至关重要的作用,确保在将控制权交给主应用程序代码之前进行适当的初始化。它们是微控制器启动序列的组成部分,帮助建立稳定和功能性的操作环境。
Bootloader存储在STM32设备的内部 Boot ROM(系统内存)中,由STM在生产期间编程。其主要任务是通过可用的串行外设之一重新编程Flash存储器。关于STM32引导加载程序的更多细节可以参阅AN2606:Application Note(https://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf)。
Enabling Bootloader
STM32F4拥有一组专用的I/0 引脚,称为启动引脚(Boot Pins)。这些引脚用于确定启动选项。STM32微控制器中的Boot ROM会检查启动引脚上预定义的逻辑电平,然后据此启用引导加载程序。在STM32F4中,可以通过在启动引脚上设置一个特定的模式来激活引导加载程序,具体模式如本文件(https://www.st.com/resource/en/application_note/cd00167594-stm32-microcontroller-system-memory-boot-mode-stmicroelectronics.pdf)中所述。
STM32 Bootloader 特性
STM32 Bootloader 支持USART协议,并提供了许多功能。这个引导加载程序支持应用程序说明(https://www.st.com/resource/en/application_note/cd00264342-usart-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf)中描述的大量命令。
RDP0
这是默认级别,在此模式下没有保护。
RDP1
在RDP1级别下,无法访问闪存。通过SWD或JTAG读取、编程和擦除闪存也被禁用。任何对受保护闪存的读取请求都会生成总线错误。在此级别下,可以从SRAM或系统引导加载程序启动。
RDP2
在这一级别,芯片完全受到保护。所有选项字节都被冻结且不可修改。JTAG、SWV(单线查看器)、ETM和边界扫描均被禁用。从SRAM或系统内存引导加载程序启动不允许。
系统中通常会有设计来维持其操作电压在适当水平的组件,比如去耦电容器。本方法的一个挑战/要求是对硬件电路进行修改或篡改,以克服这些组件提供的保护。这包括移除一些组件,替换为定制组件,以及选择瞬变注入点。
我们选择了VCAP_1作为瞬变注入点。去耦电容器被替换,这会增加我们瞬变对目标的影响。一个定制的电压瞬变注入电路,包括电容器、电阻器和MOSFET,被创建以确保精准的瞬变注入。每个组件都是经过详尽研究后精心挑选的,以确保电路能够可靠地提供所需的瞬变。
我们使用Teensy开发板来开发我们的毛刺软件,这将帮助我们控制毛刺并精确注入所需毛刺。
我们后来在一篇文章(https://sec-consult.com/blog/detail/secglitcher-part-1-reproducible-voltage-glitching-on-stm32-microcontrollers/)中找到了这个问题的解决方案,但当时我们已经开始了一种不同的方法。
研究中使用的硬件设置如下图所示。
Teensy开发板通过串口与目标的UART端口通信。它还控制瞬变注入电路,以便精确地注入瞬变。我们把瞬变注入电路的输出焊接到了VCAP_1引脚上,确保瞬变的损失最小。在“启用引导加载程序”部分提到的逻辑电平被应用到启动引脚上,使目标进入引导加载程序模式。
软件设置
ST-Link V2编程器被用于通过SWD接口连接到目标。我们使用STM32Cube IDE编写了一个示例程序并将其烧录到目标中。我们从闪存中获取了数据转储,以便稍后验证结果,下面是一个转储的快照。
启用RDP1前的固件转储
接下来,我们使用STM32CubeProgrammer在目标上启用了RDP1。STM32CubeProgrammer允许我们编程STM32微控制器的选项字节。RDP1可以通过设置除了0xAA和0xCC之外的任何值来启用。
在目标上启用了RDP1
瞬变软件
由于我们的目标是转储固件,如前(https://jerinsunny.github.io/stm32_vglitch/#stm32-bootloader-features)所述,我们将针对读取内存命令(0x11)。正如这里(https://jerinsunny.github.io/stm32_vglitch/(https://www.st.com/resource/en/application_note/cd00264342-usart-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf))所描述的,当接收到读取内存命令时,设备会检查是否设置了任何RDP级别,并根据这个决定是否提供对闪存的访问。如果RDP处于活动状态,设备会对读取内存命令返回NACK字节;否则,设备返回ACK字节。
瞬变软件执行以下操作:
send_read_memory_command()
inject_glitch(glitch_parameters)
result= read_response()
if result == ack:
# RDP1 Bypassed
data = read_265K_from(address)
# memory read successful
print(data)
elif result == nack:
#Print RDP1 active
send_read_memory_command()
毛刺攻击目标
一旦硬件和软件都设置完毕,我们开始运行初始表征过程以获取瞬变参数。从所做的表征测试中,关键的瞬变参数是瞬变偏移量和瞬变宽度。瞬变偏移量决定了何时注入瞬变,而瞬变宽度则确定了瞬变的持续时间。我们通过示波器观察瞬变,以此关联软件设定的瞬变参数与实际产生的瞬变效果。基于每次运行后所获得的表征结果,逐步优化瞬变参数。
观察触发与瞬变参数
Note
值得注意的是,在优化瞬变参数的过程中,我们发现尽管读取内存命令收到了肯定的应答(ACK),但在对比数据时,我们仅读取到了零数据,如下面所示。这意味着虽然成功绕过了RDP1保护级别,但我们并未能真正读取到数据。
进一步分析后,我们发现电压瞬变过程中无意间设置了PCROP(即SPRMOD选项字节)。这表明我们需要更精细地调整瞬变,以防在瞬变过程中意外设置PCROP。
我们手动清除了PCROP位,将RDP级别退回到RDP0,然后重新进行了整个软件设置,并重启了整个过程。
毛刺成功
在精细调整瞬变参数后,最终我们成功实现了瞬变,并读取到了一些数据,如下面所示。为了验证我们的结果,我们将输出数据与在启用RDP1之前的原始转储进行了比对。
正如上文解释,单一的读取内存命令只能在读取下一数据块前读取256字节的数据。鉴于目标的闪存大小为256KB,总共需要1024次成功的瞬变才能提取整个闪存的内容。我们保持瞬变设置运行,直到整个固件被提取为止。
大约经过22小时,以及在总共621594次尝试中成功了1024次瞬变之后,整个固件被成功提取,如下面所示。