已经有很多论文都记述了使用错误注入攻击密码学设备或者嵌入式系统,但是他们倾向于介绍对注入结果的处理,而缺少介绍整个注入流程。他们只会泛泛的介绍,针对了什么产品(FPGA或者其他产品),使用什么错误注入方法,得到了什么。但是如果你希望找到原理图或者代码来尝试复现论文所叙述的内容,就会发现细节很少,困难重重,因此,很多读者就会认为错误注入是一个很高难度的攻击方法,需要攻击者拥有很强的专业知识和很贵重的攻击设备。这同时也给硬件安全带来虚假的错觉。
然而,事实是错误注入完全可以使用简单和便宜的工具进行,例如,之前,XBOX360被爆出可以通过错误注入攻破的漏洞,00年代中期,电视的智能卡可以被一个称为unloopers的攻破。
今天,我会向您展示如何使用低于15美元的设备进行时钟毛刺攻击。
译者注:在这里,作者介绍了一个叫做giant的开源项目。该项目支持错误注入和侧信道攻击,但是已经在2016年末停止更新。Chipwhsiperer可以代替此项目,并且更加便宜,成本大概在250美元左右。
错误注入攻击指的是在计算设备中,故意引入毛刺,以期望改变软件硬件逻辑的执行流。错误注入一般期望有两种效果,避免执行和破坏正在处理的数据,这些可以用来绕过安全认证和泄露密码学算法的密钥。
有几种典型的错误注入攻击方法,最常见和最容易的是时钟和电压毛刺注入,还有其他,例如激光注入,电磁注入和热辐射注入。
时钟毛刺注入是指让时钟频率在一小段时间内突然增加。
在集成电路中(微控制器,处理器,FPGA),由于长度,走线中的寄生电容电感分布等,时钟信号会不均匀分布,且不会同时到达所有作用点。制造商会规定一个最大频率,在这个频率下,可以保证时钟信号可以正确的到达每一个寄存器。如果超出这个频率,芯片就不会正常运行。但是,如果我们强制芯片仅仅在一条指令周期超出频率,之后恢复正常,下一条指令就会正常。
时钟注入最想要的效果就是跳过一条指令的执行,也可以实现其他效果,比如让芯片加载错误的数据或者破坏已经存储的数据。建议阅读,“An In-depth and Black-box Characterization of the Effects of Clock Glitches on 8-bits MCUs” (Balasch, Gierlichs and Verbauwhede) 详细说明了针对Atmel芯片,不同的毛刺产生的不同影响。
时钟错误注入的典型应用是避免执行跳转指令,如果大家在破解软件的时候更改过JNE之类的指令来绕过保护,就可能感觉特别相似了。通过时钟注入,我们可以跳过某些逻辑分支的执行。
由于速度有要求,大多数研究和商业产品使用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的两个按钮用来出发和产生毛刺。
我们尝试使用一个简单的实验理解毛刺的效果。
我们的目标板在一个无限Loop中,我们使用一个错误注入打破该循环。
这是我们目标PIC中源代码的主要部分。
正如程序所示,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执行。
核心代码如下所示:
在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的第三个时钟周期中注入,会有最佳效果。
进行时钟错误注入的时候,电压可能至关重要。
为了现实电压的影响,我修改了ARM的系统时钟,以80mhz而不是120mhz运行,其余不变。PIC的时钟毛刺就是40mhz(25ns)而不是60mhz了。
在5v时,几乎不起作用,然而,降低典雅,攻击的成功率一下子高了,2.7v电压下,这个电压远低于数据手册中的4V,几乎每次都能成功。
https://youtu.be/ysv5LM-Vutg
电压会影响信号的传播掩饰,有时候改变电压更有利于攻击,请注意,某些控制器有内部稳压系统,他们不会有影响(或者说影响不大)
请注意,我们是电压保持稳定而不是短时间降低电压(power glitch)。温度是另外一个因素,也可以影响信号的传播帮我们实现故障。
前面的例子非常简单。 让我们再试一次,但使用更现实的场景。
在这种情况下,我们将攻击要求用户输入PIN码的假定系统,并且在三次尝试失败之后,它将锁死并且无法使用。 我们假设系统已经锁定,没有可用的尝试次数,我们想绕过检查,解锁它。
可用尝试次数存储在EEPROM中。 上电后,微控制器检查此数字,如果为零,则进入休眠状态。 我们想要在检查PIN输入剩余次数时,绕过将微控制器引入睡眠的分支指令。
目标程序如下:
我们不能和前面一样随机启动毛刺,因为如果这样做,系统崩溃的几率很高。正确的方法是同步主机和目标,在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个周期的毛刺:
PB2通过控制复位信号把目标限制在复位状态,按下PB1之后,复位被释放,并且开始计时,在准确的时间点注入3个毛刺。此攻击将解锁目标系统,允许我们尝试另一个PIN,但在实际情况下,我们仍然必须绕过PIN检查,可能需要执行第二次故障攻击。
观察生成的毛刺的捕获:
复位后58个时钟周期注入毛刺。
注意:复位后的第一个时钟周期中的长低电平周期和毛刺是由于Cortex M3架构中分支指令的持续时间可变造成的。
在最后一个例子中,我们有源代码,所以可以确定攻击的时间,但是,通常情况下我们是没有源代码的,我介绍两种方法确定注入的时间。
首先,我们可以尝试暴力破解,我们可以在特定的时钟周期中注入毛刺并检查是否发生了某些事情。 如果没有发生任何事情,我们将进入下一个周期并再次尝试。 在某些时候,我们会命中我们正在寻找的指令并看到预期的效果。 我们还可以使用外部事件,例如I / O或总线中的状态更改,以缩小我们感兴趣的指令所在的位置。
此方法可以自动化,对于快速简单的漏洞检查非常有用。 不幸的是,如果我们的攻击需要跳过两个或更多GOTO而不是一个,那么在合理的时间内是不可能完成的。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-4-18 21:23
被backahasten编辑
,原因: