CTF 训练

简介

默认情况下,RAMN ECU 实现了一些小型 CTF 挑战,可用于练习汽车领域的夺旗(CTF)赛事。

标志以明文形式存在于固件文件和源代码中;目标是开发正确的命令或脚本,在不查看源代码或二进制文件的情况下提取标志。标志的格式为 flag{xxx}

挑战

USB

  • 非常简单 :通过 USB 发送命令“^”即可访问一个标志。
  • 简单 (需要脚本):通过发送命令“&”及一个五位数字密码(例如“&12345”)可访问另一个标志。

CAN

传输

  • 非常简单 :如果你发送任何 ID 为 0x456 的消息,将广播一个带有 CAN ID 0x770 的标志。
  • 非常简单 :如果你发送一个 ID 为 0x457、有效载荷为“GIVEFLAG”的 CAN 消息,就会广播一个 CAN ID 为 0x771 的标志。
  • 非常简单 :如果你发送一个带有特定 ID 的远程帧,就会广播一个 CAN ID 为 0x772 的标志。
  • 中级 :如果你发送一个特定的 CAN 消息,并通过以下函数进行检查,就会广播一个 CAN ID 为 0x773 的标志:
static uint8_t checkIfShouldSendFlag4(const FDCAN_RxHeaderTypeDef* pHeader, const uint8_t* data) { 8001ccc: b480 push {r7} 8001cce: b083 sub sp, #12 8001cd0: af00 add r7, sp, #0 8001cd2: 6078 str r0, [r7, #4] 8001cd4: 6039 str r1, [r7, #0] 8001cd6: 687b ldr r3, [r7, #4] 8001cd8: 689b ldr r3, [r3, #8] 8001cda: 2b00 cmp r3, #0 8001cdc: d12d bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001cde: 687b ldr r3, [r7, #4] 8001ce0: 681b ldr r3, [r3, #0] 8001ce2: 4a19 ldr r2, [pc, #100] @ (8001d48 <checkIfShouldSendFlag4+0x7c>) 8001ce4: 4293 cmp r3, r2 8001ce6: d128 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001ce8: 683b ldr r3, [r7, #0] 8001cea: 781b ldrb r3, [r3, #0] 8001cec: 2b50 cmp r3, #80 @ 0x50 8001cee: d124 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001cf0: 683b ldr r3, [r7, #0] 8001cf2: 3301 adds r3, #1 8001cf4: 781b ldrb r3, [r3, #0] 8001cf6: 2b34 cmp r3, #52 @ 0x34 8001cf8: d11f bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001cfa: 683b ldr r3, [r7, #0] 8001cfc: 3302 adds r3, #2 8001cfe: 781b ldrb r3, [r3, #0] 8001d00: 2b24 cmp r3, #36 @ 0x24 8001d02: d11a bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d04: 683b ldr r3, [r7, #0] 8001d06: 3303 adds r3, #3 8001d08: 781b ldrb r3, [r3, #0] 8001d0a: 2b24 cmp r3, #36 @ 0x24 8001d0c: d115 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d0e: 683b ldr r3, [r7, #0] 8001d10: 3304 adds r3, #4 8001d12: 781b ldrb r3, [r3, #0] 8001d14: 2b57 cmp r3, #87 @ 0x57 8001d16: d110 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d18: 683b ldr r3, [r7, #0] 8001d1a: 3305 adds r3, #5 8001d1c: 781b ldrb r3, [r3, #0] 8001d1e: 2b30 cmp r3, #48 @ 0x30 8001d20: d10b bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d22: 683b ldr r3, [r7, #0] 8001d24: 3306 adds r3, #6 8001d26: 781b ldrb r3, [r3, #0] 8001d28: 2b52 cmp r3, #82 @ 0x52 8001d2a: d106 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d2c: 683b ldr r3, [r7, #0] 8001d2e: 3307 adds r3, #7 8001d30: 781b ldrb r3, [r3, #0] 8001d32: 2b44 cmp r3, #68 @ 0x44 8001d34: d101 bne.n 8001d3a <checkIfShouldSendFlag4+0x6e> 8001d36: 2301 movs r3, #1 8001d38: e000 b.n 8001d3c <checkIfShouldSendFlag4+0x70> 8001d3a: 2300 movs r3, #0 8001d3c: 4618 mov r0, r3 8001d3e: 370c adds r7, #12 8001d40: 46bd mov sp, r7 8001d42: f85d 7b04 ldr.w r7, [sp], #4 8001d46: 4770 bx lr 8001d48: 0077cafe .word 0x0077cafe

接收

发送一个 ID 为 0x458、有效载荷为空的消息,以启用以下标志的周期性传输。

  • 简单 :有一个标志位,通过 CAN ID 为 0x6F0 逐位广播。
  • 中级 :有一个标志位,通过 CAN ID 为 0x6F1 逐位广播。
  • 困难 :有一个标志位,通过 CAN ID 为 0x6F2 逐位广播(标志长度为 19 个字符)。
注意

提示 :标志始终以 ASCII 字符串“flag”开头。

UDS

  • 非常简单 :ECU D 在 ID 为 0x0001 处保存了一个标志,可通过标识符读取服务进行读取。
  • 简单 :ECU D 持有一个可在 ID 0x0002 处读取的标志,但您需要先进行身份验证。
  • 简单 (需要脚本):ECU D 持有另一个可通过标识符数据读取服务读取的标志。
  • 简单 :ECU D 持有一个标志,可通过地址 0x01234567 处的按地址读取内存操作访问,大小为 17 (0x11)。
  • 中级 (需要脚本):ECU D 具有一个自定义的 UDS 服务,其 ID 为 0x40,你能创建一个有效的请求吗?

漏洞利用(进阶)

有两个 UDS 例程实现了易受攻击的密码检查,且它们需要采用略有不同的利用方法。你能利用这些例程来恢复之前的所有标志吗?

目标并不是恢复密码或绕过检查,而是劫持执行流程以转储内存(通过 USB、CAN 等)。有关如何开始的指导,请参阅为 RAMN 编写 ARM shellcode。你可以自行编译源代码以生成.elf 和.map 文件。虽然 JTAG 调试器并非绝对必要,但它会使漏洞利用更加容易。在完成安全访问后,你需要使用例程控制服务(参见与 UDS 交互 )。如果你已经熟悉 ARM 漏洞利用,还可以为自己增加一些未强制执行的额外限制(例如禁止在 RAM 中执行任何代码)。

例程控制 0x20A

1const char expected_password[] = "VULNERABILITY";
2static void RAMN_UDS_RoutineControlVulnerabilityExample(uint8_t* data, uint16_t size)
3{
4        if (checkAuthenticated() == True)
5        {
6                char stack_password[sizeof(expected_password)];
7
8                if ((size > 4U) && (size < ISOTP_RXBUFFER_SIZE))
9                {
10                        data[size] = 0U; //zero-terminate buffer
11                        strcpy(stack_password, (char*)&data[4U]); //copy string
12                        if (strcmp(stack_password, expected_password) == 0U) RAMN_UDS_FormatPositiveResponseEcho(data, 4U);
13                        else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_ROOR);
14                }
15                else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_IMLOIF);
16        }
17        else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_SAD);
18}

例程控制 0x20B

警告

我们不会在每次更新后定期检查此题是否可解,但很可能仍然是可解的。

1const char expected_password[] = "VULNERABILITY";
2char ctf_global_password[sizeof(expected_password)];
3static void RAMN_UDS_RoutineControlVulnerabilityExample2(uint8_t* data, uint16_t size)
4{
5        if (checkAuthenticated() == True)
6        {
7                if ((size > 4U) && (size < ISOTP_RXBUFFER_SIZE))
8                {
9                        data[size] = 0U; //zero-terminate buffer
10                        strcpy(ctf_global_password, (char*)&data[4U]); //copy string
11                        if (strcmp(ctf_global_password, expected_password) == 0U) RAMN_UDS_FormatPositiveResponseEcho(data, 4U);
12                        else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_ROOR);
13                }
14                else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_IMLOIF);
15        }
16        else RAMN_UDS_FormatNegativeResponse(data, UDS_NRC_SAD);
17}