UDS协议安全CTF挑战分析

挑战来源:https://vsec.blockharbor.io/
Challeng website:https://proving-grounds.blockharbor.io/

由于官方的在线似乎要$79,我是用yichen师傅的docker版本:https://github.com/yichen115/UDSCTF

Getting Started

Can you find the interface?

1
What is the name of the CAN interface available on the virtual terminal?

在页面:https://core.ed.vsec.blockharbor.io/block/test?tab=Simulations
打开uds challenge

使用ip link查到CAN interface为vcan0
image.png

UDS Challenge

VIN获取

题目:Retrieve the VIN of the simulation using UDS.

需要使用22UDS服务读取VIN码(VIN码:车辆识别码,俗称车架号,由17位字符(字母+数字)组成,每辆车独一无二)
0xF190代表VIN码请求,22为SID(服务号),F190为DID(数据标识符)
VIN比较长,所以需要一次请求,多帧响应,使用流控帧(FC)和连续帧(CF)

第一条7df#0322f190:发送一个UDS请求:读取VIN(DID=F190)

CAN ID:7DF(功能地址:广播给所有ECU)
数据部分:03 22 F1 90(ISO-TP单帧结构:[PCI][DATA…])
03是什么? PCL(Protocol Control Information)是相当于发送数据协议的指示
0x03 = 单帧 + 长度三字节(高4位0表示单帧,低四位3表示后面有3字节数据)
22 F1 90是什么? 22:服务号(ReadDataByIdentifier),F1 90:DID(VIN)

第二条:7E0#3000000000000000:这是ISO-TP Flow Control帧(FC)

7E0:ECU接收ID(Request ID)

FC结构:[PCI][BlockSize][STmin]…

PCL:

值 含义
30 继续发送
31 等待
32 中止

完整流程:

1
2
3
4
5
6
7
8
9
10
11
12
1. 我 → ECU
7E0#0322F190

2. ECU → 我
7E8#10 xx ... (First Frame)

3. 我 → ECU(FC)
7E0#30 00 00 ...

4. ECU → 我
7E8#21 ...
7E8#22 ...

image.png

接收到:

1
2
3
4
5
6
7
vcan0  7E0   [4]  03 22 F1 90
vcan0 7E8 [8] 10 1C 62 F1 90 55 44 53
vcan0 7E0 [8] 30 00 00 00 00 00 00 00
vcan0 7E8 [8] 21 43 54 46 7B 56 49 4E
vcan0 7E8 [8] 22 59 49 43 48 45 4E 30
vcan0 7E8 [8] 23 30 31 31 32 32 33 33
vcan0 7E8 [2] 24 7D

10 1C:(第一帧、高四位=1),1C:总长度 = 1C = 28字节
去除UDS响应头62 F1 90
21 42:去除21,(序号为1)

image.png

Level1 flag 获取 (安全级别1)

描述:需要理解 UDS 安全访问机制,完成 seed-key 交换,通过安全访问 Level1 后读取 DID:C1C2

UDS安全访问机制

UDS安全访问机制就是SecurityAccess,SID = 0x27,作用是:在执行敏感诊断操作前,先让测试仪“解锁ECU”

典型的敏感操作包括:刷写固件、写入数据、清除故障码、执行例程、控制ECU行为等

  1. 核心流程:Seed-Key

这不是一个账号密码,而是一个挑战-响应机制
流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Tester -> ECU: 27 01
请求安全等级 1 的 seed

ECU -> Tester: 67 01 xx xx xx xx
返回seed

Tester 本地计算:
key = f(seed)

Tester -> ECU:27 02 key
发送key

ECU -> Tester:67 02
解锁成功

27:SecurityAccess 服务
67:0x27的正响应,0x27 + 0x40 = 0x67

子功能 含义
0x01 请求 Level 1 seed
0x02 发送 Level 1 key
0x03 请求 Level 2 seed
0x04 发送 Level 2 key
0x05 请求 Level 3 seed
0x06 发送 Level 3 key
奇数为请求seed
偶数为发送key

seed为ECU返回给tester的随机数,例如67 01 A1 B2 C3 D4
seed = A1 B2 C3 D4

key是测试仪根据seed计算出来的结果,然后发回给ECU,如果匹配则解锁成功

响应情况:

1
2
3
请求: 27 01

响应:67 01 12 34 56 78

若key成功:

1
2
3
请求:27 02 xx xx xx xx

响应:62 02

若key失败:

1
2
3
4
5
响应:7F 27 35

7F:负响应
27:原始服务
35:错误key
响应码 含义
0x35 InvalidKey,key 错误
0x36 ExceedNumberOfAttempts,尝试次数过多(ECU有锁定时间)
0x37 RequiredTimeDelayNotExpired,延时未结束
0x24 RequestSequenceError,流程顺序错误
0x12 SubFunctionNotSupported,子功能不支持
0x22 ConditionsNotCorrect,条件不满足

不同安全等级对应不同权限。(由具体厂商定义)

例如:

等级 可能权限
Level 1 读取扩展数据、普通测试
Level 2 写配置、执行部分例程
Level 3 进入编程会话、刷写固件
Level 4+ 厂商/产线/高权限功能

要完成这个题目,通过安全访问 Level1

所以需要发送指令27 01,加上ISO-TP单帧长度字节:7E0#022701

1
2
vcan0  7E0   [3]  02 27 01
vcan0 7E8 [7] 06 67 01 33 E5 9E 79

image.png

与key(0xdeadbeef)异或之后得到ed482096,然后发送即可收到成功响应

1
2
vcan0  7E0   [7]  06 27 02 ED 48 20 96
vcan0 7E8 [3] 02 67 02

然后就可以去获取标识符C1C2对应内容

1
2
vcan0  7E0   [4]  03 22 C1 C2
vcan0 7E8 [8] 10 1E 62 C1 C2 55 44 53

拿到第一帧:10 1E表示首帧,1E表示总长度30字节
下一步发送流控帧 FC,让ECU继续发送后续连续帧
cansend vcan0 7E0#3000000000000000

1
2
3
4
5
6
7
8
vcan0  7E0   [4]  03 22 C1 C2
vcan0 7E8 [8] 10 1E 62 C1 C2 55 44 53
vcan0 7E0 [8] 30 00 00 00 00 00 00 00
vcan0 7E8 [8] 21 43 54 46 7B 32 37 5F
vcan0 7E8 [8] 22 73 65 63 75 72 69 74
vcan0 7E8 [8] 23 79 58 30 72 5F 43 31
vcan0 7E8 [4] 24 43 32 7D

注意这里的30 00 00 00 00 00 00 00

现在进行逐字节解析:
第一个30:Flow Control(FC)这不是普通数据,而是流控控制消息
30 00 00 的格式是:

字节 含义
0x30 Flow Control + Continue To Send (CTS)
0x00 Block Size(一次允许发送多少帧)
0x00 Separation Time(帧间隔)

image.png

27 Level3 flag 获取 (安全级别3)

描述: 需要切换到编程会话,并使用更复杂的密钥算法,通过安全访问 Level3 后读取 DID:C1C2

切换到编程会话:7E0#021002

02:ISO-TP单帧长度,表示后面有2个字节
10:UDS服务号,0x10 = DiagnosticSessionControl(诊断会话控制),相当于切换会话
02:子功能,0x02 = Programming Session(编程会话)

成功效应:50 02
50 = 0x10 + 0x40(正响应)
02 = 编程会话

1
2
vcan0  7E0   [3]  02 10 02
vcan0 7E8 [5] 04 50 02 00 32

请求seed:022703

用给定算法算出key

1
2
3
# 步骤3: 计算级别3 key (复杂算法)  
# key = ((seed << 7) | (seed >> 25)) ^ 0xCAFEBABE + 0x12345678
# key = (key & 0xFFFF0000) | ((key & 0x0000FFFF) ^ 0xABCD) ^ 0xDEADBEEF

发送回ECU验证:06270679278C05

然后获取D1D2数据:
cansend vcan0 7E0#0322D1D2
cansend vcan0 7E0#3000000000000000

image.png

dump 内存获取 flag (安全级别5 + 内存dump)

描述: 最高难度的挑战,需要完成级别 5 安全访问,然后通过 0x23 服务 dump 内存寻找 flag

获取级别5seed,然后计算出密钥,通过认证

1
2
3
4
vcan0  7E0   [3]  02 27 05
vcan0 7E8 [7] 06 67 05 17 A8 18 7C
vcan0 7E0 [7] 06 27 06 96 D9 AC 32
vcan0 7E8 [3] 02 67 06

然后通过 0x23 服务 dump 内存寻找 flag

通过认证解锁后,使用ReadMemoryByAddress按地址范围读取内存,最后在dump数据搜索flag

0x23 = ReadMemoryByAddress:地址 + 长度,而非通过DID读取
0x22为通过DID读取,这是它们的区别

1
2
3
4
5
6
# 使用0x23 ReadMemoryByAddress服务  
# 格式: 0x23 + 格式标识符 + 地址 + 大小
# 格式标识符0x14表示: 1字节大小 + 4字节地址

# 示例: 读取0x40000000开始的80字节
7DF#0723144000000050

复位启动 flag 获取

1
2
3
4
5
6
7
8
9
10
cansend vcan0 7E0#021101


ctfuser@1218612ee3c0:~/challenge$ candump vcan0 | grep 7E8
vcan0 7E8 [8] 10 1F 62 00 00 55 44 53
vcan0 7E8 [8] 21 43 54 46 7B 52 65 73
vcan0 7E8 [8] 22 65 74 5F 54 68 45 5F
vcan0 7E8 [8] 23 55 44 53 5F 53 65 72
vcan0 7E8 [5] 24 76 65 72 7D

image.png