
在上一篇文章中,我们使用ChipWhisperer处理了Vcc小故障攻击。 我们的进一步目标是分阶段研究读取受保护的固件微控制器的过程。 使用此类攻击,攻击者可以访问所有设备密码和软件算法。 一个生动的例子是使用Vcc-glitch攻击通过MK STM32F042板对 Ledger Nano S硬件加密钱包的黑客攻击。
有意思吗 让我们在猫下看。
我们从一篇显示Vcc小故障攻击结果的文章中了解了读取受保护固件的可能性-通过引导加载程序绕过RDP保护字节,用于几个微控制器(以下称为MK)。 我们还建议您阅读有关破坏ESP32的文章 。
该研究的理论基础是使用ChipWhisperer通过面罩加载器成功读取LPC1114的受保护固件的指南 。
与第一篇文章一样,我们决定在MK STM32F103RBT6板上进行实验:

板STM32F103RBT6
将数据写入闪存和RAM扇区或读取它们以及对MK存储器执行其他操作的能力取决于保护字节的值(对于STM32-RDP)。 对于不同的MK,保护字节的值和用途以及检查它们的算法是不同的。
硬件设定
让我们开始实验。 首先,您需要根据该图将ChipWhisperer连接到MK:

ChipWhisperer与STM32的连接图,用于通过模板加载器读取受保护的固件
图中标出了应从STM32F103RBT6板上移除的元件(与标准MK连接相反)。 箭头指示ChipWhisperer的连接点,签名指示其引脚。
如图所示,不需要外部石英,因为在使用掩膜器时,MK STM32F103RBT6使用内部时钟的频率为24 MHz,因此ChipWhisperer与MK之间没有同步。
让我们继续设置ChipWhisperer。 如上所述,ChipWhisperer的推荐频率为24 MHz(或其他倍数)。 此频率的倍数越高,您可以更准确地调整起音时刻。 由于缺乏同步,scope.glitch.offset参数的选择是可选的;可以将任何值分配给它。
必须根据ChipWhisperer的设置频率选择参数scope.glitch.repeat和scope.glitch.width。 对于较大的频率值,所有短期脉冲(其数量由scope.glitch.repeat设置)将合并为一个长脉冲。 因此,可以选择参数scope.glitch.width和scope.glitch.repeat修复的值,反之亦然。 我们发现最佳脉冲持续时间应约为80 ns(定义为最大脉冲宽度的一半)。
仍然可以选择参数scope.glitch.ext_offset的值。
选择scope.glitch.ext_offset
首先,您需要选择攻击时刻。 根据STM公司文件中提出的方案,在收到从闪存扇区读取数据的请求后,将检查保护字节值:

用于响应从闪存扇区读取数据的请求的算法
为了验证这种验证方案的有效性,我们通过ST-Link读取了具有RDP保护的类似MK的Bootloader可执行代码。 下图显示了部分读取内存命令处理算法。

处理内存读取命令的一般视图(对RDP检查功能的调用以及在检查失败的情况下发送NACK清晰可见)

RDP验证功能主体
让我们注意一下RDP检查功能的主体:可以看出,正在0x40022000 + 0x1C
读取寄存器,逻辑移位为30位并分支。 从PM0075编程手册(STM32F10xxx闪存微控制器)的文档中,可以清楚地看到0x40022000
是闪存控制器的基地址,而0x1C
是FLASH_OBR寄存器偏移量 ,我们感兴趣的是RDPRT的第二位:读取保护,其中包含RDP保护状态。
攻击的必要时刻是开发LDR
指令(从内存加载)。 该指令位于读取固件的请求(发送0x11
字节和0xEE
)与UART的ACK
/ NOACK
MK响应之间。 为了从视觉上解决这一问题,有必要将示波器连接到UART1_RX(引脚PA10)和UART1_TX(引脚PA9),然后根据UART1监视电压变化。 结果,具有所选scope.glitch.ext_offset值的功率起伏波形应如下所示:

选择攻击时刻
固件读取脚本
现在,您需要在Python代码中指定CW_TRIG触发器的触发时刻,以便拦截通过UART1_RX传输校验和的时刻。 ChipWhisperer有一个用于与STM32 MK maskloader通信的库。 在正常模式下,该库用于使用位于class STM32FSerial(object)
文件中的类class STM32FSerial(object)
将手册中的固件从手册下载到MK,其路径为software/chipwhisperer/hardware/naeusb/
。 要激活触发器,您需要将该类复制到主要的可执行脚本中,以使类方法CmdGeneric(self, cmd)
成为全局可访问的对象,并在发送请求的校验和(0xEE)以读取内存扇区之前添加scope.arm()
命令。 最后的课程在下面的扰流器中给出。
用于将ChipWhisperer与STM32通信的类 import time import sys import logging from chipwhisperer.common.utils import util from chipwhisperer.hardware.naeusb.programmer_stm32fserial import supported_stm32f from chipwhisperer.capture.api.programmers import Programmer
应该注意的是,STM32F1xx maskloader使您可以在单个请求中从指定的闪存扇区读取不超过256个字节的固件。 因此,在读取MK的整个固件时,有必要在Vcc小故障攻击期间执行几个读取请求。 然后,应将收到的256个字节分成8个32字节的数组,并从中形成一个HEX文件。
HEX转换器代码和辅助功能 def int2str_0xFF(int_number, number_of_bytes): return '{0:0{1}X}'.format(int_number,number_of_bytes_in_string) def data_dividing_from_256_to_32_bytes (data_to_divide, mem_sector, mem_step=32): if mem_sector > 0xFFFF: mem_conversion = mem_sector >> 16 mem_conversion = mem_sector - (mem_conversion << 16) data_out = '' for i in range(int(256/mem_step)): data_vector = data_to_divide[(i * mem_step):((i + 1) * mem_step)] mem_calc = mem_conversion + (i * mem_step) data_out += read_and_convert_data_hex_file(data_vector, mem_calc, mem_step) + '\n' return data_out def read_and_convert_data_hex_file(data_to_convert, memory_address, mem_step): addr_string = memory_address -((memory_address >> 20) << 20) data_buffer = '' crcacc = 0 for x in range(0, len(data_to_convert)): data_buffer += int2str_0xFF(data_to_convert[x], 2) crcacc += data_to_convert[x] crcacc += mem_step temp_addr_string = addr_string for i in range (4, -1, -2): crcacc += temp_addr_string >> i*4 temp_addr_string -= ((temp_addr_string >> i*4) << i*4) crcacc_2nd_symbol = (crcacc >> 8) + 1 crcacc = (crcacc_2nd_symbol << 8) - crcacc if crcacc == 0x100: crcacc = 0 RECTYP = 0x00 out_string = ':'+ Int_To_Hex_String(mem_step, 2) +\ Int_To_Hex_String((addr_string),4) +\ Int_To_Hex_String(RECTYP, 2) +\ data_buffer +\ Int_To_Hex_String(crcacc, 2) return out_string def send_to_file(info_to_output, File_name, directory): file = open(directory + File_name + '.hex', 'w') file.write(info_to_output) file.close() def reset_target(scope): scope.io.nrst = 'low' time.sleep(0.05) scope.io.nrst = 'high' from collections import namedtuple Range = namedtuple('Range', ['min', 'max', 'step'])
现已完成配置ChipWhisperer设置。 读取固件的最终脚本如下:
该行之后的所有注释掉的print()
消息print()
except Exception as
帮助搜索故障脉冲的最佳参数时监视MC的状态。 要跟踪MK的特定状态,只需取消注释必要的print()
消息就足够了。
阅读结果
视频显示了通过ST-LINK编程器将固件下载到MK,将RDP转移到保护状态,然后读取固件:
下列错误可能会阻止成功的Vcc小故障攻击:
•读取错误的内存扇区;
•自发删除固件。
通过提高ChipWhisperer的频率来准确选择起音时刻,将有助于避免此类错误。
在开发并调试了读取受保护固件的算法之后,我们对ST-LINK-V2.1编程器的固件进行了测试读取,该编程器可在STM32F103CBT6 MK上运行。 我们将一些固件缝制在一个“干净的” MK STM32F103CBT6上,并安装了该固件,而不是出厂时的固件。 结果,替换为MK的ST-LINK-V2.1在正常模式下工作,好像没有替代品一样。
我们还试图对STM32F303RCT7进行一系列攻击。 攻击期间的此MK的行为与STM32F103RBT6相同,但对读取内存请求的响应包含等于0x00的字节,这与我们预期的结果不一致。 失败的原因是组织这些MK的保护更加复杂和发达的原则。
STM32F1xx MK有两种保护状态:保护关闭(级别0)和开启(级别1)。 在较早的型号中,有三种保护状态:禁用保护(级别0,RDP = 0x55AA),仅对闪存和SRAM存储器进行保护(级别2,RDP = 0x33CC),仅对闪存进行保护(级别1,RDP采用除以下以外的任何值)从0x55AA和0x33CC)。 由于级别1可以采用许多RDP值,因此设置级别0非常困难。 另一方面,可以通过舍弃RDP字节中的一位(如下图所示)来将保护级别从2级降低到1级(如下图所示),从而可以访问SRAM存储器。

比较不同固件保护级别的RDP值
仍然只有了解攻击者如何利用此优势。 例如,使用本文介绍的CBS(冷启动步进)方法。 此方法基于加载MC之后的SRAM存储器状态的分阶段快照(每个快照的频率在微秒区域内),以获得加密密钥,隐藏的密码或任何其他有价值的信息。 作者建议CBS方法将适用于所有STM32 MK系列。
结论
总结一下我们的实验。 我们花了几天的时间才能完成从先前研究获得的Vcc小故障攻击(可以在此处阅读)。 因此,学习如何进行此类攻击非常容易。
Vcc小故障攻击很危险,因为它们很难防御。 为了减少成功进行此类攻击的可能性,建议使用具有更高防护级别的MK。

Raccoon Security是位于火山科学技术中心的专家组成的特殊团队,其领域包括实用的信息安全性,密码学,电路,逆向工程和低级软件的创建。