介绍
在这篇博客文章中,我将描述一种故障注入攻击,以读取禁用了编程器访问权限的瑞萨 R7F701381(RH850/P1M-E 系列)的闪存内容。这款微控制器专为安全关键应用(ASIL-D)而设计,具有第二核心,可以以锁步方式运行并检查第一核心。指令高速缓存和 RAM 具有 ECC 功能。
这个项目的原因是我正在尝试从 2021 年丰田 RAV4 Prime 的电子动力转向(EPS)模块中转储固件。这是使用 Autosar 的新 SecureOnboard Communication(SecOC)标准的新丰田车型之一,用于验证 CAN 消息。这意味着 EPS 不再接受来自第三方设备的伪造 CAN 消息。希望将来我会写更多关于这个的内容。
不幸的是,这辆车的零件太罕见,无法在废品场找到,所以必须购买新的零件($$$)。我以前查看过的现代和本田 EPS 模块留下了宽敞的调试访问权限,但对于这个 EPS 来说并非如此。这意味着仅仅读取固件将是解决这个 SecOC 问题的第一个挑战。
故障注入攻击的灵感来自 Franck Jullien 对 Renesas RX65 的重要研究。然而,这种攻击在几个方面有所不同。RH850 使用 V850 架构而不是 RX,这意味着引导 ROM 可能不同。我使用双线(UART)调试接口,而不是单线 FINE 接口。Franck 还必须绕过 ID 代码(密码)检查,而在这个设备上,编程接口本身被“禁用”。
Collin O'Flynn 在他 2020 年的论文中对 NXP MPC55xx 和 MPC56xx 进行了有趣的研究:“BAM BAM!!关于在现场汽车 ECU 攻击中 EMFI 可靠性的研究”。在这篇论文中,他能够使用电磁故障注入绕过 Boot Assist Module 中的密码检查。这项研究很重要,因为 MPC55xx/MPC56xx 也是一种汽车芯片,并且具有与 RH850/P1M-E 相似的 ASIL-D 安全特性。
首先,我将描述该芯片使用的调试协议。然后,我将介绍故障注入,并描述在此攻击中使用的硬件和软件。本文中使用的所有代码都可以在 GitHub 上找到。
双线调试协议
读取固件的第一步是将其连接到计算机。幸运的是,这款瑞萨芯片支持一种双线协议,可以使用标准的 5V USB-TTL 电缆连接到计算机。要启用调试模式,只需使用上拉电阻将 FLMD0
引脚拉高。 JP0_0
和 JP0_1
必须连接到 USB-TTL 适配器上的 RX
和 TX
。
在与用于安全关键应用的微控制器一起工作时,您必须始终注意外部看门狗。这也是这个 EPS 的情况。如果外部看门狗不满意,它将通过短暂拉低复位线来强制重置微控制器。在进入引导加载程序后不久,芯片将被重置。通过将 100 欧姆电阻连接到+5V,强制将复位线拉高,从而解决了这个问题,这足以压倒外部看门狗。为了仍然能够控制复位线,我在复位和 GND 之间添加了一个 N-FET,可以使用 USB-TTL 适配器上的 DTR
线软件控制。
稍微焊接后,一切都连接好了,我可以使用瑞萨闪存编程器进行通信。不幸的是,事情并不那么简单,我收到一条消息说“此设备禁止串行连接”。在我尝试绕过这个问题之前,我首先需要了解闪存编程器和设备之间的协议。这可以让我了解可能存在的弱点,并让我构建自己的闪存器实现。
尽管没有我的确切芯片的文档,但您可以从其他芯片的文档中学到很多。瑞萨在许多芯片上使用了相似的相同调试协议实现。在这种情况下,“RX65N 组,RX651 组用户手册:硬件”(第 59.13 节,引导模式通信协议)和应用笔记“瑞萨 RA 系列标准引导固件的系统规格”都被证明非常有用。Greg Hogan 还为 V850 和 SH72 提供了 Python 实现。
连接逻辑分析仪并尝试连接后,我可以观察到 PC 和设备之间的通信。命令有两种类型:一种是回复简单的 OK,另一种是可以请求更多数据的。命令以 0x01
或 0x81
开头,后跟两个字节的长度。每个数据包以 1 字节的校验和 2 和一个常数 0x03
结束。
在以下示例中,使用命令 0x32
(频率设置命令),您可以看到设备返回一个简单的 ACK
,但当回显相同的 ACK
时,它会发送实际数据。在尝试使用读取命令读取闪存时,这将在以后变得相关。
Renesas Flash Programmer GUI 首先经过几个步骤设置设备时钟频率和波特率。然后,它发送一个“同步”命令,返回错误 0xDC
(串行编程禁用错误)。如果您跳过其中任何步骤,或尝试在不调用同步的情况下发出读取命令,您将收到错误 0xC3
(流程错误)。这进一步得到 RX65 数据表中的流程图的确认,该图显示了进入命令等待阶段所需的所有步骤,您应该能够发出读取命令。查看流程图后,我得到了这样一个想法,即同步命令是设备检查程序员连接是否被允许的唯一位置。如果您成功跳转到命令等待阶段,您应该能够做任何您想做的事情。
故障注入
故障注入是一种方法,其中您在 CPU 在正常操作范围之外运行。这可以通过(非常简要地)改变操作电压(电压故障)或时钟频率(时钟故障)来实现。还可以通过向芯片发送电磁或激光脉冲来影响其行为。
电压和时钟故障都可以使用相对便宜的硬件来实现,比如 FPGA 甚至是一个小型微控制器和少量外部部件。
故障注入的主要目标是影响微控制器的行为。这可能是由于直接跳过指令,或导致内存读取/写入失败。
在这种情况下,我将对微控制器的电源电路执行电压干扰。通过在 100 纳秒或更短的时间内短路电源,您可以修改运行程序中非常特定部分的行为。
具体来说,我正在针对接收到同步命令后检查编程是否已启用的部分。 命令的最后一个字节和响应的第一个字节之间的时间约为 100 微秒。 鉴于故障注入的持续时间很短,可能需要相当长的时间才能找到正确的时序。
电压干扰 - 硬件
在 RH850 数据表的 9.3.1 节中,显示了电源电路的方框图(见下图)。您可以看到 CPU 核心(数字电路)的电压是由内部 1.25V 稳压器(在图中标记为 eVR)生成的。为了稳定电压,它被引出到外部引脚上连接一个电容器( VCL
)。这个引脚是一个非常方便的点来执行故障注入。它对芯片的其余外围设备几乎没有影响,并且不受核心电压监视器的检查。
由于 RH850 用于安全关键应用,它有第二个核心作为检查器。可能有两个 VCL 引脚,可能每个核心一个。它们可能需要同时出现故障以达到期望的结果。否则,结果可能是核心不同步,设备将重置。
将 VCL 引脚上的去耦电容器移除,否则会平滑掉毛刺。然后将两个 VCL 引脚连接到一个 N-FET(我手头上有 DMN2050L,但任何快速 N-FET 都可以)由 Raspberry Pi Pico 驱动。下面显示了 PCB 的图片。两个 N-FET 被粘在板上,并使用短线连接。左下角的连接器用于连接到调试接口。
电压干扰 - 软件
故障是由一些代码生成的 Raspberry RP2040 生成的。固件的重要部分就是这么简单:
1. if (uart_getc(uart0) != '\x01') continue;
2. if (uart_getc(uart0) != '\x00') continue;
3. if (uart_getc(uart0) != '\x01') continue;
4. if (uart_getc(uart0) != '\x00') continue;
5. if (uart_getc(uart0) != '\xff') continue;
6.
7. busy_wait_at_least_cycles(real_delay);
8. gpio_put(GLITCH_PIN, 1);
9. busy_wait_at_least_cycles(width);
10. gpio_put(GLITCH_PIN, 0);
RP2040 等待同步命令(减去最后 \ x 03
),稍等片刻然后生成一个短脉冲。其余固件处理通过
USB 配置脉冲的延迟和宽度。
在 PC 端有一个实现调试协议并控制 RP2040 的 Python 脚本。它不断尝试通过同步命令,并循环遍历各种各样的故障参数。大约经过一天的尝试后,我取得了第一个成功。在我对故障参数有一个大致的了解后,每次后续尝试只需要 5 到 30 分钟。
通过同步命令后,确实可以发出读取内存命令!花了一些时间找出正确的起始和结束地址(该命令仅适用于整个 8kb/32kb 块),我成功地转储了整个固件。
更新 2022-11-09:
我还尝试过只对单个 VCL
引脚进行故障。只对引脚 11 上的单个 VCL
进行故障似乎也有效。然而,当只对引脚 66 上的 VCL
进行故障时,我可以通过同步命令,但然后内存读取仍然失败,出现流错误。这种效果可能是由于主核心和检查核心的设置,尽管我不太确定为什么对检查核心进行故障会导致任何结果。
结论
通过这种攻击,可以绕过 RH850/P1M-E 上的程序员禁用设置。它不需要复杂的硬件,并且可以在不拆下微控制器的情况下完成。我注意到,为了获得任何结果所需的故障长度通常会锁定整个微控制器,需要进行完全重置。在这种情况下,没有进一步设置 ID 代码或密码来保护接口。尽管这听起来可能有些多余,但由于需要两个连续的故障,这将大大增加攻击的复杂性。在这次攻击中,我绕过了同步命令,发出了一个读取命令,这个命令是在一个编程接口被禁用的设备上执行的。也许还可以通过干扰读取命令本身来读取受保护的区域。然而,要倒出整个固件,需要对每 32kb 的闪存成功进行一次干扰。这可能是提取引导 ROM 的一个好方法。
- 接口实际上并没有被禁用。您仍然可以协商波特率和时钟速度。相反,当尝试进入命令等待阶段时,设备会告诉您接口实际上无法使用。
0x100 - sum ( packet [ 1 :]) & 0xFF