首页
社区
课程
招聘
[原创]零成本入门车联网安全研究(二)
发表于: 2022-9-23 16:52 14226

[原创]零成本入门车联网安全研究(二)

2022-9-23 16:52
14226

本文是《零成本入门车联网安全研究》系列第二篇,第一篇简单介绍了下车内网络的架构,让大家对车内ECU组网的方式有了最基本的了解。本文则在车内网络的基础上,部署了真实的网络业务——基于以太网的车辆诊断服务,该服务基于doip(Diagnose On IP)协议栈实现通用诊断协议UDS。本文相比于第一篇文章更具可操作性与可玩性,可操作性的点在于大家根据文章介绍的步骤,可以搭建好自己的实验环境;可玩的点在于,本实验会在开源项目的基础上,加入可实际利用的协议漏洞,通过漏洞利用能够模拟远程控制车辆。此外,本文在描述实验步骤的同时,还会穿插介绍一些相关基础,包括:

  • CAN网络基础
  • UDS协议基础
  • DoIP协议基础
  • 远程诊断的实现原理

等等。为了帮助大家更好地开始,有必要先介绍下实验环境。本次实验的系统使用ubuntu20.04模拟车辆边缘节点,节点上运行DoIPServer,win10模拟DoIP诊断仪,诊断仪包含DoIPClient,其中win10与ubuntu组成可相互通信的局域网,即DoIP诊断仪可以通过以太网连接DoIPServer。车内CAN网络通过linux vcan实现,边缘节点、ICSim及车内UDS节点均连接到CAN网络,车内UDS节点上运行UDSServer,该Server能实现基于CAN网络的UDS诊断。整体网络架构如下图所示:

基于如上部署,DoIP诊断仪能够通过以太网连接边缘节点,通过发送诊断协议包的方式,直接对边缘节点执行远程诊断。另外,因为车内UDS节点也实现了UDS协议,因此DoIP诊断仪可以发送诊断协议包至边缘节点,由边缘节点执行DoIP转DoCAN,将基于DoIP的诊断协议包格式转换为基于CAN的诊断包格式,发送至车内UDS节点。

另外,由于ICSim未实现UDS协议,因此DoIP诊断仪的数据包理论上是不会被转发至ICSim,但我们在DoIPServer中插入了一个漏洞,将不合法的DoIP数据包转发至CAN网络,这样通过构造,便可以使用DoIP诊断仪远程发送控制报文至ICSim,实现远程车控。

关于实验环境就介绍这么多,接下来上手配置。首先在ubuntu上安装虚拟CAN网络的工具:

# 安装python can
pip3 install python-can
# 按照can-utils
sudo apt-get install can-utils
# 安装can驱动,设置can网络
sudo modprobe can
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

设置完CAN网络后,通过ifconfig查看状态:

配置ubuntu与windows10的通信环境,在同一个局域网可以相互ping通即可,ip地址不必完全按照本文配置。

其中ubuntu的网络配置如下:

windows10的网络配置如下:

安装ICSim依赖工具:

sudo apt-get install libsdl2-dev libsdl2-image-dev can-utils

下载ICSim源代码,make编译ICSim,运行ICSim:

git clone https://github.com/zombieCraig/ICSim.git
cd ICSim
make
# 运行ICSim,绑定vcan0
./icsim vcan0

下载DoIPServer与DoIPClient源码:

git clone https://gitlab.com/rohfle/doip-simulator.git

下载uds-server并编译:

https://github.com/zombieCraig/uds-server.git

启动uds-server并绑定到vcan0,uds-server用于模拟车内uds节点:

./uds-server vcan0

接下来对doipserver做一些修改,用来转发UDS报文给uds节点,以及插入漏洞代码:

修改一:

~/study/vehicle/doip-simulator$ git diff lib/server.py
diff --git a/lib/server.py b/lib/server.py
index 16f0248..6315479 100644
--- a/lib/server.py
+++ b/lib/server.py
@@ -20,6 +20,7 @@ import time
 from . import uds
 from . import doip
+from . import utils
 from . import simulator as sim
@@ -90,6 +91,22 @@ class DOIPServer(object):
                         if not self.simulator.has_target_address(target_address):
                             logger.error('Error: target_address 0x{:02x} is unknown'.format(target_address))
+
+                            # send fake message to vcan0
+                            # print(utils.bytes_to_hex(userdata))
+                            data_len = len(userdata) + 1
+                            can_data = bytearray(data_len)
+
+
+                            laddr = target_address
+                            if target_address != 0x188 and userdata[0] == 0x22:
+                                can_data[0] = 0x02
+                                can_data[1:] = userdata
+                            else :
+                                can_data[:] = userdata[1:]
+                            utils.send2vcan0(bytes(can_data), target_address)
+                            time.sleep(1)
+
                             response = doip.DiagnosticMessageNegativeAck(source_address, target_address,

修改二:

~/study/vehicle/doip-simulator$ git diff lib/utils.py
diff --git a/lib/utils.py b/lib/utils.py
index 427de2c..802e58f 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -15,6 +15,15 @@
 """
 import inspect
+import can
+
+
+def send2vcan0(can_data, laddr):
+    bus = can.Bus(channel='vcan0', interface='socketcan')
+    msg = can.Message(arbitration_id=laddr, data=can_data, is_extended_id=False)
+    bus.send(msg)
+
+
 def get_subclasses(mod, cls):

在doipclient的源码中,加入canid为0x188及0x710的报文配置,分别针对ICSim和UDS节点:

config = {
    'datamap': {
        # target_address (hex) : {
        #   identifier (hex) : tuple(label (str), key (str), parser (func))
        # }
        0x188: { # control ICSim
            0x0100: ('Fake Msg', 'fake', parse_fake_msg),
            0x0200: ('Fake Msg', 'fake', parse_fake_msg),
        },
        0x3300: {
            0x3200: ('Dummy Accelerator', 'accelerator', parse_accelerator),
            0x3230: ('Dummy Brake', 'brake', parse_brake_pressure),
        },
        0x3301: {
            0x3250: ('Dummy Steering', 'steering', parse_steering_angle),
        },
        0x710: { # send to uds-server
            0xF187: ('Fake Msg', 'fake', parse_fake_msg),
        }
    }
}

修改完成之后,分别在ubuntu和windows10上将doipserver、doipclient运行起来:

# on ubuntu
python3 doipserver.py
# on windows10
python3 doipclient.py

此时,所有组件都开始工作。doipserver运行起来后会监听13400端口,等待doipclient连接。同时doipserver会周期性地发送广播帧向外报活,广播帧的内容主要包含车辆VIN码及边缘节点的逻辑地址,如下图所示:

广播节点逻辑地址的原因和UDS协议中的寻址方式有关系,UDS协议规定了两种寻址方式:功能寻址与物理寻址。功能寻址可以简单地理解为广播的形式,例如诊断仪发送一个广播帧询问有哪些节点存活着,它不针对指定的ECU;而物理寻址则是针对指定的ECU,因此每个ECU都会对应确定的逻辑地址,当诊断仪想要和指定的ECU进行通信时,就会采用物理寻址的方式,指定ECU的逻辑地址发起连接请求。

简单了解了UDS的寻址方式之后,我们就能明白Doipserver广播帧中携带逻辑地址的意义。

doipclient起来后首先接收广播帧,即协议中描述为“车辆发现”的动作,通过接收doipserver的广播帧,doipclient获得了server端的逻辑地址及车辆VIN码信息,接着连接13400端口,并发起激活路由的请求(Routing activation request)。UDS协议中,在执行针对某个ECU的诊断之前,首先要激活路由。接着发送具体的诊断报文“ReadDataByIdentifier”,“ReadDataByIdentifier”属于UDS的标准服务之一,诊断仪通过指定需要读取数据的Identifier,读取目标ECU中的对应数据:

通过调整doipclient的log级别,我们可以在终端输出doipclient接收到的数据,这些数据由边缘节点生成,包括accelerator、brake和steering的实时数据:

到目前为止,我们看到了doipclient连接doipserver的过程,并简单分析了doipclient与doipserver之间进行UDS诊断通信的过程。该过程仅包含了以太网的通信,接下来我们看看诊断报文转发到CAN网络的过程,首先上一张效果图: 

上面的gif效果显示,ICSim周期性地接收到了左转向和右转向信号,说明doipclient的报文被成功转发到了vcan0,即我们成功地通过远程诊断仪实现了对车辆功能的控制。通过candump验证vcan0接收到的消息:

vcan0上接收到的消息包括ICSim左转向、ICSim右转向、VCDS gateway request及VCDS response。candump的结果显示了can消息中包含的canid,消息id(方括号中的内容),以及can报文的data部分。canid 0x188即ICSim的逻辑地址,数据01表示开启左转向灯,数据02表示开启右转向灯。VCDS的request和response包含两个不同的逻辑地址0x710、0x77A,因为当ECU节点作为发送方和接收方时,分别对应一个逻辑地址。uds-server接收到的请求数据如下:

通过调整doipserver的log级别,我们可以看到doipserver收发请求的完整过程,如下图所示:

实验至此就结束了,过程中涉及到的相关知识点均轻笔带过,本文仅作为大家学习的一个引子,提供一种可实验、可操作的入门方式,感兴趣的朋友可以继续全面深入地学习相关的知识。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//