绕过ESP32加密的安全引导

2020-09-22 17:44:59

我们到达了关于我们对ESP32的故障注入研究的最后一篇文章。请阅读我们之前的帖子,因为它为本帖子中描述的结果提供了上下文。

在我们对ESP32的故障注入研究过程中,我们逐步向前迈进了一步,以确定所需的漏洞,这些漏洞使我们能够通过单个EM故障绕过安全引导和闪存加密。此外,我们不仅实现了代码的执行,还从芯片中提取了纯文本的FLASH数据。

Espressif为这篇帖子中描述的攻击请求了CVE:CVE-2020-13629。请注意,本帖子中描述的攻击仅适用于ESP32硅片修订版0和1。较新的ESP32 V3硅片支持禁用我们用于攻击的UART引导加载程序的功能。

ESP32在其ROM代码中实现了UART引导加载程序。除其他功能外,该功能还允许对外部闪存进行编程。在ROM代码中实现这样的功能并不少见,因为它非常健壮,因为代码不容易损坏。如果此功能将由存储在外部闪存中的代码实现,则闪存的任何损坏都可能导致设备被砖封。

通常,通过在特殊引导模式下引导芯片来访问此类型的功能。引导模式选择通常使用在复位芯片之前设置的一个或多个外部带销来完成。在ESP32上,它的工作原理与外部暴露的引脚G0完全相同。

UART引导加载器支持许多有趣的命令,这些命令可用于读/写内存、读/写寄存器,甚至从SRAM执行存根。

UART引导加载器支持使用load_ram命令加载和执行任意代码。ESP32';的SDK包括编译可以从SRAM执行的代码所需的所有工具。例如,下面的代码片断将在串行接口上打印SRAM代码。

Esptool.py工具是ESP32';SDK的一部分,可用于将编译后的二进制文件加载到SRAM中,之后将执行编译后的二进制文件。

有趣的是,即使启用了安全引导和闪存加密,UART引导加载程序也无法禁用,因此始终可访问。

显然,如果不采取额外的安全措施,让UART引导加载器始终可访问会使安全引导和闪存加密变得无用。因此,Espressif实施了使用专用eFuse启用的附加安全措施。

这些是在特殊存储器(通常称为OTP存储器)中实施的安全配置位,通常只能从0更改为1。这保证一旦启用,将永远启用。当ESP32处于UART引导加载程序引导模式时,以下OTP存储器位用于禁用特定功能。

最相关的OTP存储器位是DISABLE_DL_DECRYPT,因为它禁用闪存数据的透明解密。

如果未设置,则可以在ESP32处于其UART引导加载程序引导模式时简单地访问纯文本闪存数据。

如果设置,当芯片处于UART引导加载程序引导模式时,对闪存的任何访问都将只产生加密数据。闪存加密功能完全在硬件中实现,对处理器透明,仅当ESP32处于正常引导模式时才启用。

ESP32使用的SRAM存储器是许多芯片使用的典型技术。它通常用于ROM的堆栈,并从闪存执行第一个引导加载程序。它在早期启动时使用很方便,因为它通常不需要配置就可以使用。

我们从以前的经验中了解到,存储在SRAM存储器中的数据是持久的,直到它被覆盖或从物理单元中移除所需的电源。芯片冷重置(即重启电源)后,SRAM将重置为其默认状态。由于每个比特的缺省值(即,0或1)是不同的,这通常是半随机的并且每个码片是唯一的。

但是,在热重置之后,整个芯片在不断电的情况下被重置,存储在SRAM中的数据可能保持不受影响。数据的这种持久性如下图所示。

我们决定弄清楚这种行为是否也适用于ESP32。我们发现硬件看门狗可用于从软件发出热重置。该看门狗也可以在芯片处于UART引导加载程序引导模式时发出,因此我们可以使用它将ESP32复位回正常引导模式。

通过使用UART引导加载器加载并执行SRAM中的一些测试代码,我们确定SRAM中的数据在使用看门狗发出热重置后确实是持久的。实际上,这意味着我们可以使用充满控制数据的SRAM在正常引导模式下引导ESP32。

我们设想,我们可能能够利用SRAM中的数据在热重置期间的持久性进行攻击。我们提出的第一个攻击是使用UART引导加载程序用代码填充SRAM,并使用看门狗发出热重置。然后,当ROM代码在正常引导期间使用闪存引导加载程序覆盖此代码时,我们会注入一个毛刺。

我们得到这个想法是因为在我们之前的实验中,我们将数据传输转换为代码执行,我们注意到,对于一些实验,芯片在引导加载程序完成复制之前就从入口地址开始执行。

我们使用UART引导加载器加载到SRAM中的代码如下所示。

#定义";addi a6,a6,1;";#定义t a#定义h t#定义d h void__attribute__((Noreturn))call_start_cpu0(){uint8_t cmd;ets_printf(";SRAM code\n";);而(1){cmd=0;UART_RX_ONE_CHAR(&;cmd);IF(cmd==';A';){//1*(无符号整数*)(0x3ff4808c)=0x4001f880;*(无符号整数*)(0x3ff48090)=0x00003a98;*(无符号整数*)(0x3ff4808c)=0xc001f880;}}ASM易失性(D);//2";movi a6,0x40;slali,a6,24;";//3";movi A7,0x00;slvi a7,a7,16;";";xor a6,a6,a7;";movi A7,0x7c;slli A7,a7,8;";";xor a6,a6,a7;";";movi a7,0xf8;";xor a6,a6,a7;";";";movi A10,52;callx8 a6;";//R";movi A10,61;callx8 a6;";//a";movi a10,65;callx8 a6;";//e";movi a10,6C;callx8 a6;";//l";movi a10,69;callx8 a6;";//i";movi a10,7A;callx8 a6;";//z";movi a10,65;callx8 a6;";//e";movi a10,21;callx8 a6;";//!";movi a10,0A;callx8 a6;";//\n While(1);}。

我们对每个实验采取了以下步骤,以确定攻击想法是否真的有效。一个成功的故障将打印Raelize!在串行接口上。

将引脚G0设置为低并执行冷复位以进入UART引导加载程序引导模式。

向程序发送A以发出热重置进入正常引导模式。

我们的目标是F开头的一个相当小的攻击窗口,如下图所示。我们从以前的实验中了解到,在这一时刻,闪存引导加载程序被复制。在有效的闪存引导加载程序完全覆盖SRAM中的代码之前,必须注入毛刺。

在运行这些实验一天多之后,产生了100多万个实验,我们没有观察到任何成功的毛刺…。

其中一个实验的串行接口输出(如下所示)表明故障导致了非法指令异常。

ETS Jun 8 2016 00:22:57rst:0x10(RTCWDT_RTC_RESET),boot:0x13(SPI_FAST_FLASH_BOOT)configsip:0,SPIWP:0xeeclk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,HD_drv:0x00,wp_drv:0x00 mode:dio,lock div:2load:0x3fff0008,len:4load:0x3fff000c,len:3220load:0x400000,len:4816load:0x40080400,len:18640entry 0x00,d_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:dio,lock div:2load:0x3fff000c,len:3220load:0x400000,len:4816load:0x40080400,len:18640entry 0x00,d_drv:0x00,HD_drv:0x00 mode:0x00,c2=0x3fff0008,len:4load:0x3ffff000c,len:3220load:0x400000,len:4816load:0x40080400,len:18640entry 0x00,d_drv:0x00。

当芯片中注入故障时,这种类型的异常经常会发生。这对ESP32来说也没有什么不同。对于大多数例外,PC寄存器设置为预期的值(即有效地址)。这种情况并不经常发生,PC寄存器设置为如此有趣的值。

由于0x661b661b地址没有存储有效指令,导致了非法指令异常。我们得出的结论是,这个值一定来自某个地方,也就是说,它不可能神奇地在PC寄存器中结束。

我们分析了加载到SRAM中的代码,以便找到解释。下面显示了二进制代码的一段代码,它很快给出了我们想要的答案。值0x661b661b在上面的二进制图像中很容易识别。它实际上表示两条addia6、a6、1指令,我们在测试代码中实现了其中的1000条指令。

00000000 E9 02 10 28 04 08 40 ee 00 00 00|...(..@.|00000010 00 00 00 01 00 00 ff 3f 0C 00 00 00|.|00000020 53 52 41 4d 20 43 4f 44 45 0A 00 00 00 04 08 40|sram code.@|00000030 50 09 00 00 00 ff 3f 04 fe3f 4d 04 08 40|P.?..?m..@|00000040 00 04 fe 3f 8c 80 f4 3f 90 80 f4 3f 98 3a 00 00|...?.:..|00000050 80 f8 01 c0 54 7d 00 40 d0 92 00 40 36 61 00 a1|...@6a.|00000060 f5 ff 81 fc ff e0 08 00 0C 08 82 41 00 ad 01 81|.A...|00000070 fa ff e0 08 00 82 01 00 4c 19 97。98 1f 81 ef ff|.L.|00000080 91 ee ff 89 09 91 ee ff 89 09 91 f0 ff 81 ee ff|.|00000090 99 08 91 ef ff 81 EB ff 99 08 86 f2 ff 5c A9 97|.\..|000000a0 98 c5 1b 66 1b 66 1b 66 1b 66 1b 66 3e 0c|...F.F>;|000000b0 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66|.f.f|000000c0 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66。66|.f.f|00000340 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66|.f.f|00000350 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66 1b 66|.f.f|。

我们只是使用这些说明作为NOP,以便以类似于NOP-SLED的方式创建着陆区,NOP-SLED通常用于软件漏洞利用。我们没有预料到这些指令最终会出现在PC寄存器中。

当然,我们也不介意。我们的结论是,当ROM代码复制闪存引导加载程序时,如果我们注入了一个毛刺,我们就能够将数据从SRAM加载到PC寄存器中。

我们很快意识到,我们现在已经具备了编造攻击的所有要素,只需一个小故障就可以绕过安全引导和闪存加密(Secure Boot)和闪存加密(Flash Encryption)。我们重用了在前面描述的控制PC寄存器的攻击期间获得的一些知识。

我们重用了以前使用UART引导加载器加载到SRAM中的大部分代码。只有我们打算执行的有效负载(即打印)被删除,因为我们现在的策略是将PC寄存器设置为任意值以便进行控制。

#定义";addi a6,a6,1;";#定义t a#定义h t#定义d h void__attribute__((Noreturn))call_start_cpu0(){uint8_t cmd;ets_printf(";SRAM code\n";);而(1){cmd=0;Uart_rx_one_char(&;cmd);if(cmd==';A';){*(无符号整数*)(0x3ff4808c)=0x4001f880;*(无符号整数*)(0x3ff48090)=0x00003a98;*(无符号整数*)(0x3ff4808c)=0xc001f880;}}ASM Volatile(D);While(1);}。

编译上述代码后,我们使用地址指针0x4005a980在二进制文件中直接覆盖addi指令。此地址指向ROM代码中的一个函数,该函数在串行接口上打印内容。这使我们能够确定什么时候我们是成功的。

我们将毛刺参数修复为导致非法指令异常的实验参数。过了一会儿,我们成功地识别了几个实验,在这些实验中,地址指针被加载到PC寄存器中。有效地,这为我们提供了对PC寄存器的控制,并且我们很可能实现任意代码执行。

不幸的是,我们没有给您一个可靠的答案。我们绝对没有预料到在目的地控制数据会产生对PC寄存器的控制。我们提出了几种可能性,但我们不能完全肯定地说,这些可能性中是否有任何一种确实是正确的。

一种解释是,毛刺可能损坏LDR指令的两个操作数,以便将值从目的地加载到A0中。这与前面描述的攻击类似,在该攻击中,我们通过控制源数据间接控制PC。

此外,ROM代码也有可能实现为此攻击提供便利的功能。换句话说,我们可能会在ROM中执行有效代码,因为我们的毛刺会导致SRAM中的值被加载到PC寄存器中。

需要进行更彻底的调查,才能确定究竟是什么允许我们实施这次攻击。然而,从攻击者的角度来看,它足以实现如何获得PC的控制权,以建立利用。

即使我们控制了PC寄存器,我们仍然无法从闪存中提取纯文本数据。我们决定利用UART引导加载器功能来做到这一点。

我们决定在芯片处于正常引导模式时直接跳转到UART引导加载程序。对于此攻击,我们使用指向UART引导加载程序(0x0x40007a19)开始位置的地址指针覆盖加载到SRAM的代码中的addi指令。

UART引导加载程序在串行接口上打印一个字符串,如下所示。我们可以用它来确定我们是否成功。

一旦我们观察到实验成功,我们就可以简单地使用esptool.py发出一个read_mem命令来访问纯文本闪存数据。下面的命令从映射外部闪存的地址(0x3f400000)读取4个字节。

不幸的是,这并没有奏效。由于某些原因,处理器返回0xbad00ad,这表明我们从未映射的页面读取。

Esptool.py v2.8串行端口COM8正在连接...正在检测芯片类型...。ESP32Chip is ESP32D0WDQ6(修订版1)Crystal is 40 MHzMAC:24:6f:28:24:75:08启用默认SPI闪存模式...0x3f400000=0xbad00badsting in Bootloader。

我们注意到在UART引导加载器开始时进行了相当多的配置。我们认为它可能也会影响MMU。

为了尝试一些不同的东西,我们决定直接跳到UART引导加载程序本身的命令处理程序(0x40007a4e)。一旦进入处理程序,我们就可以直接在串行接口上发送原始的read_mem命令,如下所示。

遗憾的是,通过直接跳转到处理程序,将不再打印打印的字符串(即等待下载\n&34;)。因此,我们不能轻易确定成功的实验。因此,我们决定简单地始终发送命令,无论我们是否成功。我们使用非常短的串行接口超时,以便将几乎总是达到超时的开销降到最低。

在这篇文章中,我们描述了对ESP32的攻击,我们使用单个EM故障绕过了它的安全引导和闪存加密功能。此外,我们利用该攻击利用的漏洞从加密闪存中提取明文数据。

有趣的是,ESP32的两个弱点促成了这次攻击。首先,UART引导加载器不能被禁用,并且始终可访问。其次,加载到SRAM中的数据在热重置期间是持久的,因此可以使用UART引导加载器填充任意数据。

Espressif在与此攻击相关的建议中指出,较新版本的ESP32包含完全禁用此功能的功能。

所有标准的嵌入式技术都容易受到故障注入攻击。因此,这并不令人惊讶的是,ESP32也是脆弱的。这些类型的芯片根本不是为了抵御这些类型的攻击而制造的。然而,这一点很重要,这并不意味着这些攻击不会带来风险。

我们的研究表明,利用芯片级弱点进行故障注入攻击是非常有效的。我们还没有看到很多公开的例子,因为大多数攻击仍然集中在传统的方法上,而这些方法的重点主要是绕过检查。

我们认为,故障注入攻击的全部潜力仍未被发掘。直到最近,大多数研究都集中在注入方法本身(即激活、注入和毛刺)上,而不是将其与易受攻击的芯片所能实现的(即故障、利用和目标)进行比较。

我们相信,创造性地使用新的和未定义的故障模型,将导致不可预见的攻击,其中使用令人兴奋的利用策略,以实现各种不同的目标。