- 12V 转 5V 降压电路
- 5V 转 3.3V LDO
- STM32 微控制器
- 两个 CAN接口
CAN 接口连接到电缆的每一端,使设备能够在仪表盘显示屏与汽车的其余ECU之间进行中间人攻击,从而可以过滤或修改发送或接收的任何消息。
首先,我们来研究排针的用途。使用万用表的连通性模式,我们可以追踪微控制器的每个引脚与排针的连接情况。根据 STM32 上的标签:
- 顶部引脚连接到 PA13
- 中间引脚连接到 PA14
- 底部引脚连接到地(GND)
发现了 SWD接口!
Connecting Debugger连接调试器:
现在我们可以读取寄存器了
> stm32f1x options_read 0
option byte register=0x3fffffe
write protection register=0xffffffff
read protection: on
watchdog: software
One Exploit to Rule Them All
尝试复现《One Exploit to Rule them All?》中攻击来读取这颗受读保护的STM32F105 。
Wiring up the Target
幸运的是,GitHub 上有 CTzX 提供的适用于 Pi Pico 的攻击代码(https://github.com/CTXz/stm32f1-picopwner),其中包含加载到 STM32 SRAM 的payload 。在连接任何设备之前,先将此固件烧录到 Pi Pico 中。将在漏洞利用部分讨论此固件的功能。
该攻击需要对 STM32 的电源进行严格控制,并且能够快速响应 nRST 的变化,因此需要移除 3v3 电源线上的旁路电容和 nRST 上的电阻。此外,还将移除 BOOT0 引脚上的电阻,以便控制 STM 的启动模式。
所有需要移除的组件都用粉色标记了出来 :
移除这些元件后,便可以开始将 STM32 连接到 Pi Pico 上。
请注意:BOOT1 需要始终保持高电平状态。
我们还需要重新连接调试器,以便将payload写入 SRAM。
Exploit
正如之前提到的,即使处于锁定状态,这款 STM32 仍然允许一定程度的调试访问,并且可以通过 BOOTCFG 引脚从 SRAM 启动。当连接调试器时,对 Flash 的访问会被禁用,只有在电源复位后才会重新启用。从 SRAM 启动时,对 Flash 的访问也会被禁用。此次攻击的第一步是将一个分两阶段的利用程序加载到 STM32 的 SRAM 中。
一旦加载了攻击代码,我们先断开调试器,接着拉高 BOOT0 引脚,让设备从 SRAM 启动。此时,由于之前连接过调试器,Flash 仍然处于锁定状态,并且会一直保持到电源循环复位,但电源循环复位会导致我们 SRAM 中的有效载荷丢失。重点是,切断电源时,SRAM 里的内容不会马上消失。我们在给设备降压时盯着 nRST 线,等 nRST 线的信号恢复正常,马上把电供回去,这样设备就能重新访问 Flash,而且 SRAM 里的重要数据也不会丢。
以下是一张 Saleae 的 VCC 故障截图:
接下来,我们从SRAM启动,执行流程将从`_start`开始,这是第一阶段。在`entry.S`中,此方法仅设置一个闪存补丁块并进入等待循环。
@ -- Stage 1 --
@ Set FPB to remap the reset vector to SRAM_start:
ldrr0, =0xe0002000@ FP_CTRL, see https://developer.arm.com/documentation/ddi0337/e/System-Debug/FPB/FPB-programmer-s-model
movs r1, #3@ FP_CTRL: Enable FPB
movs r2, #0x20@ FP_REMAP: Remap to stage 2 vtable entry (0x20)
movs r3, #0x05@ FP_COMP0: Set COMP0 to (rst_vector | EN) = 0x05
stm r0!, {r1, r2, r3}@ Apply to FPB
waitrst:
b waitrst@ Wait for NRST pin to be toggled
Flash Patch Blocks主要用于调试,能够重映射读取操作或设置断点,且这些设置在设备复位后依然有效。该攻击利用其重映射模式,将真实的闪存入口点替换为我们存储在 SRAM 中的第二阶段方法。
接下来,我们将芯片配置为从flash启动,并通过 nRST 引脚对其进行复位。当芯片启动时,它会寻找入口点,此时Flash Patch Blocks生效,导致执行流程从第二阶段开始。由于芯片是“通过flash启动”的,因此第二阶段的处理程序可以完全访问flash。其在 entry.S
中的处理程序调用了 main.c
,而 main.c
的作用仅仅是通过 UART 将flash的内容转储出来 :
// Called by stage 2 in entry.S
intmain(void)
{
iwdg_enabled = (_WDG_SW == 0); // Check WDG_SW bit.
refresh_iwdg();
usart = init_usart1();
/* Print start magic to inform the attack board that
we are going to dump */
for (uint32_t i = 0; i < sizeof(DUMP_START_MAGIC); i++)
{
writeChar(DUMP_START_MAGIC[i]);
}
uint32_t const *addr = (uint32_t *)0x08000000;
while (((uintptr_t)addr) < (0x08000000U + (1024UL * 1024UL))) // Try dumping up to 1M. When reaching unimplemented memory, it will cause hard fault and stop.
{
writeWord(*addr);
++addr;
}
while (1) // End
{
refresh_iwdg(); // Keep refreshing IWDG to prevent reset
}
}
以下是 Saleae 逻辑分析仪对整个过程的截图:
Load in Ghidra
将固件加载到 Ghidra 中,已知该架构为小端序的 Cortex,其 Flash 位于地址 0x8000000。
根据数据手册,我们先要把 RAM 放在 0x20000000 这个地址位置,然后在地址 0 的地方做一个 Flash 的镜像。最后,用 SVD 加载器把芯片里剩下的外设都映射好。
通过查看字符串信息,我们发现 UART 上使用了类似 AT 指令风格的命令,这些命令很可能被用于蓝牙应用程序的通信,尽管我们并未购买此功能。或许,如果我们购买一个蓝牙模块并自行连接,它就能正常工作。
Reverse Engineering
先从复位向量入手,看看设备启动时是怎么初始化的。在 ARM 固件里,初始程序计数器的值就在 Flash 里,从开头往后数,排在初始堆栈指针值后面第四个位置。一般来说,在裸机固件中,顺着每个函数的最后一次调用一路追踪下去,就能找到程序的主循环。
通过对app_loop
的分析,开始了解这个设备的真实功能。伪代码如下:
void app_loop()
{
if (10000 < ticks_since_fwd_can_msg) {
disable_bluetooth();
}
can_fwd_car2ipc();
can_fwd_ipc2car();
if (pending_config_changed_notif) {
send_config_changed_notif();
}
uart_command_handler();
if (selected_config != *p_flash_selected_config) {
flash_program(p_flash_selected_config, selected_config);
}
}
原文始发于微信公众号(安全脉脉):“里程停止器”拆解分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论