UDS 服务仿真实验

👨‍💻 最近在 Github 上找到一个比较符合学习需求的 UDS 模拟器,拿来补充了一下,顺带搞个仿真实验教程。

UDS 服务仿真实验

参考资料:

📌 本文中的理论知识部分均摘自上述参考资料

UDS:Unified Diagnostic Services,中文名称统一诊断服务,简称UDS。UDS协议是当前使用最为广泛的车载控制器诊断协议。UDS 既可应用于CAN总线,又可应用于以太网、LIN总线等。

环境搭建

  • 本文测试使用系统:Ubuntu 22.04

  • 本文实验所用模拟器:uds-server-simulator

    • 模拟应用于 CAN 总线上的 UDS 诊断协议
1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装依赖环境
sudo apt install can-utils
pip3 install python-can
# 启用虚拟 can 接口
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up
# 编译模拟器
git clone https://github.com/ex7l0it/uds-server-simulator
cd ./uds-server-simulator && make
# 启动模拟器(使用 config.json 中的 TBOX ECU配置)
./uds-server-simulator -e TBOX vcan0
# 另开终端会话,启动 candump 监听 can 通信数据
candump vcan0

CAN 数据帧

首先了解一下 CAN 数据帧结构:

摘自canfd.net

简写 全称 中文含义
N_PCIType Network Protocol Control Information Type 网络层协议控制信息类型
SF_DL Single Frame Data Length 单帧数据长度
N_Data Network Layer Data 网络层数据
FF_DL First Frame Data Length 首帧数据长度
SN Sequence Number 序号
FS Flow Status 流控帧状态
BS Block Size 块大小(允许一次最大可发送连续帧数)
STmin Separation Time Minium 最小间隔时间
CF Consecutive Frame 连续帧、后续帧
  • N_PCIType = 0 时,表示当前帧为单帧,首字节的低 4bit 代表当前帧后续的数据长度
  • N_PCIType = 1 时,表示将要发送的数据不足以在单个帧中发送完毕,需要拆分,当前帧为首帧
  • N_PCIType = 2 时,表示当前帧为拆分发送的数据,当前帧为连续帧,SN表示当前的帧序号
  • N_PCIType = 3 时,表示当前帧为流控帧,能够调整 CF N_PDUs 发送速率

0x10 Diagnostic Session Control

服务简介

诊断会话控制服务,SID: 0x10

该服务内容详解:《UDS协议从入门到精通》系列——图解0x10:诊断会话控制

对应 ISO-14229-1 页码:36

UDS 协议中定义了三种会话模式,同一时间只能处于一种会话模式下,10 服务则用于切换会话模式:

  • 默认会话:上电默认进入的会话,在切换到任意非默认会话时,默认会话中的诊断功能同样可用
  • 编程会话
  • 扩展会话

推荐的UDS诊断服务配置方式见下表(摘自canfd.net):

SID(0X) Service 默认会话 编程会话 扩展会话
10 Diagnostic Session Control Y Y Y
11 ECU Reset Y Y Y
27 Security Access - Y Y
28 Communication Control - - Y
3E Tester Present Y Y Y
85 Control DTC Setting - - Y
22 Read Data By Identifier Y Y Y
2E Write Data By Identifier - Y* Y*
14 Clear Diagnostic Information Y - Y
19 Read DTC Information Y - Y
2F Input Output Control By Identifier - - Y*
31 Routine Control - - Y*
34 Request Download - Y* -
36 Transfer Data - Y* -
37 Request Transfer Exit - Y* -

ngt4n5mg.1dr

  • $x^a$:在默认会话模式中是否也允许0x86服务是特定于实现的,即不一定支持该服务;
  • $x^b$:如果是访问安全相关的DID则需基于安全访问服务,因此如果是该情况下要进非默认会话;
  • $x^c$:如果是访问安全相关的内存区域则需要安全访问服务,因此如果是该情况下要进非默认会话;
  • $x^d$:可以在默认和非默认会话中动态定义DID,因此非默认会话也支持这个服务;
  • $x^e$​:如果是安全相关例程需安全访问服务,因此需要非默认会话模式;需要客户端主动停止的例程也需要非默认会话模式。

请求 / 响应数据包分析

使用 cansend 发送诊断会话控制请求,请求更改会话模式为扩展会话模式:

1
$ cansend vcan0 733#0210030000000000

查看 candump 捕获到 vcan0 接口上的 can 报文数据:

1
2
vcan0  733   [8]  02 10 03 00 00 00 00 00
vcan0 73B [8] 06 50 03 00 32 01 F4 00

0x10 服务请求数据帧:

1720663353541

Sub-Function 的 1 Byte 数据代表 SessionType:

SessionType 含义
0x00 保留未使用
0x01 默认会话模式(Default Session),ECU上电后默认进入的模式,该模式无需 0x3E 服务进行维持
0x02 编程会话模式(Programming Session),主要用于 ECU 软件升级刷写
0x03 扩展会话模式(Extended Diagnostic Session),用于解锁需要高权限的诊断服务,基本覆盖各类服务(最常见的是读写 DID 前先进入扩展会话模式)
0x03 安全模式(Safety System Diagnostic Session),使能所有跟车载系统安全相关的服务(如安全气囊)

当前使用的 UDS 模拟器暂不支持 0x03 安全模式

0x10 服务肯定响应数据帧:

1720665925832

  • P2Server_max:表示 ECU 在收到请求与给出响应的时间间隔为 50 ms (0x32)
  • P2* Server_max表示 ECU 发送 NRC 0x78 之后继续发送下帧诊断响应报文的时间间隔为 5000ms (0x01F4 * 10)

0x10 服务否定响应数据帧:

  • 使用 cansend 发送一个长度为 3 Byte 的请求,得到否定响应:
1
2
vcan0  733   [8]  03 10 03 00 00 00 00 00
vcan0 73B [8] 03 7F 10 13 00 00 00 00

1720679290434

可能出现的 NRC(否定响应码):

NRC 描述 含义
0x12 sub-functionNotSupported 子功能参数不受支持
0x13 incorrectMessageLengthOrInvalidFormat 消息长度错误
0x22 conditionsNotCorrect 不满足请求标准/条件

0x22 ReadDataByIdentifier

服务简介

通过 ID 读数据,SID: 0x22

该服务内容详解:《UDS协议从入门到精通》系列——图解0x22:通过ID读数据

对应 ISO-14229-1 页码:106

  • DID:Data Identifier,数据标识符,能够用来标识数据的代号

  • 22 服务能够查询指定 DID 对应的数据

  • ISO 14229-1 中 337 页的附录中定义了一部分 DID 含义,其中一些 常见的DID:

DID 描述 含义
0xF180 BootSoftwareIdentificationDataIdentifier ECU 引导软件标识
0xF187 vehicleManufacturerSparePartNumberDataIdentifier 备件编号
0xF18B ECUManufacturingDateDataIdentifier ECU 生产日期
0xF190 VINDataIdentifier 车辆 VIN 码

请求 / 响应数据包分析

使用 cansend 发送请求读取 DID 为 0xF190 (VIN)的数据:

1
2
3
4
#!/bin/bash
cansend vcan0 733#0210030000000000
cansend vcan0 733#0322F19000000000
cansend vcan0 733#3000000000000000

查看捕获到 的 can 报文数据:

1
2
3
4
5
6
7
8
9
10
# 请求切换到扩展会话模式
vcan0 733 [8] 02 10 03 00 00 00 00 00
# 确认切换到扩展会话模式
vcan0 73B [8] 06 50 03 00 32 01 F4 00
# ReadDataByIdentifier:
vcan0 733 [8] 03 22 F1 90 00 00 00 00
vcan0 73B [8] 10 14 62 F1 90 4C 55 41
vcan0 733 [8] 30 00 00 00 00 00 00 00
vcan0 73B [8] 21 55 32 41 55 42 33 47
vcan0 73B [8] 22 45 33 38 33 34 36 37

0x22 服务请求数据帧:

1720677506735

0x22 服务可以请求一个或多个DID(但实验所使用的 uds 模拟器暂不支持请求多个DID)

0x22 服务不支持 sub-function

0x22 服务肯定响应数据帧(单个DID):

由于数据超出一个 CAN 帧能够携带的数据长度,需要分段发送,接收到一个分段数据的首帧:

1720679348704

发送流控帧,请求后续的数据:

1720679085007

接收到后续的连续帧数据:

1720679634417

拼接即得到 VIN(0xF190) 数据:LUAU2AUB3GE383467

0x22 服务否定响应数据帧:

使用 cansend 发送一个请求不存在的 DID 的数据:

1
2
vcan0  733   [8]  03 22 DE AD 00 00 00 00
vcan0 73B [8] 03 7F 22 31 00 00 00 00

0x22 服务支持的否定响应代码:

NRC 描述 含义
0x13 incorrectMessageLengthOrInvalidFormat 消息长度错误
0x14 responseTooLong 响应消息太长,比如一个请求包含多个DID,超过传输协议允许最大长度
0x22 conditionsNotCorrect 当前条件不满足
0x31 requestOutOfRange 请求的DID对当前设备或在当前会话不受支持,参数错误
0x33 securityAccessDenied 安全访问错误,比如访问的DID数据是安全数据,但当前等级未解锁

0x22 服务 NRC 处理流程:

2h0tgvpm.ofv

0x27 SecurityAccess

服务简介

安全访问,SID:0x27

该服务内容详解:《UDS协议从入门到精通》系列——图解0x27:安全访问

对应 ISO-14229-1 页码:47

  • ECU 中某些重要数据会受到安全保护,对其进行读或写操作时需要先申请安全访问,解锁对应的安全等级后才能进行后续操作
  • 例如在通过 0x2E 服务对指定 DID 进行写操作时,或通过0x34 服务进行请求下载操作时通常需要安全解锁

工作流程(摘自👆详解):

dhexdyjl.hcw

该服务支持 sub-function

pxlvb4pe.cwp

请求 / 响应数据包分析

本实验采用的模拟器自行定义了 03, 19, 21 三个 安全等级,以下是各等级的限制(这几个安全等级均为自行定义):

  • 安全等级 03:读数据无需安全访问,写数据需要安全访问(fake key,固定key:deadbeef
  • 安全等级 19:读数据无需安全访问,写数据需要安全访问
  • 安全等级 21:读写数据均需要安全访问

所使用的 key 生成算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
uint8_t *security_algorithm(uint8_t *seed_ptr) {
uint8_t *key_ptr = (uint8_t *)malloc(sizeof(uint8_t)*4);
uint8_t Seed[4];
uint8_t Const[4];
uint8_t Key[4];
uint32_t wConst = 0xdeadbeef;

Seed[0] = *seed_ptr;
Seed[1] = *(seed_ptr+1);
Seed[2] = *(seed_ptr+2);
Seed[3] = *(seed_ptr+3);

Const[3] = (uint8_t)((wConst & 0xff000000) >> 24);
Const[2] = (uint8_t)((wConst & 0x00ff0000) >> 16);
Const[1] = (uint8_t)((wConst & 0x0000ff00) >> 8);
Const[0] = (uint8_t)(wConst & 0x000000ff);

Key[0] = Const[0] * (Seed[0] * Seed[0]) + Const[1] * (Seed[1] * Seed[1]) + Const[2] * (Seed[0] * Seed[1]);
Key[1] = Const[0] * (Seed[0]) + Const[1] * (Seed[1]) + Const[3] * (Seed[0] * Seed[1]);
Key[2] = Const[0] * (Seed[2] * Seed[3]) + Const[1] * (Seed[3] * Seed[3]) + Const[2] * (Seed[2] * Seed[3]);
Key[3] = Const[0] * (Seed[2] * Seed[3]) + Const[1] * (Seed[3]) + Const[3] * (Seed[2] * Seed[3]);

key_ptr = Key;
return key_ptr;
}

接下来使用安全等级 21 进行演示,捕获到的 can 报文如下:

1
2
3
4
5
6
7
8
9
# 请求切换到扩展会话模式
[TX] 733 [8] 0210030000000000
# 确认切换到扩展会话模式
[RX] 73b [8] 065003003201f400
# Security Access
[TX] 733 [8] 0227210000000000
[RX] 73b [8] 0667213071940900
[TX] 733 [8] 0627226e4ece5200
[RX] 73b [8] 0267220000000000

0x27 服务请求数据帧:

1720749947508

请求种子(seedRequest)请求中,SID 后一字节数据为奇数,代表对应的安全等级,securityAccessDataRecord 一般不使用

发送密钥(sendKey)请求中,SID 后一字节数据为偶数,其值为目标的安全等级+1

0x27 服务肯定响应数据帧:

1720750212050

大多情况下肯定响应帧只有 0x67 和 Sub-Function,只有在请求种子的响应中会携带 securitySeed 数据

0x27 服务否定响应数据帧:

发送一个错误的密钥进行否定响应演示,捕获到的 CAN 数据如下:

1
2
3
4
5
6
7
8
[TX] 733 [8] 0210030000000000
[RX] 73b [8] 065003003201f400
[TX] 733 [8] 0227210000000000
[RX] 73b [8] 0667216151303900
# 发送错误的密钥
[TX] 733 [8] 062722deadbeef00
# 得到否定响应
[RX] 73b [8] 037f273500000000

0x27 服务支持的否定响应代码:

NRC 描述 含义
0x12 sub-functionNotSupported 子功能参数不受支持
0x13 incorrectMessageLengthOrInvalidFormat 消息长度错误
0x22 conditionsNotCorrect 不满足请求标准/条件
0x24 requestSequenceError 请求顺序错误,比如应该先发送请求种子,而不是先发送密钥数据
0x31 requestOutOfRange 请求中携带的数据是无效的
0x35 invalidKey 密钥不匹配,即Tester计算出来的key和目标ECU计算出来的不一样:若始终不匹配还不断尝试,ECU会回复下面的NRC=36,告诉你已经超过失败的次数了,不能再请求安全解锁了
0x36 exceededNumberOfAttempts 超过最大试错次数,已达到解锁最大错误次数,若你执意再请求,ECU就会回复你下面的NRC=37,意思是ECU现在不接受安全访问,这就是ECU锁死的现象,需等待一定时间后才可继续请求安全访问
0x37 requiredTimeDelayNotExpired 当前服务器处于延时状态,超时时间未到

未额外声明时,当前服务的上述 NRC 在 UDS 模拟器中均可支持

0x2E WriteDatabyIdentifier

服务简介

通过 ID 写数据,SID:0x2E

该服务内容详解:《UDS协议从入门到精通》系列——图解0x2E:通过ID写数据

对应 ISO-14229-1 页码:162

本服务不支持 sub-function,且不支持一次性写多个 DID

请求 / 响应数据包分析

UDS 模拟器的配置文件中声明了可用的 DID,接下来使用默认 TBOX 配置中的 DID 0xF190(VIN码) 作为目标 DID 进行演示(安全等级 19)

发送请求:请求读取 F190 数据,然后请求写 F190 数据(写入 Hello world),最后请求读 F190 数据,捕获到的 CAN 数据包如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 请求切换到扩展会话模式
[TX] 733 [8] 0210030000000000
# 确认切换到扩展会话模式
[RX] 73b [8] 065003003201f400
# 请求读取 DID 为 F190 的数据
[TX] 733 [8] 0322f19000000000
# 返回数据
[RX] 73b [8] 101462f1904c5541
# 流控帧
[TX] 733 [8] 3000000000000000
# 返回数据
[RX] 73b [8] 2155324155423347
[RX] 73b [8] 2245333833343637
# 安全访问请求
[TX] 733 [8] 0227190000000000
[RX] 73b [8] 0667193625349200
[TX] 733 [8] 06271aa084d8e400
[RX] 73b [8] 02671a0000000000
# WriteDatabyIdentifier:
[TX] 733 [8] 100e2ef19048656c
[RX] 73b [8] 30000f0000000000
[TX] 733 [8] 216c6f20776f726c
[TX] 733 [8] 2264000000000000
[RX] 73b [8] 036ef19000000000
[TX] 733 [8] 0322f19000000000
[RX] 73b [8] 100e62f19048656c
[TX] 733 [8] 3000000000000000
[RX] 73b [8] 216c6f20776f726c
[RX] 73b [8] 2264000000000000

0x2E 服务请求数据帧:

qbnen5dn.ens

0x2E 服务肯定响应数据帧:

1720767745805

0x2E 服务否定响应数据帧:

尝试请求写一个安全等级尚未解锁的目标 DID:

1
2
3
4
5
[TX] 733 [8] 0210030000000000
[RX] 73b [8] 065003003201f400
[TX] 733 [8] 100e2efa1948656c
# 返回否定响应
[RX] 73b [8] 037f2e3300000000

0x2E 服务支持的否定响应代码:

NRC 描述 含义
0x13 incorrectMessageLengthOrInvalidFormat 消息长度错误
0x22 conditionsNotCorrect 当前条件不满足
0x31 requestOutOfRange 请求参数不受支持,参数错误
0x33 securityAccessDenied 安全访问错误,比如访问的DID数据是安全数据,但当前等级未解锁
0x72 generalProgrammingFailure 通用编程错误,一般写入内存出错就报这个NRC

UDS 模拟器暂不支持 NRC 0x72

0x2E 服务 NRC 处理流程:

vjp1uiyv.2z3

TODO

0x34, 0x35, 0x36, 0x37 服务