查看原文
其他

针对微控制器的时钟错误注入攻击

backahasten 看雪学院 2019-09-17


译者注:这是一篇14年的文章,但是所写的内容一点都不过时,文中详细介绍了如何自己DIY一个低成本的错误注入设备,并如何具体实施攻击,希望本文可以启发读者,做出更有意思的攻击。


原文:http://www.t4f.org/articles/fault-injection-attacks-clock-glitching-tutorial/



>>>>

引言


已经有很多论文都记述了使用错误注入攻击密码学设备或者嵌入式系统,但是他们倾向于介绍对注入结果的处理,而缺少介绍整个注入流程。他们只会泛泛的介绍,针对了什么产品(FPGA或者其他产品),使用什么错误注入方法,得到了什么。


但是如果你希望找到原理图或者代码来尝试复现论文所叙述的内容,就会发现细节很少,困难重重,因此,很多读者就会认为错误注入是一个很高难度的攻击方法,需要攻击者拥有很强的专业知识和很贵重的攻击设备。这同时也给硬件安全带来虚假的错觉。

 

然而,事实是错误注入完全可以使用简单和便宜的工具进行,例如,之前,XBOX360被爆出可以通过错误注入攻破的漏洞,00年代中期,电视的智能卡可以被一个称为unloopers的攻破。

 


>>>>

正文介绍


今天,我会向您展示如何使用低于15美元的设备进行时钟毛刺攻击。

 

译者注:在这里,作者介绍了一个叫做giant的开源项目。该项目支持错误注入和侧信道攻击,但是已经在2016年末停止更新。Chipwhsiperer可以代替此项目,并且更加便宜,成本大概在250美元左右。


1. 什么是错误注入攻击?


错误注入攻击指的是在计算设备中,故意引入毛刺,以期望改变软件硬件逻辑的执行流。错误注入一般期望有两种效果,避免执行和破坏正在处理的数据,这些可以用来绕过安全认证和泄露密码学算法的密钥。

 

有几种典型的错误注入攻击方法,最常见和最容易的是时钟和电压毛刺注入,还有其他,例如激光注入,电磁注入和热辐射注入。


2. 时钟毛刺注入


时钟毛刺注入是指让时钟频率在一小段时间内突然增加。

 

在集成电路中(微控制器,处理器,FPGA),由于长度,走线中的寄生电容电感分布等,时钟信号会不均匀分布,且不会同时到达所有作用点。制造商会规定一个最大频率,在这个频率下,可以保证时钟信号可以正确的到达每一个寄存器。


如果超出这个频率,芯片就不会正常运行。但是,如果我们强制芯片仅仅在一条指令周期超出频率,之后恢复正常,下一条指令就会正常。

 

时钟注入最想要的效果就是跳过一条指令的执行,也可以实现其他效果,比如让芯片加载错误的数据或者破坏已经存储的数据。


建议阅读,“An In-depth and Black-box Characterization of the Effects of Clock Glitches on 8-bits MCUs” (Balasch, Gierlichs and Verbauwhede)详细说明了针对Atmel芯片,不同的毛刺产生的不同影响。

 

时钟错误注入的典型应用是避免执行跳转指令,如果大家在破解软件的时候更改过JNE之类的指令来绕过保护,就可能感觉特别相似了。通过时钟注入,我们可以跳过某些逻辑分支的执行。


3. 我们的设备


由于速度有要求,大多数研究和商业产品使用FPGA产生时钟毛刺。高频时钟信号经过FPGA分频之后获得工作频率,需要毛刺的时候,改变分频系数,时钟频率增加。 可能根据需要修改信号路径,增加一些延时来改变时钟相位。

 

我个人推荐xilinx的spartan-3,他便宜,且还有一个数字时钟管理器(DCM)模块,可以合成不同频率的时钟并且精准控制他们的相位。


没有必要选择更高级的FPGA,因为他的150Mhz的主频足够攻击大多数微控制器。(译者注:现在spartan-6都快被淘汰了,我推荐lattice的mx系列)。我知道使用FPGA对很多人很难并且不够方便,所以我决定出于演示的目的,使用微控制器去攻击微控制器。

 

主机控制器里面的固件,以恒定频率切换IO口,来产生一个时钟信号,这个时钟信号给到目标微控制器,并作为他的时钟输入。为了注入毛刺,我们需要主控制器尽可能快的切换IO口,因为控制器需要两条指令产生一个时钟周期(一条把IO口置1,另一置0),所以我们能产生的最大频率是主控制器频率的一半。

 

有了清晰的演示,假设我们使用32Mhz和8 MIPS的Microchip PIC 18F作为主控制器,那么可以产生的时钟最快是4Mhz,时间分辨率,或者说最小周期是1/4Mhz = 250ns,我们生成的任何毛刺都只能是他的倍数。(500ns,750ns,1us,.......)


正如之前所说,为了攻击成功,我们需要所产生的时钟信号比目标系统最大工作频率高,实际上,可能需要高两,三倍。因此,PIC是不能满足要求的,因为大多数微控制器都在20mhz左右。

 

我们需要更快的MCU作为主控制器,能够产生40mhz时钟。PIC,AVR,MSP430都不行,我们需要ARM。


过去几年,德州仪器,NXP或者ST都发布了廉价的开发板,其中一些由于价格便宜销售的很好,我会使用NXP的LPCXpresso,搭载LPC1769芯片。他的Cortex M3架构可以让他工作在120Mhz,其中高速GPIO可以产生60mhz的信号,这样的话,毛刺分辨率是1/60mhz = 16.6667ns。(译者注。我推荐ST的STM32H7系列,主频高达400mhz的怪兽)

 

 

现在我们来选择攻击目标,我选择我最喜欢的Microchip PIC,具体是PIC 16F88。但是实际上,我做了很多实验,针对12F675,12F683,16F84,16F628,16F648和16F876也都可以,我也成功在Atmel的AVR上复现。

 

16F88的数据手册说,最高频率是20mhz,实际情况下,其容忍极限频率可以到30mhz,但是制造商为了安全冗余限制到20mhz,为了产生影响,我们需要比30mhz更快的毛刺。

 

PIC连接两个LED灯,我们用它来现实PIC的状态,时钟输入和复位信号链接到ARM的两个GPIO,如图所示。

 

 

ARM的两个按钮用来出发和产生毛刺。

 


4. 最简单的实验


我们尝试使用一个简单的实验理解毛刺的效果。

 

我们的目标板在一个无限Loop中,我们使用一个错误注入打破该循环。

 

这是我们目标PIC中源代码的主要部分。


START:

BSF LED1 ; LED1 开启 , LED2 关闭
BCF LED2

INFINITE_LOOP1:

GOTO INFINITE_LOOP1 ; 无限循环,等待glitch
; 开始

BCF LED1 ; LED1 关闭 , LED2 开启
BSF LED2

INFINITE_LOOP2:

GOTO INFINITE_LOOP2 ; 无限循环
; 开始

GOTO START

正如程序所示,PIC指令有两个无限循环,第一个是LED1开启2关闭,下一个是LED2开启1关闭,这个实验的目的是如何使用错误注入绕过GOTO环路。

 

您可以在此下载全部代码:

 

[https://github.com/RamiroPareja/FaultInjectionTutorial]

 

如果您需要自己少些,请保证配置使用外部振荡器,MCLRE为ON,LVP为ON且警用看门狗和欠电复位。

 

ARM产生15mhz的时钟信号传递给目标,但是按下按钮PB1时,会产生60mhz的4个周期的毛刺,因为我们必须快速的切换GPIO,所以使用汇编编写程序。

 

由于没有时间对PB1进行延时抖消,所以我设置了一个PB2,PB2准备毛刺,PB1执行。

 

核心代码如下所示:


LOOP_WAITING_BUTTON1: ; This loop generates a 15 MHz clock signal while PB1 is not pressed

str r1, [r0,#24] ; Clock = 1
str r1, [r0,#24]
ldr r2, [r3,#0x14] ; Checks if PB1 is pressed (pin 12 - port 2)
tst r2, #0x1000

str r1, [r0,#28] ; Clock = 0
bne LOOP_WAITING_BUTTON1 ; If PB1 was not pressed, repeat the loop

str r4, [r0,#24] ; SYNCH = 1
; This signal is used to trigger the oscilloscope

; The button has been pressed. A glitch has to be injected

str r1, [r0,#28] ; This instruction is to wait just one instruction cycle.
; Otherwise, the last clock period before glitching will be shorter

; Generates four clock cycles at 60MHz
str r1, [r0,#24]
str r1, [r0,#28]
str r1, [r0,#24]
str r1, [r0,#28]
str r1, [r0,#24]
str r1, [r0,#28]
str r1, [r0,#24]
str r1, [r0,#28]

LOOP_WAITING_BUTTON2: ; This loop generates a 15 MHz clock signal while PB2 is not pressed

str r1, [r0,#24] ; Clock = 1
str r1, [r0,#24]
ldr r2, [r3,#0x14] ; Checks if PB2 is pressed (pin 11 - port 2)
tst r2, 0x800

str r1, [r0,#28] ; Clock = 0
bne LOOP_WAITING_BUTTON2 ; If PB2 was not pressed, repeat the loop

str r4, [r0,#28] ; SYNCH = 0

; Generates one more clock cycle before jumping to the beginning
; The number of instructions are counted to avoid shorter periods

str r1, [r0,#28] ; This instruction is to wait just one instruction cycle.

str r1, [r0,#24] ; Clock = 1
str r1, [r0,#24]
str r1, [r0,#24]
str r1, [r0,#24]

str r1, [r0,#28] ; Clock = 0

b LOOP_WAITING_BUTTON1 ; Jump to the beginning


在Cortex M3中,nop指令并不意味着控制器必须等待一个时钟周期,处理器可能删除NOP而没有延时,因此,为了确保延时,我重复上一次的GPIO访问而不是使用NOP。


大多数情况下,只需要一个周期的鼓掌,如果我们延长一个周期,不仅仅会忽略所执行的周期,还会忽略下一个周期,但是,因为PIC架构使用4个时钟周期执行每条指令(分支除外),因此,我使用4个周期的毛刺信号。

 

这是示波器抓到的信号——

 

 

因为我的示波器模拟带宽只有100mhz,60mhz的方波抓不完全,因此,这次捕获,时钟减慢了4倍。

 

由于目标始终执行相同的GOTO指令,因此我们可以随时启动毛刺,因为它很容易“击中”GOTO指令。,而不需要同步。 但是,如果循环在每次迭代中还要执行其他逻辑,我们应该在目标执行GOTO的那一刻同步要产生的毛刺。 我稍后会再说。

 

该视频显示了攻击过程:

 

https://youtu.be/anv6O1f3jXk

 

开始时,16F88卡在第一个无限循环中。之后注入毛刺,PIC跳过分支指令,进入第二个循环。再执行一次,又跳回第一个循环。

 

在0.09左右,执行攻击之后两个灯都亮了,这是因为毛刺影响了GOTO指令,也同时影响了关闭LED1的指令。

 

正如视频所示,并不是所有的故障都可以成功,有时候需要很多尝试才可以。这是因为毛刺的产生没有和运行同步,没有在最佳时刻执行,正如我们稍后会看到的,如果毛刺同步,在GOTO的第三个时钟周期中注入,会有最佳效果。


5. 与电压配合


进行时钟错误注入的时候,电压可能至关重要。

 

为了现实电压的影响,我修改了ARM的系统时钟,以80mhz而不是120mhz运行,其余不变。PIC的时钟毛刺就是40mhz(25ns)而不是60mhz了。

 

在5v时,几乎不起作用,然而,降低典雅,攻击的成功率一下子高了,2.7v电压下,这个电压远低于数据手册中的4V,几乎每次都能成功。

 

https://youtu.be/ysv5LM-Vutg

 

电压会影响信号的传播掩饰,有时候改变电压更有利于攻击,请注意,某些控制器有内部稳压系统,他们不会有影响(或者说影响不大)

 

请注意,我们是电压保持稳定而不是短时间降低电压(power glitch)。温度是另外一个因素,也可以影响信号的传播帮我们实现故障。


6. 一个更加现实的攻击场景


前面的例子非常简单。 让我们再试一次,但使用更现实的场景。

 

在这种情况下,我们将攻击要求用户输入PIN码的假定系统,并且在三次尝试失败之后,它将锁死并且无法使用。 我们假设系统已经锁定,没有可用的尝试次数,我们想绕过检查,解锁它。

 

可用尝试次数存储在EEPROM中。 上电后,微控制器检查此数字,如果为零,则进入休眠状态。 我们想要在检查PIN输入剩余次数时,绕过将微控制器引入睡眠的分支指令。

 

目标程序如下:


START:

CALL GET_PIN_TRIES ; 检查还有多少次PIN码尝试的机会
ANDLW 0xFF
BTFSC STATUS, Z
GOTO GO_SLEEP ; 没有机会,开始休眠

BSF LED1 ; 还有尝试的机会

; ...
; Here, the PIN check and the rest of the application should be implemented
;

END_LOOP:
GOTO END_LOOP

GO_SLEEP:

BSF LED2 ; 休眠
SLEEP

GOTO GO_SLEEP ; Just in case it wakes up from the sleep

我们不能和前面一样随机启动毛刺,因为如果这样做,系统崩溃的几率很高。正确的方法是同步主机和目标,在GOTO指令执行的时候产生毛刺。我们还需要知道,从触发事件到敏感操作之间有多少个时钟周期。


触发事件可以选择输入事件(例如,按下按钮或总线上的输入命令),或输出事件(例如,打开的LED)。

 

我们这里,使用主机产生的复位信号作为触发事件,主机系统将保持16F88的RESET信号为低电平,使其处于复位状态。释放复位之后PIC开始运行,ARM开始计算注入延时,注入需要让PIC跳过GOTO GO_SLEEP指令的执行,从源代码我们知道,需要等待14个指令周期,也就是56个时钟周期。

 

一些实验之后,我发现在GOTO指令的第3,4,5个时钟周期注入效果最好。(注意!PIC架构中的分支操作需要8个时钟周期),所以我们应该等待58个周期而不是56个周期。下面的源代码是ARM代码的摘录,它在释放复位并等待58个时钟周期(GOTO指令的第3个周期)后注入3个周期的毛刺:


LOOP_WAITING_BUTTON1: ; 这个循环会保证目标在复位状态直到PB1被按下

str r4, [r0,#28] ; SYNCH = 0
str r1, [r0,#28] ; Clock = 0
str r5, [r0,#28] ; Reset = 0

movw r6, 0x003A ; 加载延时
movt r6, 0x0000

ldr r2, [r3,#0x14] ; 检查PB1是否已经被按下
tst r2, #0x1000
bne LOOP_WAITING_BUTTON1 ; 如果PB1没按下,循环

str r5, [r0,#24] ; Reset = 1

LOOP_1:

str r1, [r0,#24] ; Clock = 1
str r1, [r0,#24]
str r1, [r0,#24]
subs r6, 1

str r1, [r0,#28] ; Clock = 0
bne LOOP_1 ; If counter=0, break the loop

str r4, [r0,#24] ; SYNCH = 1
; 这个是用来触发示波器的

; 计数到0 ,开始执行glitch

str r1, [r0,#28] ; 延时一个周期
; Otherwise, the last clock period before glitching would be shorter

; 时钟被升高到60mhz
str r1, [r0,#24]
str r1, [r0,#28]
str r1, [r0,#24]
str r1, [r0,#28]
str r1, [r0,#24]
str r1, [r0,#28]

LOOP_WAITING_BUTTON2: ; 这是用来产生15mhz时钟信号的

str r1, [r0,#24] ; Clock = 1
str r1, [r0,#24]
ldr r2, [r3,#0x14] ; 检查PB2是否被按下

str r1, [r0,#28] ; Clock = 0
bne LOOP_WAITING_BUTTON2 ; If PB2 was not pressed, repeat the loop

b LOOP_WAITING_BUTTON1 ; Jump to the beginning

PB2通过控制复位信号把目标限制在复位状态,按下PB1之后,复位被释放,并且开始计时,在准确的时间点注入3个毛刺。此攻击将解锁目标系统,允许我们尝试另一个PIN,但在实际情况下,我们仍然必须绕过PIN检查,可能需要执行第二次故障攻击。

 

观察生成的毛刺的捕获:

 

 

复位后58个时钟周期注入毛刺。

 

注意:复位后的第一个时钟周期中的长低电平周期和毛刺是由于Cortex M3架构中分支指令的持续时间可变造成的。


7. 确定何时进行GLITCH


在最后一个例子中,我们有源代码,所以可以确定攻击的时间,但是,通常情况下我们是没有源代码的,我介绍两种方法确定注入的时间。

 

首先,我们可以尝试暴力破解,我们可以在特定的时钟周期中注入毛刺并检查是否发生了某些事情。 如果没有发生任何事情,我们将进入下一个周期并再次尝试。 


在某些时候,我们会命中我们正在寻找的指令并看到预期的效果。 我们还可以使用外部事件,例如I / O或总线中的状态更改,以缩小我们感兴趣的指令所在的位置。

 

此方法可以自动化,对于快速简单的漏洞检查非常有用。 不幸的是,如果我们的攻击需要跳过两个或更多GOTO而不是一个,那么在合理的时间内是不可能完成的。

 

另一种方法是使用侧信道分析技术来确定哪个时钟周期应该被glitch。

 

我将在另一个文章写这个,但基本上我的想法是,如果我们测量微控制器消耗的功率,不同指令的执行将产生不同的能量轨迹。 因为在大多数微控制器架构中, 分支指令需要清理执行流水线,需要两个指令周期,因此GOTO指令的“能量轨迹签名”很容易从其他指令中辨别出来。

 

此外,如果处理器由于安全检查失败而在无限循环上运行,则电源信号的自相关函数将显示循环的指令周期,并有助于猜测毛刺的位置。







看雪ID:backahasten

https://bbs.pediy.com/user-775585.htm  



本文由看雪论坛 backahasten   原创

转载请注明来自看雪社区




⚠️ 注意



2019 看雪安全开发者峰会门票正在热售中!

长按识别下方二维码即可享受 2.5折 优惠!




往期热门回顾

1、迅雷下载服务加速节点的来源分析

2、Android逆向----某CTF题

3、打造Wi-Fi “DOS”攻击工具——Wi-Fi_deauther

4、从0到Reverseshell:Mikrotik SMB漏洞实战(CVE-2018–7445)









公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com




点击阅读原文,了解更多!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存