0x00 前言
在过去几年的研究之中,我们对于故障注入(Fault Injection,FI)格外关注。我们发表了许多文章,并且在学术会议和安全会议上发表过多次演讲。通过长期的研究,我们认为FI是绕过Secure Boot和实现Linux上特权提升的有效技术。
与你们许多人一样,在研究的过程中,我的好奇心被不断激发,因此也在故障注入的领域越陷越深。本篇文章将主要介绍我们在故障注入方面的最新研究成果。
0x01 历史研究成果(2016-2019)
我们在POC2019的演讲中概括地描述了曾在2016年FDTC上发表的研究成果,在当时的研究中,我们证明了“数据传输”的过程中可以利用FI的方式实现“任意代码执行”。在现场演示中,我们使用了开源的iceGLITCH将电压Glitch(指电压波动,超压或欠压)注入到Espressif ESP32中。更具体地来说,我们展示了在攻击者仅能控制传输的数据的情况下,如何使用FI修改callx8指令,以实现对程序计数器的任意控制。
最重要的是,根据我们在2016年的研究,程序计数器可以在直接寻址的处理器体系结构(例如ARMv7)上进行控制。根据我们在2019年的研究成果,即使程序计数器不能直接寻址(例如ARMv8),也可以对其进行控制。甚至,可以利用类似memcpy函数这样的通用软件结构。
当攻击者仅控制正在传输的数据时,就可以执行此类攻击。数据传输的来源和目的都不需要控制,只需要控制数据自身。此外,攻击者可以利用由特权代码(例如内核)执行的通用软件结构,这些特权结构会从不受信任的域(例如通信接口、U盘等外部数据源)复制数据,理论上可以从任何设备上获取数据。一旦底层硬件容易受到FI攻击,特别是许多标准芯片都容易受到攻击,那么无论处理器是什么架构,都可以实现对程序计数器的完全控制。
在披露之前,我们已经首先将研究结果提交给了Espressif。我们重点分析了ESP32芯片的潜在风险。尽管如此,由于该结果是在针对测试代码的完全受控环境中实现的,因此我们一直认为当时的影响最小。
0x02 研究成果(2020)
时间快进到2020年初,我们有了新的硬件FI工具来支持我们的研究工作。我们尝试在老目标上测试新的功能。在这里,我们决定专注于使用电磁(EM)Glitch。我们将这种技术称为EMFI。
首先从一种经典的方法开始,我们使用完全受控制的测试代码作为目标,确认ESP32是否具有对电磁Glitch的弹性(鲁棒性)。如果能够确定ESP32存在风险,我们就可以将2019年的研究成果付诸实践。
首选要做的,是绕过ESP32的Secure Boot实现。在我们开始研究时,就有几位研究人员已经发布了针对这种实现的FI攻击,Espressif也为此发布了公告。与他们的攻击方式不同,我们计划使用电磁Glitch代替电压Glitch来触发类似的硬件漏洞。我们计划采用的EMFI方法具有很多优点,包括:
1、通常不需要对目标进行侵入式修改;
2、由于电磁场具有确定的空间分布,因此只需在局部发起攻击。
值得关注的是,除了重现已被发现的攻击之外,我们还找到了几种利用漏洞的新方法,这些方法可以让我们绕过Secure Boot和Flash Encryption。我们还发现了一些新的漏洞,Espressif也同样真对这些漏洞发布了公告。Espressif表示这些攻击不再适用于更新后的ESP32 V3和ESP32-S2芯片。
在这篇文章中,我们将深入探究使用EMFI绕过未启用Flash Encryption的Secure Boot的方法,也就是复现CVE-2019-15894。我们接下来也会发布其他几篇文章,介绍我们新发现的漏洞,即CVE-2020-15048和CVE-2020-13629。
0x03 准备工作
经过十多年的FI测试与研究,我们从第一手经验中了解到,发现和利用FI漏洞可能需要花费较长时间。尽管理论上漏洞发现与复现过程非常复杂,但如果使用一些专家级的工具,就可以大幅简化发现漏洞、复现已知漏洞的过程。因此,我们在FI研究的过程中,通常使用一些商业化的工具。
但与此同时,使用低成本(例如ChipShouter)或DIY(例如BADFET)的工具也可以实现相同的结果。然而,这种类型的工具可能有一定局限性,并且通常情况下不能充分地清除故障参数搜索空间。尽管如此,我们认为,一旦发现并利用了FI漏洞,并且已知所需的故障参数,就可以调整或构建低成本工具,以实现成功的故障注入。
为了增加成功的概率,同时确保能够有效地扫描故障参数搜索空间,我们计划使用以下组件来进行FI研究:
· Riscure Spider
· Riscure XYZ stage
· Riscure EM-FI Transient Probe
· Riscure Inspector FI Python
我们在Intel NUC上执行Inspector FI Python框架,以便与上述组件进行通信。准备过程的思路如下所示:
下图展示了实际的准备工作,同时我们向ESP32注入了EM Glitch。
0x04 测试目标
我们使用了AZ Delivery提供的ESP32 NodeMCU模块,如下图所示。这些模块是基于ESP32-WROOM-32。
我们将ESP32-WROOM-32封装的金属外壳拆下,以便将EMFI探针的尖端放到芯片表面上。这是我们需要对目标进行的唯一修改,用于注入EM Glitch。
0x05 定位
在进行EMFI时,最重要的Glitch参数是位置、功率和时序。要调整这些参数并不容易,特别是在针对特定目标(例如Secure Boot)进行尝试的时候。组合后的参数搜索空间可能巨大,对于使用精细网格进行空间定位的场景来说难度较高。通常情况下,我们从一个中间步骤开始,该步骤可以让我们高效地确定芯片可能存在EM Glitch攻击的确切位置。
需要说明的是,在做好准备工作后,第一步是确定目标是否容易受到攻击。由于这种漏洞是存在于芯片级别(即硬件)上,因此我们可以在开发板上测试,也可以在最终漏洞上测试,都能达到同样的效果。对于我们来说,如果可能的话,我们通常更喜欢使用带有相同或相似芯片设计的开发板进行最初的研究。然后,将同样的攻击方式复现到最终设备上,特别是在软件相同的情况下(例如ROM代码),这种复现过程通常会比较简单。
5.1 测试代码
在中间步骤中,我们使用的测试代码通常是命令处理程序,其中包含至少一个命令,该命令执行以下操作:
1、接收命令字节;
2、将触发信号设置为高电平;
3、使用汇编程序,用已知值初始化未使用的寄存器;
4、使用汇编程序,增加带有增加指令的计数器(即:增加sled);
5、将触发信号设置为低电平;
6、打印计数器结果。
下面是我们在对ESP32进行研究过程中使用的一种实现。测试代码的作用是通过观察串行端口上的计数器来识别是否出现了故障。如果计数器的值与预期值(即10000)不同,这说明我们注入的Glitch会影响目标的执行,但不会阻止程序的继续运行。
char cmd; unsigned int counter; while(1) { cmd = -1; uart_rx_one_char(&cmd); // receive command if(cmd == 'A') { GPIO_OUTPUT_SET(26, 0); // trigger high asm volatile ( // set unused registers "movi a0, 0x40404040;" "movi a1, 0x41414141;" "movi a2, 0x42424242;" "movi a3, 0x43434343;" "movi a4, 0x44444444;" "movi a5, 0x45454545;" // "movi a6, 0x46464646;" "movi a7, 0x47474747;" "movi a8, 0x48484848;" "movi a9, 0x49494949;" "movi a6, 0;" "addi a6, a6, 1;" // start add sled < repeat 10,000 times > "mov %[counter], a6;" : [counter] "=r" (counter) : : "a6", "a0", "a1", "a2", "a3", "a4", "a5", "a7", "a8", "a9" ); GPIO_OUTPUT_SET(26, 1); // trigger low } printf("AAAA%08xBBBB\n", counter); // send result back }
我们通过GPIO引脚来生成触发高电平信号和低电平信号。这样一来,我们就拥有了一个可测量的攻击窗口期,能够充分利用增加指令的计数器。这样一来,就增加了故障注入的成功概率,让我们可以识别硬件漏洞是否存在。
5.2 定位
我们使用XYZ Stage自动地将EMFI探针在芯片表面移动。Spider会一直等待,直至触发器设置为高电平为止,这样一来就实现了在攻击窗口期内进行故障注入。窗口期由触发器为高电平和触发器变为低电平的时刻定义。我们将攻击窗口期的时间和EMFI探针的功率进行随机化。在最开始的尝试中,使用了30x30的网格。
根据观察到的行为,将所有的实验进行分组,并为每组确定一种颜色。经过多次实验,其结果如下:
1、绿色的点表示没有明显的效果;
2、黄色的点表示芯片出现了重置(reset)或静音(mute);
3、红色菱形表示出现了不同的计数器值,即成功触发了故障注入;
4、红色十字表示出现了异常,例如非法指令。
我们在8.5小时左右进行了大约165000次实验,在每次实验中,我们都需要移动EM-FI探头,并在最大功率的10%到100%之间随机分配Glitch的功率。
实验结果如下。对于每种类型,我们都提供了一个在串行接口上观察到的示例。
类型:预期
响应:AAAA00002710BBBB
数值:165404
类型:异常
响应:Fatal exception (0): IllegalInstruction
数值:6
类型:重置/静音
响应:ets Jun 8 2016 00:22:57….rst:0x1 (POWERON_RESET)
数值:1489
类型:成功
响应:AAAA0000270ABBBB
数值:1
红色菱形表示我们能够成功引入Glitch的位置,这个Glitch会引发故障,从而影响计数器的增量,但同时不影响芯片的连续运行。尽管我们仅仅发现了一个成功的Glitch,但它足以证明ESP32存在EM Glitch的风险。
我们实现了第一个目标,即确定了一个硬件漏洞。接下来,让我们看看是否可以通过引入允许我们进行实际攻击的漏洞来实现利用。我们决定将EMFI探针修复到绕过Secure Boot成功的Glitch的位置。这样便可以将故障参数的搜索空间降低一个维度。
0x06 漏洞利用
Espressif IoT开发框架(ESP-IDF)中包含许多便捷的工具,包括编译软件(idf.py)、刻录eFuses(espefuse.py)、生成密钥(espsecure.py)、对内存进行编程(esptool.py)。我们在寻找漏洞利用方法的过程中,广泛利用了这些工具。
6.1 执行完整性检查
进行完整性检查的目的是验证Secure Boot是否已经启用并会按照预期工作,做到有备无患。一种思路是让bootloader无效,然后观察串行接口输出存在的差异。如果没有串行接口输出,也可以适用其他信息,例如与闪存进行通信或芯片的功耗。ESP32的串行接口无法禁用,因此这种方法是始终可行的。
重启电源后,ROM(绿色)和bootloader(红色)打印以下内容:
+ ets Jun 8 2016 00:22:57 + + rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:2 + load:0x3fff0008,len:4 + load:0x3fff000c,len:3220 + load:0x40078000,len:4816 + load:0x40080400,len:18640 + entry 0x40080740 - I (86) boot: Chip Revision: 1 - I (87) boot_comm: chip revision: 1, min. bootloader chip revision: 0 - I (42) boot: ESP-IDF v3.3.1 2nd stage bootloader - I (42) boot: compile time 16:14:32 - I (42) boot: Enabling RNG early entropy source... - I (46) boot: SPI Speed : 40MHz - I (50) boot: SPI mode : DIO - I (54) boot: SPI Flash Size : 2MB - - Hello, I am the bootloader
我们通过将字符串bootloader(在上述串行端口输出的最后一行中可见)修改为Raelize!!!的方式,导致bootloader无效。然后,将修改后的bootloader编程到ESP32的外部闪存中。然后,当我们启动ESP32时,我们观察到串行接口上打印了以下输出:
+ ets Jun 8 2016 00:22:57 + + rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:2 + load:0x3fff0008,len:4 + load:0x3fff000c,len:3220 + load:0x40078000,len:4816 + load:0x40080400,len:18640 + csum err:0xb5!=0xdf + ets_main.c 371
ROM会验证bootloader的校验和,由于我们已经做了修改,因此校验和已经不匹配。由于在串行接口上打印了所需的信息,因此很容易识别内存映像中存在的校验和值。我们只需要将0xb5更改为0xdf,就可以使校验和与修改后的版本匹配。如预期的那样,ROM现在会提示Secure Boot检查失败。
+ ets Jun 8 2016 00:22:57 + + rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:2 + load:0x3fff0008,len:4 + load:0x3fff000c,len:3220 + load:0x40078000,len:4816 + load:0x40080400,len:18640 + secure boot check fail + ets_main.c 371
现在,我们有了Secure Boot实现,可以考虑通过利用硬件漏洞来绕过它。
6.2 判断攻击时间
在定位阶段,我们已经明确了EMFI探针的位置,并深入了解所需的电源。遗憾的是,基于GPIO引脚的触发器已经无法使用,因为验证过程是由ROM代码执行的,这部分无法修改。因此,我们需要一个新的参考点,来确定FI攻击的时间。
我们尝试使用复位信号作为触发,但发现这种方式存在明显的都懂。因此,我们决定使用闪存活动来计算攻击时间,它通常可以作为触发的重要信号,因为它通常与软件执行密切相关,特别是在Secure Boot期间。
下图蓝色表示的是闪存pin1上的活动,红色表示的是我们在此活动中获得的触发信号。
在这个研究阶段,我们并不完全清楚每个时刻(从A到G)都发生了什么。但是,我们知道在A之前就让芯片退出的复位状态,并且可以假设bootloader是在F期间的最后阶段才复制的。如果在闪存上编写了有效的bootloader,则芯片会在G期间的某个地方开始执行bootloader。
6.3 执行攻击
我们决定在F之后,立刻在10微妙的攻击窗口期注入Glitch。每个实验的攻击周期如下所示,这样一来我们每秒可以进行约10次实验。
1、下拉EN引脚,使芯片保持复位状态;
2、上拉EN引脚,使芯片从复位状态释放;
3、等待17毫秒,直到E和F之间的闪存活动间隙;
4、等待闪存引脚1的第一次下降,将触发器设置为高电平;
5、在攻击窗口内,注入使用随机功率和定时的EM故障;
6、将串行接口输出存储在数据库中以备后期处理。
经过约35000次实验(耗时约55分钟),我们观察到3个成功的故障,利用电磁故障绕过了Secure Boot。我们可以轻松地识别成功的Glitch,因为此时的Raelize!!!会在串行接口上打印,而非bootloader,如下所示。
+ ets Jun 8 2016 00:22:57 + + rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) + configsip: 0, SPIWP:0xee + clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 + mode:DIO, clock div:2 + load:0x3fff0008,len:4 + load:0x3fff000c,len:3220 + load:0x40078000,len:4816 + load:0x40080400,len:18640 + entry 0x40080740 - I (86) boot: Chip Revision: 1 - I (87) boot_comm: chip revision: 1, min. bootloader chip revision: 0 - I (42) boot: ESP-IDF v3.3.1 2nd stage bootloader - I (42) boot: compile time 16:14:32 - I (42) boot: Enabling RNG early entropy source... - I (46) boot: SPI Speed : 40MHz - I (50) boot: SPI mode : DIO - I (54) boot: SPI Flash Size : 2MB - - Hello, I am the Raelize!!!
下图展示了所有实验的结果,其中X轴是时间(纳秒),Y轴是功率(最高功率的百分比)。其中的三个红色菱形,表示三次成功的Glitch。
这些实验的统计数据如下所示:
类型:预期
数量:19494
占比:56.54%
类型:重置/静音
数量:14981
占比:43.45%
类型:成功
数量:3
占比:0.01%
我们可以通过调整故障参数来确定攻击的成功率。当我们采用完全相同的故障参数(包括位置、功率和时序)时,就可以实现每分钟2次成功Glitch的平均值。这表明,一旦获知了Glitch的参数,就能够轻松地复现攻击。
0x07 总结
我们已经证明,ESP32容易受到EMFI的攻击,并且成功利用这个硬件漏洞实现了对Secure Boot的攻击。尽管此前已经有研究人员发布过类似攻击,但我们也证明了EMFI相对于其他故障注入技术具有比较明显的优势。
对于这种Secure Boot攻击,我们没有假设任何特定的故障模型,因为我们仅仅利用了可观察到的芯片行为变化。使用完善的扫描技术,再加上有效的定时触发,就可以构造出成功的Secure Boot攻击。
此外,这些初步研究结果也表明了,我们可能有机会利用更为完善的故障模型实现漏洞利用。我们期望可以将以前发表的研究成果付诸实践,将复现变为执行原语。如果一切顺利,也许可以发现强大的新攻击方式,而不仅仅是使用单个Glitch来绕过Secure Boot。
除了本文所描述的攻击之外,我们还进行了一些不同的尝试。如同任何研究一样,可能并不会一帆风顺,但我们还是使用了一种有创意的方式来绕过Secure Boot和Flash Decryption。
重要的不是漏洞,而是如何利用漏洞来揭示出其真正的价值。
后续研究成果将在接下来的几周内发布,敬请期待。
本文翻译自:https://raelize.com/posts/espressif-systems-esp32-bypassing-sb-using-emfi/如若转载,请注明原文地址: