​ 本文基于当前阶段开展的一项关于CAN总线相关测试项目,其中所涉不触及项目秘密的技术探索部分形成了本文所采用的技术线路,用于技术积累、分享或是交流关于can总线相关车联网安全技术。

CAN总线数据交互初探

0x01 初识CAN

​ CAN 是Controller Area Network 的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。在汽车产业中,出于对安全性、舒适性、方便性、低功耗、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面向汽车的CAN 通信协议。此后,CAN 通过ISO11898 及ISO11519 进行了标准化,在欧洲已是汽车网络的标准协议。

​ 普遍认为,基于CAN总线的分布式控制系统具备网络各节点之间的数据通信实时性强等优点,与本文相关性支撑性较强的理论为:(1)结构简单,只有2根线与外部相连,并且内部集成了错误探测和管理模块。(2)CAN协议的一个最大特点是废除了传统的站地址编码,而代之以对通信数据块进行编码。采用这种方法的优点可使网络内的节点个数在理论上不受限制,数据块的标识符可由11位或29位二进制数组成,因此可以定义2或2个以上不同的数据块,这种按数据块编码的方式,还可使不同的节点同时接收到相同的数据,这一点在分布式控制系统中非常有用。数据段长度最多为8个字节,可满足通常工业领域中控制命令、工作状态及测试数据的一般要求。同时,8个字节不会占用总线时间过长,从而保证了通信的实时性。

0x02 实施路线

​ 至少在我本人看来,如果接触一个第一个触及的领域或事物时,尽管抱有浓厚兴趣,但总会有无从下手的感觉;在今天直接提供方法指导的鹏哥和提供设备的极物安全实验室团队表示感谢,这很大程度上推动了我对CAN相关应用测试的有效进展;

​ 开展CAN相关研究的过程初步分为了如下部分:

​ 1、完成CAN相关硬件设备搭建;

​ 2、通过有线方式连接至电脑USB端口,使用测试或开发环境读取识别到设备;

​ 3、搭建与硬件设备进行通信数据完成初步测试项数据交互验证;

0x03 硬件环境搭建

​ 在本次开展的CAN相关测试需要完成对提供功能设备进行一些安全相关的测试项,需要构建的部分包括有:(1)提供稳定供电的电源稳压器;(2)使用CAN总线进行通信的目标硬件设备;(3)CAN分析仪;(4)连接上述设备的排线或数据线若干;如图1所示,橘黄标识出的1、2、3、4分别对应上述(1)(2)(3)(4)

​ 图1 CAN初步搭建硬件设备示意图

​ 其中,1处所示电源稳压器提供设备供电,2处所示设备为被测试设备(设备仅有使用权所以翻转到背面并打了一部分码)可以由图2左侧所示OBD模拟器设备代替;4处所示为设备供电的夹线,其中黄线(B+)红线(ACC)接正极,黑色(GND)接负极;3处CAN分析仪将设备总线的CAN_H和CAN_L接入对应接口的H和L处完成对设备的CAN通信收发读取;在此处,所涉及测试项仅为测试数据收发读取,如图2所示将CAN分析仪的CAN1-H接入CAN2-H,将CAN分析仪的CAN1-L接入CAN2-L处,为保证数据通信正常,安装设备官方说明,应添加120Ω的电阻,在图2中示出。

​ 图2 ODB设备和CAN分析仪接线

0x04 软硬件交互环境搭建

​ 本次测试实施例选用的平台为Ubuntu18.04,使用的CAN分析仪为CANalyst-Ⅱ(Linux版本),将分析仪通过USB接入电脑插口,例如本次使用的Ubuntu18.04安装在虚拟机中,接入设备时需要将设备接入系统选为Ubuntu18.04;官方文档中所述,该分析仪对Linux系统是免驱的,通常不需要手动安装驱动,拷贝官方文档提供的系统环境包:controlcan;

​ 1、编辑环境包中的Makefile文件,其中修改yourpath处为实际系统环境中的路径内容:

1
2
$ vim Makefile
$ g++ -o hello_cpp main.cpp /home/yourpath/Desktop/controlcan/libcontrolcan.so -lpthread

​ 2、若需要查看相关环境信息,可以执行如下命令:

1
2
$ uname -a
$ gcc -v

​ 图3 环境信息查看

​ 3、查看设备输入lsusb命令查看接入状态,如下回显为正常:

1
2
3
4
5
6
7
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 019: ID 04d8:0053 Microchip Technology, Inc.
Bus 002 Device 018: ID 0e0f:0008 VMware, Inc.
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

​ 4、查看修改C语言测试样例程序,依次执行如下命令测试样例程序,图4所示为执行后的正确回显,CAN1与CAN2完成了数据交互,至此说明软硬件数据通讯环境搭建完成:

1
2
3
4
$ vim main.cpp
$ make clean && make
$ sudo su
$ ./hello_cpp

​ 图4 测试样例执行回显

0x05 python3测试与总结

​ Python测试项,前置条件拷贝libcontrolcan.so链接库文件文件;测试代码如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from ctypes import *

VCI_USBCAN2 = 4
STATUS_OK = 1
class VCI_INIT_CONFIG(Structure):
_fields_ = [("AccCode", c_uint),
("AccMask", c_uint),
("Reserved", c_uint),
("Filter", c_ubyte),
("Timing0", c_ubyte),
("Timing1", c_ubyte),
("Mode", c_ubyte)
]
class VCI_CAN_OBJ(Structure):
_fields_ = [("ID", c_uint),
("TimeStamp", c_uint),
("TimeFlag", c_ubyte),
("SendType", c_ubyte),
("RemoteFlag", c_ubyte),
("ExternFlag", c_ubyte),
("DataLen", c_ubyte),
("Data", c_ubyte*8),
("Reserved", c_ubyte*3)
]
canDLL = cdll.LoadLibrary('./libcontrolcan.so')

ret = canDLL.VCI_OpenDevice(VCI_USBCAN2, 0, 0)
if ret == STATUS_OK:
print('调用 VCI_OpenDevice成功\r\n')
if ret != STATUS_OK:
print('调用 VCI_OpenDevice出错\r\n')

#初始0通道
vci_initconfig = VCI_INIT_CONFIG(0x80000008, 0xFFFFFFFF, 0,
0, 0x03, 0x1C, 0)#波特率125k,正常模式
ret = canDLL.VCI_InitCAN(VCI_USBCAN2, 0, 0, byref(vci_initconfig))
if ret == STATUS_OK:
print('调用 VCI_InitCAN1成功\r\n')
if ret != STATUS_OK:
print('调用 VCI_InitCAN1出错\r\n')

ret = canDLL.VCI_StartCAN(VCI_USBCAN2, 0, 0)
if ret == STATUS_OK:
print('调用 VCI_StartCAN1成功\r\n')
if ret != STATUS_OK:
print('调用 VCI_StartCAN1出错\r\n')

#初始1通道
ret = canDLL.VCI_InitCAN(VCI_USBCAN2, 0, 1, byref(vci_initconfig))
if ret == STATUS_OK:
print('调用 VCI_InitCAN2 成功\r\n')
if ret != STATUS_OK:
print('调用 VCI_InitCAN2 出错\r\n')

ret = canDLL.VCI_StartCAN(VCI_USBCAN2, 0, 1)
if ret == STATUS_OK:
print('调用 VCI_StartCAN2 成功\r\n')
if ret != STATUS_OK:
print('调用 VCI_StartCAN2 出错\r\n')

#通道1发送数据
ubyte_array = c_ubyte*8
a = ubyte_array(1,2,3,4, 5, 6, 7, 8)
ubyte_3array = c_ubyte*3
b = ubyte_3array(0, 0 , 0)
vci_can_obj = VCI_CAN_OBJ(0x1, 0, 0, 1, 0, 0, 8, a, b)#单次发送

ret = canDLL.VCI_Transmit(VCI_USBCAN2, 0, 0, byref(vci_can_obj), 1)
if ret == STATUS_OK:
print('CAN1通道发送成功\r\n')
if ret != STATUS_OK:
print('CAN1通道发送失败\r\n')

#通道2接收数据
a = ubyte_array(0, 0, 0, 0, 0, 0, 0, 0)
vci_can_obj = VCI_CAN_OBJ(0x0, 0, 0, 0, 0, 0, 0, a, b)#复位接收缓存
ret = canDLL.VCI_Receive(VCI_USBCAN2, 0, 1, byref(vci_can_obj), 2500, 0)
#print(ret)
while ret <= 0:#如果没有接收到数据,一直循环查询接收。
ret = canDLL.VCI_Receive(VCI_USBCAN2, 0, 1, byref(vci_can_obj), 2500, 0)
if ret > 0:#接收到一帧数据
print('CAN2通道接收成功\r\n')
print('ID:')
print(vci_can_obj.ID)
print('DataLen:')
print(vci_can_obj.DataLen)
print('Data:')
print(list(vci_can_obj.Data))

#关闭
canDLL.VCI_CloseDevice(VCI_USBCAN2, 0)

​ 测试项脚本执行结果如图5所示说明Python实现CAN通信数据交互成立:

​ 总结:在今天开展的CAN总线初探中实现了软硬件环境搭建,使用测试用例测试了CAN数据交互通信流程,过程中加深对CAN数据传输的认识,基于此为后续深入开展CAN相关安全测试项构成有效实施支撑。