孩子瞎写着玩的
靶场链接BlockHarbor Hackathon,写这个也只是一时兴起(同时也是在进行简单的备战防止线下当挂件),同时记录一些CAN和UDS的笔记,还有一些简单的can-utils
的使用
参考了此Writeup(链接包含前3部分WP的链接):【WriteUP】VSEC 车联网安全 CTF 挑战赛(四),但是有很多挺重要的点没有详细剖析,所以自己也去翻了ISO标准,但是分P这招好悬没给我搞吐(
UDS Challenge
Simulation VIN
题目要求获取模拟环境下车辆的VIN码,模拟环境内已经预置了can-utils
便于我们进行CAN报文的发送,那接下来我们应该怎么办呢?
第一步自然是先把CAN报文给dump下来方便读取吧,因此我们首先应该通过ifconfig
或者ip link
查看CAN接口,这里是vcan0
,接下来就是输入candump -l vcan0 &
将CAN报文dump到日志中
OK,这样我们就能通过日志读取报文了,所以接下来我们就该使用cansend
发送CAN报文了,但是该发什么呢…?
OBD诊断基础
根据ISO 15765-4标准,OBD预定义了一系列地址用于诊断信息的请求和响应,其中7E0
~7E7
为向ECU#1~#8发送请求的地址,而7E8
~7EF
依次为对应ECU响应的地址,而有一个比较独特的地址7DF
,测试设备通过这个地址发送诊断数据时,会向所有ECU询问接收此数据后ECU是否有响应要返回(人话:广播)
由于我们不知道应该往哪个ECU发送诊断数据,所以这里我们要以7DF
作为我们的CAN ID发送数据,那么我们又应该发送什么数据呢?
UDS协议基础
根据ISO 14229-1标准(Annex A),我们发现F190
对应的是VINDataIdentifier
,因此我们需要通过此Identifier获取VIN数据,同时在同一标准下第9节,我们可以发现UDS定义的一系列功能和其对应的功能ID,由于我们是通过Identifier读取数据,因此对应的功能为ReadDataByIdentifier(0x22)
,根据10.2节对该功能的定义,我们可以知道格式为22(Identifier1)(Identifier2)...
,其中每个Identifier长度2字节,而本题很自然是需要发送22F190
这三个字节
但是如果你直接发送数据的话会出问题,这是因为UDS协议的传输层是通过帧进行数据传输的:
UDS帧格式
根据ISO 15765-2标准,CAN总线上的诊断数据会通过4种帧进行分割传输:
- 单帧(SingleFrame,SF)
- 首帧(FirstFrame,FF)
- 连续帧(ConsecutiveFrame,CF)
- 流控帧(FlowControl,FC)
很明显,前三者是用于传输实际数据的,而最后一种是用于控制数据传输节奏的
各帧的首个字节为帧类型标识(PCI域),用于分辨帧类型,接下来简单写一下各帧的格式:
本部分只写了常规情况:单帧数据长度<8/首帧数据长度<4095的情况,对于大于等于的情况请自行阅读标准的9.6.1节
单帧(SF)
Byte 0 | Byte 1~N |
---|---|
0x0N | Data Field |
对于单帧,Byte 0的高4位固定为0用于分辨帧类型,低4位则代表帧长度(Data Length,DL),之后的N字节则为数据域
需要注意的是,1≤N≤7,因为一个标准的CAN数据域长度为8字节,而首字节被PCI域占用,因此单帧最多只能传输7字节数据
首帧(FF)
Byte 0 | Byte 1 | Byte 2~7 |
---|---|---|
0x1X | 0xYZ | Data Field Snippet |
和单帧同理,Byte 0的高4位固定为1,而之后的0xXYZ则是首帧的DL(即DL = 0xX << 8 + 0xYZ
),后面则是数据域
由DL可以看出,首帧是在传输的数据量较大的情况下使用的,而有了首帧那自然会有后面的帧,也就是连续帧:
连续帧(CF)
Byte 0 | Byte 2~7 |
---|---|
0x2N | Data Field Snippet |
Byte 0高4位固定为2,此处的N表示的是连续帧的序列号(SN),从0x0到0xF循环,超过则重新回到0x0
流控帧(FC)
Byte 0 | Byte 1 | Byte 2 | Byte 3~7 |
---|---|---|---|
0x3N | BS | STmin | Reserved (N/A) |
对于多帧数据传输,我们需要有一个能控制数据流传输速度的方法,而FC就是做这个的
Byte 0高4位固定为3,低4位的N实际上是设置流控状态(FS)的:
- 继续发送(CTS,0x0)(允许发送后续CF帧)
- 等待(WT,0x1)(暂停发送,需重新请求FC)
- 溢出(OVFLW,0x2)(接收方缓冲区满,终止传输)
Byte 1的BS则表示块大小,即发送方可以连续发送的CF帧数量,如果设置为0则表示无限制
Byte 2的STmin则是最小间隔时间,即每个CF帧之间的间隔时间,单位为ms(0x00~0x7F)或者μs(0xF1~0xF9),其中如果数据符合后者,则时间间隔为(STmin-0xF0)*100
μs
通过FF,CF和FC,我们大致能知道在多帧传输下,数据传输的流控过程如下:

好的,现在我们知道了UDS的帧格式,那么是时候发送数据了!我们首先需要发送的数据长度为3,因此单帧就能满足我们的需求,因此我们输入命令cansend vcan0 7DF#0322F190
就能获取到VIN数据了!
诶,但是我们知道一个VIN长度为固定的17个字节,因此传输回来的帧实际上是一个FF帧:7E8#101462F190666C61
简单解析一下:根据前面的OBD诊断知识,7E8
对应的地址为7E0
,而帧数据中正是ReadDataByIdentifier
的标准返回,长度为0x014,即20,其中前3个字节分别为固定的响应识别码和请求的Identifier,从666C
开始才是数据
那么根据刚刚提到的流控机制,我们只需要再发送一个FC帧就能读取所有的数据了,因此接下来输入cansend vcan0 7E0#300000
即可得到完整的VIN码
Startup Message
我们首先还是使用candump捕获CAN报文,接下来就应该看题了
题目说明模拟终端在启动的时候通过7DF
发送了诊断信息,然后问我们信息是什么,然后给的提示是让我们重启ECU
到这里其实答案已经呼之欲出了:我们只需要先捕获CAN报文,然后中途让ECU重启就能得到启动的诊断讯息了,所以接下来就是愉快的啃文档时间~(棒读)
那么根据ISO 14229-1的9.3小节对ECUReset(0x11) Service
的规范,我们可以知道我们需要进行一次hardReset来模拟电源启动,也就是子功能0x01,因此我们需要传输的数据是0x1101
,加上单帧数据就是021101
因此我们只需要cansend vcan0 7DF#021101
之后读取日志,等待响应后马上通过7DF
发送的诊断数据就能得到flag啦
好消息是发送的诊断数据是SF,所以我们无需再次发送FC读取后续帧
Engine Trouble?
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2025/08/12/vsec-wp/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!