作者:让大古变成光
介绍:泽鹿安全-极物实验室车联网安全研究员
0x00 前言
上个月参加了中国智能网联汽车大赛,在大家的共同努力下取得了第一名的好成绩,一直在忙项目终于抽出时间整理一下。这次比赛主要分成预赛和决赛,两场比赛的侧重点并不相同。这里主要是来复现一下预赛所考察的知识点。
首先查看主办方给我们的预赛介绍:
然后是具体的考点:
可以看到,预赛的主要侧重点还是车辆CAN协议安全。我们在赛前也做了一些猜测。动作信号指令逆向题,应该是实时抓取CAN帧的同时做开车门、关车门、开启大灯、关闭大灯等动作,然后分析这些CAN帧即可。车辆信息读取题,往往是接入OBD,发送符合某种标准的诊断CAN帧,从而获取车辆的这些信息。
车辆逆向题其实并不需要多做太多准备,尽管分值很高,但更多是考验分析与随机应变能力,所以我们队在赛前主要是对车辆信息读取题做了很多准备。
因此这次预赛复现的侧重点在于车辆诊断协议。
0x01 诊断协议之间的关系
诊断系统分为多层:
既然是车辆信息读取题,那么首先应该研究的是网络层,我们需要知道,我们的诊断仪是如何寻址到目的ECU并与之通信的,当我们想要获取或者发送长数据时,双方采取什么方式进行数据交换。
然后是应用层,ISO 15031-5主要面向排放相关ECU的服务,而UDS(即ISO 14229)面向所有ECU(电控单元)的服务。
0x02 网络层
2.1 网络节点之间的通信
两个或多个网络节点之间,依靠can ID来进行通信,就像TCP/IP协议的网络中,使用IP地址进行通信一样。不过两者的机制稍微有些不同。
首先来了解两个概念:
功能寻址:广播诊断请求Request,同时等待总线上的ECU给与响应。
物理寻址:指定发送特定诊断请求Request,等待指定ECU给与响应。
功能寻址是一对多,而物理寻址是一对一。
ISO 15765-4标准定义了一些CAN ID的含义:
首先7DF是功能寻址,用来向总线上所有的ECU发送CAN帧,7E0-7E7为1号到8号ECU的接收ID,而7E8到7EF为1号到8号ECU的发送ID。
可能有的人会问,为什么ECU会有两种ID号,我们可以举一个非常简单的例子:
1号ECU的接收ID为0x7DF、0x7E0,发送ID为0x7E8。为什么接收ID会有两个呢,是因为0x7DF是专门用作功能寻址的,而0x7E0是用作物理寻址的。每个ECU都有一个和其它ECU不同的物理寻址的接收ID,物理寻址的接收ID是不可重复的。而多个ECU可以拥有相同的功能寻址的接收ID,就比如这个0x7DF。发送ID通常为物理寻址的接收ID+8,因此物理寻址的接收ID为0x7E0,则发送ID为0x7E8。
当诊断仪发出一个can id为0x7DF的请求报文时,因为多个ECU都具有0x7DF这个接收ID,因此多个ECU都接收并处理了这个请求报文,并且返回can id为ECU的发送ID的响应报文。
当诊断仪发出一个can id为0x7E0的请求报文时,1号ECU发现该请求报文的can id与自己物理寻址的接收ID一致,便会接收并处理这个请求报文,并且返回can id为0x7E8的响应报文。其它ECU因为不具备0x7E0这个接收ID,因此不会理会这个报文
当然,标准只是起到一个指导的作用,具体每个ECU的CAN ID的定义,可能会是厂商自己来实现。
2.2 拆分的方法
一个can帧有8个字节的数据段,当我们要查询的数据量远远超过8个字节,一个can帧没法完全将数据传输过来,因此就需要发送多个can帧,而为了协调这个发送多个can帧的过程,ISO 15765-2标准定义了四种can帧的类型。
这四种类型分别为:
1. 单帧
2. 首帧
3. 连续帧
4. 流控制帧
标准中四种类型的帧的格式是这样的:
N_PCItype字段用来表示帧的类型,不同的值代表不同的类型的帧,其中对应值的帧类型以及帧的定义为:
首先来看单帧,单帧的第一个字节的0-3位,即SF_DL字段,代表着数据(指应用层报文)的长度。
然后是首帧,首帧的FF_DL字段指明了首帧和后续连续帧中的数据(指应用层报文)的长度。
之后是连续帧,连续帧的SN字段代表着该连续帧的顺序号,序号从0开始。那么就产生了一个问题,SN只有4位bit,所能表示的最大的数字也只不过是15,那么如果要发送的数据过长,使用16个连续帧依旧无法将数据发送完整,那怎么办呢?标准给出的答案是,将序号重置为0,然后接着顺序发送。
最后是流控帧,流控帧的FS字段指示数据发送方是否继续发送数据,如果它的值为0,代表让数据发送方继续发送数据,如果它的值为1,代表让数据发送方等待新的流控帧的到来,如果它的值为2,代表让数据发送方中止数据的发送。BS字段指示数据发送方发送连续帧的最大数目,如果BS字段为n,则数据发送方最多只能发送n个连续帧,一个特殊的情况是,如果BS字段为0,则数据发送方不停的发送剩下的连续帧。STmin字段指定数据发送方发送连续帧的时间间隔。
我们可以拿查询VIN码的例子来加深理解:
1、 首先设置好我们的VIN码;
2、然后使用$09服务来查询VIN码;
3、 查看回显的CAN帧;
4、发送流控制帧,让1号ECU继续发送剩下的连续帧。
有的师傅可能对第3条的0x14有些疑惑,这里的长度0x14是如何计算出来的呢?首先我们将首帧以及后续的连续帧的网络层的数据去除,即N_PCI,那么首帧和后续的连续帧就只剩下了应用层报文,即:
首帧: 0x49 0x02 0x01 0x31 0x32 0x33
一号连续帧:0x34 0x35 0x36 0x37 0x38 0x39 0x30
二号连续帧:0x31 0x32 0x33 0x34 0x35 0x36 0x37
计算这些应用层数据的长度,刚好为0x14。
那么我们最终得到的VIN码是什么呢?
接着将首帧的应用层控制字段去掉,即0x49 0x02 0x01,那就只剩下了0x31,0x32 0x33,然后再加上一号二号连续帧的数据,从0x34-0x30,再从0x31-0x37,把它们从ASCII码转换成字符,即为12345678901234567。
大致流程图如下:
0x03 ISO 15031-5
3.1 车辆里程
ISO 15031-5标准中并没有定义直接获取总里程的方法,但是可以获取故障灯点亮之后的里程数以及故障码清除后的里程数。
这里我们可以采用标准中的$01服务。
先来看$01服务的报文格式:
非常简单,仅仅是SID+PID。因为是$01服务,所以SID为1,而PID标准中后面的表中定义,我找到了两个里程数的PID。
故障灯被激活后的里程数的PID为0x21,而故障码被清除后的里程数的PID为0x31。
1、 首先设置好两种里程数,将MIL点亮后的行驶距离改为197Km,将清除故障码后的行驶距离改为6101Km;
2、发送CAN帧并获得回显,计算两个里程。
我们发现第一个响应报返回的数据为0xC5,也就是197,第二个响应包为0x17D5,也就是6101。
3.2 故障码(DTC)
故障码就是代表某一种故障的代码,UDS DTC一般采用三个字节,而OBD DTC采用两个字节,因此我们这里展开讲解两个字节的DTC,后面到ISO 14229-1再来看3个字节的DTC。
两个字节的DTC的结构为:
如果一个故障码为B1234,则它的结构如下:
ISO 15031-6中定义了很多故障码的含义,从第15页开始,查询到相关故障码时,可以对照表查询对应故障。
想要查询故障码,我们可以使用$03服务,使用实例如下:
1、设置故障码;
2、使用$03服务查询故障码;
3、解析响应包
-
向所有ECU发送[01 03]
-
每一个故障码两个字节,总共五个故障码,一个单帧是无法完全负载这些数据的,所以使用首帧和连续帧进行负载。[10 0C]代表首帧并且数据共0xc个字节,[43 05]分别为响应SID号与故障码的数量,[01 01]代表了P0101,[92 34]代表B1234(不理解0x9234的师傅可以看上面的故障码结构体),然后发送流控帧指示ECU继续发送,ECU发送后续故障码。
当故障被修理后,此时车辆不存在故障,那么故障码也应该被清除,我们可以使用$04服务对所有故障码以及相关数据进行清除。
直接发送04即可清除故障码以及相关数据。
3.3 车速
车速也可以通过$01服务进行查询,我们只需要找到车速的PID即可。
实验结果如下:
我们得到了0x88,即136,与我们的查询一致。
3.4 关于总里程以及电池电量
这两项指标也在PPT中,但是在标准中并没有定义,所以这种状态的查询一般都是厂商自定义的。
0x04 ISO 14229-1
4.1 $22服务
我们抽到的题目,大多都是给你某个ECU的can id,还有DID(数据标识),然后让你查询它的值,想要实现这种效果,就得用到$22服务了。
$22服务的请求报格式为:请求SID+DID。
$22服务的响应报格式为:响应SID+DID+DATA。
关于DID表,我们可以在ISO 14229-1中查到。
这里以VIN码查询为例:
所以我们应该发送:
0x7DF | 03 22 F1 90 00 00 00 00
因为使用的模拟器不支持UDS,所以这里也就不做测试了。
0x05 总结
UDS的东西有很多,ISO 14229-1共有390多页,只不过因为篇幅的原因,这里就不再多说了。这次比赛虽然取得很好的成绩,但是复盘起来还是很多需要学习和研究知识,对于车联网方面的学习还是要更加深入。希望以后可以学到更多知识分享给大家。
原文始发于微信公众号(智能网联汽车安全):中国智能网联汽车大赛预赛篇