汽车

概述

注解

所有与汽车相关的功能在Linux系统上都能发挥最佳效果。Scapy中的CANSockets和ISOTPSockets基于Linux内核模块。python can项目用于支持除Linux之外的其他系统上的can和CANSockets。本指南介绍BeagleBone Black上的硬件设置。选择BeagleBone Black是因为它在主处理器上有两个CAN接口。一个设备中存在两个CAN接口,这就可能导致CAN MITM攻击和会话劫持。Cannelloni框架将单板计算机转换为CAN-to-UDP接口,这使您可以在更强大的机器上运行Scapy。

协议

下表应简要概述SCAPY的所有汽车功能。大多数应用层协议都有许多专门的 Packet 类。这些特殊用途类不属于本概述的一部分。使用 explore() 函数获取一个特定协议的所有信息。

应用层

协议

欺诈性实施

应用层

UDS(ISO 14229)

UDS,UDS,UDS测试仪代表发送方

GMLAN

GMLAN,GMLAN,GMLAN测试仪发送方

一些/知识产权

索米普

宝马ENET

网络,网络插座

OBD

OBD公司

CCP

CCP、DTO、CRO

运输层

ISO-TP(ISO 15765-2)

等电位插座,等电位插座,等电位插座

ISOTPSniffer,ISOTPMessageBuilder,ISOTPSession

异硫醚

等温线,等温线,等温线,等温线,等温线

数据链路层

CAN(ISO 11898)

CAN、CANSocket、rdcandump、CANDUMP读卡器

罐头层

如何

通过Linux SocketCAN发送和接收消息:

load_layer('can')
load_contrib('cansocket')

socket = CANSocket(channel='can0')
packet = CAN(identifier=0x123, data=b'01020304')

socket.send(packet)
rx_packet = socket.recv()

socket.sr1(packet, timeout=1)

通过矢量CAN接口发送消息:

import can
load_layer('can')
conf.contribs['CANSocket'] = {'use-python-can' : True}
load_contrib('cansocket')
from can.interfaces.vector import VectorBus

socket = CANSocket(channel=VectorBus(0, bitrate=1000000))
packet = CAN(identifier=0x123, data=b'01020304')

socket.send(packet)
rx_packet = socket.recv()

socket.sr1(packet)

教程

Linux SocketCAN

本小节总结了一些关于Linux SocketCAN的基础知识。Oliver Hartkopp的精彩概述如下:https://wiki.automotivelinux.org//u media/agl-distro/agl2017-socketcan-print.pdf

虚拟CAN设置

Linux SocketCAN支持虚拟CAN接口。这些接口是在CAN总线上执行一些初始步骤的简单方法,无需特殊硬件。除此之外,虚拟CAN接口在Scapy单元测试中得到了广泛的应用。

虚拟CAN套接字需要一个特殊的Linux内核模块。以下shell命令加载所需的模块:

sudo modprobe vcan

为了使用虚拟CAN接口,需要一些附加的设置命令。此代码段选择名称 vcan0 用于虚拟CAN接口。可以在此处选择任何名称:

sudo ip link add name vcan0 type vcan
sudo ip link set dev vcan0 up

同样的命令可以从Scapy执行,如下所示:

from scapy.layers.can import *
import os

bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'"
os.system(bashCommand)

如果需要,可以将CAN接口设置为 listen-onlyloopback 模式与 ip link set 命令:

ip link set vcan0 type can help  # shows additional information

Linux can实用程序

作为Linux SocketCAN的一部分,Oliver Hartkopp提供了一些非常有用的命令行工具:https://github.com/linux-can/can-utils

下面的示例显示了Linux can-utils的基本功能。这些实用程序对于从命令行快速检查、转储、发送或记录CAN消息非常方便。

../_images/animation-cansend.svg

CAN帧

有关的基本信息可以在这里找到:https://en.wikipedia.org/wiki/CAN_总线

下面的示例假设Scapy会话中的CAN层已加载。如果没有,可以在Scapy会话中使用以下命令加载CAN层:

>>> load_layer("can")

标准罐架的创建:

>>> frame = CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')

扩展CAN框架的创建:

frame = CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')
>>> frame.show()
###[ CAN ]###
  flags= extended
  identifier= 0x10010000
  length= 8
  reserved= 0
  data= '\x01\x02\x03\x04\x05\x06\x07\x08'
../_images/animation-scapy-canframe.svg

可框接和导出

帧可以写入和读取吗 pcap 文件夹::

x = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')
wrpcap('/tmp/scapyPcapTest.pcap', x, append=False)
y = rdpcap('/tmp/scapyPcapTest.pcap', 1)
../_images/animation-scapy-rdpcap.svg

另外,可以从 candump 输出和日志文件。这个 CandumpReader 类的用法与 socket 对象。这允许您使用 sniff 以及来自Scapy的其他功能:

with CandumpReader("candump.log") as sock:
    can_msgs = sniff(count=50, opened_socket=sock)
../_images/animation-scapy-rdcandump.svg

斯皮坎索克

在Scapy中,实现了两种cansocket。一个实现称为 本地CANSocket ,另一个实现称为 Python-can CANSocket .

因为python3支持 PF_CAN 插座, 本地CANSockets 可以在基于Linux的系统上使用Python3或更高版本。这些套接字具有性能优势,因为 select 对他们是可以召唤的。MITM在这种情况下有很大的影响。

出于兼容性原因, Python-can CANSockets 被添加到Scapy中。在Windows或OSX以及所有没有python3的系统上,可以通过 python-can . python-can 需要安装在系统上:https://github.com/hardbyte/python-can/ Python-can CANSockets 是scapython can接口对象的包装器。两个cansocket都提供了相同的API,这使得它们在大多数情况下都是可交换的。然而,必须尊重每种CANSocket类型的一些独特行为。一些CAN接口,如Vector硬件仅在Windows上受支持。这些接口可以通过 Python-can CANSockets .

本地CANSocket

创建简单的本机cansocket::

conf.contribs['CANSocket'] = {'use-python-can': False} #(default)
load_contrib('cansocket')

# Simple Socket
socket = CANSocket(iface="vcan0")

创建本机cansocket只侦听id==0x200:的消息:

socket = CANSocket(iface="vcan0", can_filters=[{'can_id': 0x200, 'can_mask': 0x7FF}])

创建本机cansocket仅侦听ID大于等于0x200且ID小于等于0x2ff:的消息:

socket = CANSocket(iface="vcan0", can_filters=[{'can_id': 0x200, 'can_mask': 0x700}])

创建本机cansocket只侦听ID为的消息!= 0x200:

socket = CANSocket(iface="vcan0", can_filters=[{'can_id': 0x200 | CAN_INV_FILTER, 'can_mask': 0x7FF}])

使用多个can_筛选器创建本机cansocket::

socket = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff},
                                               {'can_id': 0x400, 'can_mask': 0x7ff},
                                               {'can_id': 0x600, 'can_mask': 0x7ff},
                                               {'can_id': 0x7ff, 'can_mask': 0x7ff}])

创建本机cansocket,它还接收自己的消息:

socket = CANSocket(iface="vcan0", receive_own_messages=True)
../_images/animation-scapy-native-cansocket.svg

在CanSocket上嗅探:

../_images/animation-scapy-cansockets-sniff.svg

CanSocket python罐

python can需要在Windows、OSX或Linux上使用各种can接口。python can库是通过CANSocket对象使用的。要创建python CANSocket对象,python的所有参数都可以 interface.Bus 对象必须用于CANSocket的初始化。

创建python can cansocket的方法:

conf.contribs['CANSocket'] = {'use-python-can': True}
load_contrib('cansocket')

创建一个简单的python can cansocket::

socket = CANSocket(bustype='socketcan', channel='vcan0', bitrate=250000)

创建具有多个过滤器的python can cansocket::

socket = CANSocket(bustype='socketcan', channel='vcan0', bitrate=250000,
                can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff},
                            {'can_id': 0x400, 'can_mask': 0x7ff},
                            {'can_id': 0x600, 'can_mask': 0x7ff},
                            {'can_id': 0x7ff, 'can_mask': 0x7ff}])

有关python的更多详细信息,请查看:https://python-can.readthedocs.io/

用桥接器和嗅探来攻击MITM

这个例子展示了如何在虚拟CAN接口上使用桥接和嗅探。对于真实世界的应用程序,使用真实的CAN接口。在Linux终端上设置两个VCAN::

sudo modprobe vcan
sudo ip link add name vcan0 type vcan
sudo ip link add name vcan1 type vcan
sudo ip link set dev vcan0 up
sudo ip link set dev vcan1 up

导入模块:

import threading
load_contrib('cansocket')
load_layer("can")

创建用于攻击的CAN套接字::

socket0 = CANSocket(iface='vcan0')
socket1 = CANSocket(iface='vcan1')

创建一个函数来发送带线程的数据包:

def sendPacket():
    sleep(0.2)
    socket0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))

创建用于转发或更改数据包的函数::

def forwarding(pkt):
    return pkt

创建一个在两个套接字之间桥接和嗅探的函数:

def bridge():
    bSocket0 = CANSocket(iface='vcan0')
    bSocket1 = CANSocket(iface='vcan1')
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1)
    bSocket0.close()
    bSocket1.close()

创建用于发送数据包和桥接和嗅探的线程::

threadBridge = threading.Thread(target=bridge)
threadSender = threading.Thread(target=sendMessage)

启动线程:

threadBridge.start()
threadSender.start()

嗅探数据包:

packets = socket1.sniff(timeout=0.3)

关闭插座:

socket0.close()
socket1.close()
../_images/animation-scapy-cansockets-mitm.svg../_images/animation-scapy-cansockets-mitm2.svg

DBC文件格式和CAN信号

为了支持DBC文件格式, SignalFields 以及 SignalPacket 课程被添加到Scapy中。 SignalFields 只能在 SignalPacket . 多路复用器字段(mux)可以通过 ConditionalFields . 下面的示例演示了用法:

DBC Example:

BO_ 4 muxTestFrame: 7 TEST_ECU
 SG_ myMuxer M : 53|3@1+ (1,0) [0|0] ""  CCL_TEST
 SG_ muxSig4 m0 : 25|7@1- (1,0) [0|0] ""  CCL_TEST
 SG_ muxSig3 m0 : 16|9@1+ (1,0) [0|0] ""  CCL_TEST
 SG_ muxSig2 m0 : 15|8@0- (1,0) [0|0] ""  CCL_TEST
 SG_ muxSig1 m0 : 0|8@1- (1,0) [0|0] ""  CCL_TEST
 SG_ muxSig5 m1 : 22|7@1- (0.01,0) [0|0] ""  CCL_TEST
 SG_ muxSig6 m1 : 32|9@1+ (2,10) [0|0] "mV"  CCL_TEST
 SG_ muxSig7 m1 : 2|8@0- (0.5,0) [0|0] ""  CCL_TEST
 SG_ muxSig8 m1 : 0|6@1- (10,0) [0|0] ""  CCL_TEST
 SG_ muxSig9 : 40|8@1- (100,-5) [0|0] "V"  CCL_TEST

BO_ 3 testFrameFloat: 8 TEST_ECU
 SG_ floatSignal2 : 32|32@1- (1,0) [0|0] ""  CCL_TEST
 SG_ floatSignal1 : 7|32@0- (1,0) [0|0] ""  CCL_TEST

Scapy此DBC描述的实现:

class muxTestFrame(SignalPacket):
    fields_desc = [
        LEUnsignedSignalField("myMuxer", default=0, start=53, size=3),
        ConditionalField(LESignedSignalField("muxSig4", default=0, start=25, size=7), lambda p: p.myMuxer == 0),
        ConditionalField(LEUnsignedSignalField("muxSig3", default=0, start=16, size=9), lambda p: p.myMuxer == 0),
        ConditionalField(BESignedSignalField("muxSig2", default=0, start=15, size=8), lambda p: p.myMuxer == 0),
        ConditionalField(LESignedSignalField("muxSig1", default=0, start=0, size=8), lambda p: p.myMuxer == 0),
        ConditionalField(LESignedSignalField("muxSig5", default=0, start=22, size=7, scaling=0.01), lambda p: p.myMuxer == 1),
        ConditionalField(LEUnsignedSignalField("muxSig6", default=0, start=32, size=9, scaling=2, offset=10, unit="mV"), lambda p: p.myMuxer == 1),
        ConditionalField(BESignedSignalField("muxSig7", default=0, start=2, size=8, scaling=0.5), lambda p: p.myMuxer == 1),
        ConditionalField(LESignedSignalField("muxSig8", default=0, start=3, size=3, scaling=10), lambda p: p.myMuxer == 1),
        LESignedSignalField("muxSig9", default=0, start=41, size=7, scaling=100, offset=-5, unit="V"),
    ]

class testFrameFloat(SignalPacket):
    fields_desc = [
        LEFloatSignalField("floatSignal2", default=0, start=32),
        BEFloatSignalField("floatSignal1", default=0, start=7)
    ]

bind_layers(SignalHeader, muxTestFrame, identifier=0x123)
bind_layers(SignalHeader, testFrameFloat, identifier=0x321)

dbc_sock = CANSocket("can0", basecls=SignalHeader)

pkt = SignalHeader()/testFrameFloat(floatSignal2=3.4)

dbc_sock.send(pkt)

此示例使用类 SignalHeader 作为标题。有效载荷由个人指定 SignalPackets . bind_layers 根据CAN标识符将收割台与有效负载组合。如果你想直接接收 SignalPackets 从你 CANSocket ,提供参数 baseclsinit 您的功能 CANSocket .

Canmatrix支持从DBC或AUTOSAR XML文件创建Scapy文件https://github.com/ebroecker/canmatrix

CAN校准协议(CCP)

CCP源自CAN。CAN头是CCP框架的一部分。CCP有两种类型的消息对象。一个称为命令接收对象(CRO),另一个称为数据传输对象(DTO)。通常,CRO发送到一个ECU,DTO从一个ECU接收。如果一个DTO回答CRO,则信息通过计数器字段(ctr)实现。如果两个对象都具有相同的计数器值,则可以通过相关CRO对象的命令来解释DTO对象的有效负载。

创建CRO消息::

CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02)
CCP(identifier=0x711)/CRO(ctr=2)/GET_SEED(resource=2)
CCP(identifier=0x711)/CRO(ctr=3)/UNLOCK(key=b"123456")

如果我们对电子控制单元的DTO不感兴趣,我们可以这样发送一条CRO消息:发送一条CRO消息:

pkt = CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02)
sock = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000))
sock.send(pkt)

如果我们对ECU的DTO感兴趣,我们需要将cansocket的basecls参数设置为ccp,我们需要使用sr1:发送cro消息:

cro = CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12")
sock = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000), basecls=CCP)
dto = sock.sr1(cro)
dto.show()
###[ CAN Calibration Protocol ]###
  flags=
  identifier= 0x700
  length= 8
  reserved= 0
###[ DTO ]###
     packet_id= 0xff
     return_code= acknowledge / no error
     ctr= 83
###[ PROGRAM_6_DTO ]###
        MTA0_extension= 2
        MTA0_address= 0x34002006

由于sr1调用answers函数,我们的dto对象的有效负载通过cro对象的命令得到解释。

ISOTP

系统兼容性

根据您的设置,必须使用不同的实现。

Python操作系统

Linux与can ou isopp

Linux wo can ou isopp

Windows/OSX

Python 3

ISOTPNativeSocket

ISOTPSoftSocket

ISOTPSoftSocket

conf.contribs['CANSocket'] = {'use-python-can': True}

conf.contribs['CANSocket'] = {'use-python-can': False}

Python 2

ISOTPSoftSocket

conf.contribs['CANSocket'] = {'use-python-can': True}

班级 ISOTPSocket 可以设置为 ISOTPNativeSocket 或A ISOTPSoftSocket . 决定取决于配置 conf.contribs['ISOTP'] = {{'use-can-isotp-kernel-module': True}} (选择 ISOTPNativeSocketconf.contribs['ISOTP'] = {{'use-can-isotp-kernel-module': False}} (选择 ISOTPSoftSocket )这将允许您编写与平台无关的代码。在加载各向同性层之前应用此配置 load_contrib("isotp") .

关于等粒子相容性的另一个评论。总是与一起用于套接字创建。例子::

with ISOTPSocket("vcan0", did=0x241, sid=0x641) as sock:
    sock.send(...)

同位素信息

创建一条等幅线消息:

load_contrib('isotp')
ISOTP(src=0x241, dst=0x641, data=b"\x3eabc")

使用扩展寻址创建一个ISOTP消息:

ISOTP(src=0x241, dst=0x641, exdst=0x41, data=b"\x3eabc")

使用扩展寻址创建一个ISOTP消息:

ISOTP(src=0x241, dst=0x641, exdst=0x41, exsrc=0x41, data=b"\x3eabc")

从ISOTP消息创建CAN帧::

ISOTP(src=0x241, dst=0x641, exdst=0x41, exsrc=0x55, data=b"\x3eabc" * 10).fragment()

通过isopp套接字发送isopp消息::

isoTpSocket = ISOTPSocket('vcan0', sid=0x241, did=0x641)
isoTpMessage = ISOTP('Message')
isoTpSocket.send(isoTpMessage)

嗅探ISOTP消息:

isoTpSocket = ISOTPSocket('vcan0', sid=0x641, did=0x241)
packets = isoTpSocket.sniff(timeout=0.5)

用桥接和嗅探等电位MITM攻击

在Linux终端上设置两个VCAN::

sudo modprobe vcan
sudo ip link add name vcan0 type vcan
sudo ip link add name vcan1 type vcan
sudo ip link set dev vcan0 up
sudo ip link set dev vcan1 up

设置ISOTP:

首先确保安装了isotp内核模块。

当VCAN核心模块加载“sudo modprobe vcan”时,ISO TP模块可以加载到内核。

因此,导航到isopp目录,并使用“sudo insmod./net/can/can isopp.ko”加载模块。(在内核4.9.135-1-manjaro上测试)

详细说明请参见https://github.com/hartkopp/can-isopp。

导入模块:

import threading
load_contrib('cansocket')
conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
load_contrib('isotp')

创建到isopp套接字进行攻击::

isoTpSocketVCan0 = ISOTPSocket('vcan0', sid=0x241, did=0x641)
isoTpSocketVCan1 = ISOTPSocket('vcan1', sid=0x641, did=0x241)

创建用于在VCAN0上发送数据包的函数,线程为:

def sendPacketWithISOTPSocket():
    sleep(0.2)
    packet = ISOTP('Request')
    isoTpSocketVCan0.send(packet)

创建转发数据包的函数:

def forwarding(pkt):
    return pkt

创建在两条总线之间桥接和嗅探的函数:

def bridge():
    bSocket0 = ISOTPSocket('vcan0', sid=0x641, did=0x241)
    bSocket1 = ISOTPSocket('vcan1', sid=0x241, did=0x641)
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1)
    bSocket0.close()
    bSocket1.close()

创建用于发送数据包和桥接和嗅探的线程::

threadBridge = threading.Thread(target=bridge)
threadSender = threading.Thread(target=sendPacketWithISOTPSocket)

启动线程基于Linux内核模块。除了Linux之外,python can项目还用于支持其他系统上的can和cansockets。本指南介绍了BeagleboneBlack的硬件设置。选择BeagleboneBlack是因为它在主处理器上有两个CAN接口。在一个设备中存在两个CAN接口,可能会导致CAN MITM攻击和会话劫持。cannelloni框架将beaglebone black转换为can-to-udp接口,使您可以在更强大的机器上自由运行scapy。::

threadBridge.start()
threadSender.start()

在VCAN1上嗅探:

receive = isoTpSocketVCan1.sniff(timeout=1)

关闭套接字:

isoTpSocketVCan0.close()
isoTpSocketVCan1.close()

一个等速球不会尊重 src, dst, exdst, exsrc 一个isopp消息对象。

同位素插座

scapy提供了两种isopp套接字。其中一个实现是使用Hartkopp的Linux内核模块。另一个实现,isoppsOftsocket在python中完全实现。这个实现可以在Linux、Windows和OSX上使用。

ISOTPNativeSocket

要求:

  • Python 3

  • Linux系统

  • Hartkopp的Linux内核模块: https://github.com/hartkopp/can-isotp.git

Isolestaptics通常在测试中有更好的可靠性。如果您正在使用Linux,请考虑以下实现:

conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
load_contrib('isotp')
sock = ISOTPSocket("can0", sid=0x641, did=0x241)

因为这个实现使用的是标准的Linux套接字,所以所有的垃圾函数都像 sniff, sr, sr1, bridge_and_sniff 开箱即用。

ISOTPSoftSocket

isopsuftsockets可以使用任何cansocket。这使得可以灵活地使用所有的python can接口。此外,这些插座可用于python2和python3。在带有本机cansockets的Linux上的用法:

conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False}
load_contrib('isotp')
with ISOTPSocket("can0", sid=0x641, did=0x241) as sock:
    sock.send(...)

使用python can cansockets::

conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False}
conf.contribs['CANSocket'] = {'use-python-can': True}
load_contrib('isotp')
with ISOTPSocket(CANSocket(iface=python_can.interface.Bus(bustype='socketcan', channel="can0", bitrate=250000)), sid=0x641, did=0x241) as sock:
    sock.send(...)

第二个示例允许使用 python_can.interface 对象。

注意: isopsOffsockets的内部实现需要一个后台线程。为了能正确地闭合这根线,我们建议使用 Python 。 with 语句。

isotpcan和ISOTPScanner

函数是一个函数,可以在总线上找到一个ISOTP。ISOTPScanner是用于相同函数的命令行实用程序。

../_images/animation-scapy-isotpscan.svg

命令行用法示例:

python -m scapy.tools.automotive.isotpscanner -h
usage:      isotpscanner [-i interface] [-c channel] [-b bitrate]
                [-n NOISE_LISTEN_TIME] [-t SNIFF_TIME] [-x|--extended]
                [-C|--piso] [-v|--verbose] [-h|--help] [-s start] [-e end]

    Scan for open ISOTP-Sockets.

    required arguments:
    -c, --channel         python-can channel or Linux SocketCAN interface name
    -s, --start           Start scan at this identifier (hex)
    -e, --end             End scan at this identifier (hex)

    additional required arguments for WINDOWS or Python 2:
    -i, --interface       python-can interface for the scan.
                          Depends on used interpreter and system,
                          see examples below. Any python-can interface can
                          be provided. Please see:
                          https://python-can.readthedocs.io for
                          further interface examples.
    -b, --bitrate         python-can bitrate.

    optional arguments:
    -h, --help            show this help message and exit
    -n NOISE_LISTEN_TIME, --noise_listen_time NOISE_LISTEN_TIME
                          Seconds listening for noise before scan.
    -t SNIFF_TIME, --sniff_time SNIFF_TIME
                          Duration in milliseconds a sniff is waiting for a
                          flow-control response.
    -x, --extended        Scan with ISOTP extended addressing.
    -C, --piso            Print 'Copy&Paste'-ready ISOTPSockets.
    -v, --verbose         Display information during scan.

    Example of use:

    Python2 or Windows:
    python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 --bitrate=250000 --start 0 --end 100
    python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --bitrate 250000 --start 0 --end 100
    python2 -m scapy.tools.automotive.isotpscanner --interface socketcan --channel=can0 --bitrate=250000 --start 0 --end 100

    Python3 on Linux:
    python3 -m scapy.tools.automotive.isotpscanner --channel can0 --start 0 --end 100

交互式shell用法示例:

>>> conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
>>> conf.contribs['CANSocket'] = {'use-python-can': False}
>>> load_contrib('cansocket')
>>> load_contrib('isotp')
>>> socks = ISOTPScan(CANSocket("vcan0"), range(0x700, 0x7ff), can_interface="vcan0")
>>> socks
[<<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98e27c8210>,
 <<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98f9079cd0>,
 <<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98f90cd490>,
 <<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98f912ec50>,
 <<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98f912e950>,
 <<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_ISOTP socket > at 0x7f98f906c0d0>]

UDS

UDS的主要用途是闪烁和诊断ECU。UDS是一种应用层协议,可以用作DOIP或ENET负载,也可以通过一个ISOPSOCKET直接发送UDS包。每个OEM都有自己的自定义UDS。这增加了一般应用程序的难度,渗透测试需要原始设备制造商的专门知识。routineControl作业和readDataByIdentifier/writeDataByIdentifier服务都是高度定制的。

使用参数 basecls=UDSinit 等速粒子的函数。

以下是两个用法示例:

../_images/animation-scapy-uds.svg../_images/animation-scapy-uds2.svg

自定义自定义项

在实际的用例中,UDS层是大量定制的。原始设备制造商定义了他们自己的包的子结构。尤其是readdatabaseidentifier或writedatabaseidentifier有一个非常OEM甚至ECU特定的子结构。因此 StrField dataRecord 未添加到 field_desc . 预期用途是创建特定于ECU或OEM的描述文件,通过进一步的协议实现扩展scapy的通用UDS层。

自定义示例:

cat scapy/contrib/automotive/OEM-XYZ/car-model-xyz.py
#! /usr/bin/env python

# Protocol customization for car model xyz of OEM XYZ
# This file contains further OEM car model specific UDS additions.

from scapy.packet import Packet
from scapy.contrib.automotive.uds import *

# Define a new packet substructure

class DBI_IP(Packet):
name = 'DataByIdentifier_IP_Packet'
fields_desc = [
    ByteField('ADDRESS_FORMAT_ID', 0),
    IPField('IP', ''),
    IPField('SUBNETMASK', ''),
    IPField('DEFAULT_GATEWAY', '')
]

# Bind the new substructure onto the existing UDS packets

bind_layers(UDS_RDBIPR, DBI_IP, dataIdentifier=0x172b)
bind_layers(UDS_WDBI, DBI_IP, dataIdentifier=0x172b)

# Give add a nice name to dataIdentifiers enum

UDS_RDBI.dataIdentifiers[0x172b] = 'GatewayIP'

如果要使用此自定义添加,可以在运行时将其加载到scapy解释器:

>>> load_contrib("automotive.uds")
>>> load_contrib("automotive.OEM-XYZ.car-model-xyz")

>>> pkt = UDS()/UDS_WDBI()/DBI_IP(IP='192.168.2.1', SUBNETMASK='255.255.255.0', DEFAULT_GATEWAY='192.168.2.1')

>>> pkt.show()
###[ UDS ]###
  service= WriteDataByIdentifier
###[ WriteDataByIdentifier ]###
     dataIdentifier= GatewayIP
     dataRecord= 0
###[ DataByIdentifier_IP_Packet ]###
        ADDRESS_FORMAT_ID= 0
        IP= 192.168.2.1
        SUBNETMASK= 255.255.255.0
        DEFAULT_GATEWAY= 192.168.2.1

>>> hexdump(pkt)
0000  2E 17 2B 00 C0 A8 02 01 FF FF FF 00 C0 A8 02 01  ..+.............
../_images/animation-scapy-uds3.svg

GMLAN

GMLAN与UDS非常相似。这是GMS应用层协议,用于对他们的汽车进行闪烁、校准和诊断。使用参数 basecls=GMLANinit 等速粒子的函数。

用法示例:

../_images/animation-scapy-gmlan.svg

ECU实用程序示例

ECU实用程序可用于分析正在调查的ECU的内部状态。这个实用程序很大程度上依赖于所用协议的支持。 UDS 支持。

记录应用于ECU的所有命令

这个例子展示了ECU对象的日志记录机制。ECU日志是一个应用UDS命令字典。此字典的关键是UDS服务名称。该值由一组元组组成,其中包含时间戳和日志值

使用实例:

ecu = ECU(verbose=False, store_supported_responses=False)
ecu.update(PacketList(msgs))
print(ecu.log)
timestamp, value = ecu.log["DiagnosticSessionControl"][0]

跟踪应用于ECU的所有命令

这个例子展示了一个ECU对象的跟踪机制。ECU对象的当前状态的痕迹和接收到的消息被打印在stdout上。根据协议的不同,有些信息会改变ECU的内部状态。

使用实例:

ecu = ECU(verbose=True, logging=False, store_supported_responses=False)
ecu.update(PacketList(msgs))
print(ecu.current_session)

生成ECU支持的响应

这个例子展示了一种通过分析数据包列表来克隆真实世界ECU的机制。

使用实例:

ecu = ECU(verbose=False, logging=False, store_supported_responses=True)
ecu.update(PacketList(msgs))
supported_responses = ecu.supported_responses
unanswered_packets = ecu.unanswered_packets
print(supported_responses)
print(unanswered_packets)

分析多个UDS消息

此示例显示如何加载 UDS 来自的消息 .pcap 包含 CAN 信息。A PcapReader 对象用作套接字和 ISOTPSession 解析 CAN 帧到 ISOTP 然后浇铸到 UDS 对象通过 basecls 参数

使用实例:

with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock:
    udsmsgs = sniff(session=ISOTPSession, session_kwargs={"use_ext_addr":False, "basecls":UDS}, count=50, opened_socket=sock)


ecu = ECU()
ecu.update(udsmsgs)
print(ecu.log)
print(ecu.supported_responses)
assert len(ecu.log["TransferData"]) == 2

使用ECUSession动态分析

这个例子展示了ECUSession在sniff中的用法。可以使用ISOTPSocket或任何类似于套接字的对象来返回正确协议的全部消息。安 ECUSessionISOTPSession . 获得 ECU 对象来自 ECUSession , the ECUSession 必须在嗅探之外创造。

使用实例:

session = ECUSession()

with PcapReader("test/contrib/automotive/ecu_trace.pcap") as sock:
    udsmsgs = sniff(session=ISOTPSession, session_kwargs={"supersession": session, "use_ext_addr":False, "basecls":UDS}, count=50, opened_socket=sock)

ecu = session.ecu
print(ecu.log)
print(ecu.supported_responses)

一些/ip和一些/ip sd消息

创建some/ip消息

此示例显示了一个some/ip消息,该消息使用方法0x421请求服务0x1234。不同类型的某些/IP消息遵循相同的过程,它们的规范可以在这里看到。 http://www.some-ip.com/papers/cache/AUTOSAR_TR_SomeIpExample_4.2.1.pdf .

加载贡献:

load_contrib("automotive.someip")

创建UDP包::

u = UDP(sport=30509, dport=30509)

创建IP包:

i = IP(src="192.168.0.13", dst="192.168.0.10")

创建一些/IP包::

sip = SOMEIP()
sip.iface_ver = 0
sip.proto_ver = 1
sip.msg_type = "REQUEST"
sip.retcode = "E_OK"
sip.srv_id = 0x1234
sip.method_id = 0x421

添加有效载荷:

sip.add_payload(Raw ("Hello"))

堆叠并发送:

p = i/u/sip
send(p)

创建some/ip sd消息

在此示例中,显示了一些/ip sd provide服务消息和一个IPv4端点。不同的条目和选项基本上遵循与此处所示相同的过程,可以在中看到 https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_ServiceDiscovery.pdf .

加载贡献:

load_contrib("automotive.someip")

创建UDP包::

u = UDP(sport=30490, dport=30490)

UDP端口必须是为某些/IP SD传输选择的端口。

创建IP包:

i = IP(src="192.168.0.13", dst="224.224.224.245")

IP源必须来自服务,目标地址必须是所选的多播地址。

创建条目数组输入::

ea = SDEntry_Service()

ea.type = 0x01
ea.srv_id = 0x1234
ea.inst_id = 0x5678
ea.major_ver = 0x00
ea.ttl = 3

创建选项数组输入::

oa = SDOption_IP4_Endpoint()
oa.addr = "192.168.0.13"
oa.l4_proto = 0x11
oa.port = 30509

在本例中,l4_proto定义了与端点udp通信的协议。

创建sd包并输入:

sd = SD()
sd.set_entryArray(ea)
sd.set_optionArray(oa)

堆叠并发送:

p = i/u/sd
send(p)

OBD

OBD消息

OBD是在ISOTP之上实现的。使用ISOTPSocket与ECU通信。你应该设置参数 basecls=OBDpadding=True 在您的isotpsocket init调用中。

车载诊断系统分为不同的服务组。以下是一些示例请求:

请求支持的服务PIDS 0x01::

req = OBD()/OBD_S01(pid=[0x00])

响应将包含packetlistfield,调用 data_records . 此字段包含实际响应:

resp = OBD()/OBD_S01_PR(data_records=[OBD_S01_PR_Record()/OBD_PID00(supported_pids=3196041235)])
resp.show()
###[ On-board diagnostics ]###
  service= CurrentPowertrainDiagnosticDataResponse
###[ Parameter IDs ]###
     \data_records\
      |###[ OBD_S01_PR_Record ]###
      |  pid= 0x0
      |###[ PID_00_PIDsSupported ]###
      |     supported_pids= PID20+PID1F+PID1C+PID15+PID14+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID0A+PID07+PID06+PID05+PID04+PID03+PID01

假设我们测试的ECU支持PID 0x15:

req = OBD()/OBD_S01(pid=[0x15])
resp = sock.sr1(req)
resp.show()
###[ On-board diagnostics ]###
  service= CurrentPowertrainDiagnosticDataResponse
###[ Parameter IDs ]###
     \data_records\
      |###[ OBD_S01_PR_Record ]###
      |  pid= 0x15
      |###[ PID_15_OxygenSensor2 ]###
      |     outputVoltage= 1.275 V
      |     trim= 0 %

OBD中的不同服务支持不同类型的数据。服务01和服务02支持参数标识符(pid)。维修03、07和0A支持故障诊断码(dtc)。服务04不需要有效载荷。服务05未通过CAN在车载诊断系统上实施。服务06支持监视标识符(mid)。服务08支持测试标识符(tid)。服务09支持信息标识符(iid)。

实例:

请求支持的信息标识符::

req = OBD()/OBD_S09(iid=[0x00])

请求车辆识别号(VIN)::

req = OBD()/OBD_S09(iid=0x02)
resp = sock.sr1(req)
resp.show()
###[ On-board diagnostics ]###
  service= VehicleInformationResponse
###[ Infotype IDs ]###
     \data_records\
      |###[ OBD_S09_PR_Record ]###
      |  iid= 0x2
      |###[ IID_02_VehicleIdentificationNumber ]###
      |     count= 1
      |     vehicle_identification_numbers= ['W0L000051T2123456']
../_images/animation-scapy-obd.svg

测试设置教程

硬件设置

Beagle Bone Black操作系统设置

  1. 下载图像
    最新的Debian Linux映像可以在网站上找到。
    https://beagleboard.org/latest-images . 选择Beagleboon黑色物联网版本并下载。
    wget https://debian.beagleboard.org/images/bone-debian-8.7\
    -iot-armhf-2017-03-19-4gb.img.xz
    

    下载后,将其复制到至少4 GB存储空间的SD卡。

    xzcat bone-debian-8.7-iot-armhf-2017-03-19-4gb.img.xz | \
    sudo dd of=/dev/xvdj
    
  2. 启用WiFi
    Debian Linux支持USB WiFi软件狗。通过bbb上的ssh登录并将WiFi网络凭据添加到文件中 /var/lib/connman/wifi.config . 如果USB WiFi加密狗不可用,也可以与通过USB模拟的BBB以太网连接共享主机的互联网连接。共享主机网络连接的教程可在此页面上找到:
    https://elementztechblog.wordpress.com/2014/12/22/sharing-internet -using-network-over-usb-in-beaglebone-black/ .
    以根用户身份登录bbb:
    ssh debian@192.168.7.2
    sudo su
    

    向Connman提供WiFi登录凭据:

    echo "[service_home]
    Type = wifi
    Name = ssid
    Security = wpa
    Passphrase = xxxxxxxxxxxxx" \
    > /var/lib/connman/wifi.config
    

    重新启动connman服务:

    systemctl restart connman.service
    

双CAN设置

  1. 设备树设置
    只有当您想使用两个CAN接口(DCAN0和DCAN1)时,才需要遵循本节。这将禁止I2c2使用DCAN0所需的插脚P9.19和P9.20。您只需要执行本节中的步骤一次。
    警告:本节中的配置将禁用BBB Capes的工作。每个斗篷都有一个小的I2c EEPROM,它存储BBB需要知道的信息,以便与斗篷通信。禁用I2c2,BBB无法与Cape Eeproms通话。当然,如果你不使用资本支出,那么这不是问题。
    获取与内核版本匹配的DTS源。去 here 切换到表示内核版本的分支。将整个分支下载为zip文件。提取它并执行以下操作(4.1版如示例所示):
    # cd ~/src/linux-4.1/arch/arm/boot/dts/include/
    # rm dt-bindings
    # ln -s ../../../../../include/dt-bindings
    # cd ..
    Edit am335x-bone-common.dtsi and ensure the line with "//pinctrl-0 = <&i2c2_pins>;" is commented out.
    Remove the complete &ocp section at the end of this file
    # mv am335x-boneblack.dts am335x-boneblack.raw.dts
    # cpp -nostdinc -I include -undef -x assembler-with-cpp am335x-boneblack.raw.dts > am335x-boneblack.dts
    # dtc -W no-unit_address_vs_reg -O dtb -o am335x-boneblack.dtb -b 0 -@ am335x-boneblack.dts
    # cp /boot/dtbs/am335x-boneblack.dtb /boot/dtbs/am335x-boneblack.orig.dtb
    # cp am335x-boneblack.dtb /boot/dtbs/
    Reboot
    
  2. 覆盖设置
    本节介绍如何为两个CAN设备(DCAN0和DCAN1)构建设备覆盖。您只需要执行本节中的步骤一次。
    以两种方式之一获取BBB Cape Overlay…
    # apt-get install bb-cape-overlays
    https://github.com/beagleboard/bb.org-overlays/
    
    然后执行以下操作:
    # cd ~/src/bb.org-overlays-master/src/arm
    # ln -s ../../include
    # mv BB-CAN1-00A0.dts BB-CAN1-00A0.raw.dts
    # cp BB-CAN1-00A0.raw.dts BB-CAN0-00A0.raw.dts
    Edit BB-CAN0-00A0.raw.dts and make relevant to CAN0. Example is shown below.
    # cpp -nostdinc -I include -undef -x assembler-with-cpp BB-CAN0-00A0.raw.dts > BB-CAN0-00A0.dts
    # cpp -nostdinc -I include -undef -x assembler-with-cpp BB-CAN1-00A0.raw.dts > BB-CAN1-00A0.dts
    # dtc -W no-unit_address_vs_reg -O dtb -o BB-CAN0-00A0.dtbo -b 0 -@ BB-CAN0-00A0.dts
    # dtc -W no-unit_address_vs_reg -O dtb -o BB-CAN1-00A0.dtbo -b 0 -@ BB-CAN1-00A0.dts
    # cp *.dtbo /lib/firmware
    
  3. Can0覆盖示例
    在dts文件夹中,创建一个包含以下列表内容的文件。
    cd ~/bb.org-overlays/src/arm
    cat <<EOF > BB-CAN0-00A0.raw.dts
    
    /*
     * Copyright (C) 2015 Robert Nelson <robertcnelson@gmail.com>
     *
     * Virtual cape for CAN0 on connector pins P9.19 P9.20
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License version 2 as
     * published by the Free Software Foundation.
     */
    /dts-v1/;
    /plugin/;
    
    #include <dt-bindings/board/am335x-bbw-bbb-base.h>
    #include <dt-bindings/pinctrl/am33xx.h>
    
    / {
        compatible = "ti,beaglebone", "ti,beaglebone-black", "ti,beaglebone-green";
    
        /* identification */
        part-number = "BB-CAN0";
        version = "00A0";
    
        /* state the resources this cape uses */
        exclusive-use =
            /* the pin header uses */
            "P9.19",        /* can0_rx */
            "P9.20",        /* can0_tx */
            /* the hardware ip uses */
            "dcan0";
    
        fragment@0 {
            target = <&am33xx_pinmux>;
            __overlay__ {
                bb_dcan0_pins: pinmux_dcan0_pins {
                    pinctrl-single,pins = <
                        BONE_P9_19 (PIN_INPUT_PULLUP | MUX_MODE2) /* uart1_txd.d_can0_rx */
                        BONE_P9_20 (PIN_OUTPUT_PULLUP | MUX_MODE2) /* uart1_rxd.d_can0_tx */
                    >;
                };
            };
        };
    
        fragment@1 {
            target = <&dcan0>;
            __overlay__ {
                status = "okay";
                pinctrl-names = "default";
                pinctrl-0 = <&bb_dcan0_pins>;
            };
        };
    };
    EOF
    
  4. Test the Dual-CAN Setup
    每次需要时都可以执行以下操作,或者根据需要自动执行这些步骤。
    # echo BB-CAN0 > /sys/devices/platform/bone_capemgr/slots
    # echo BB-CAN1 > /sys/devices/platform/bone_capemgr/slots
    # modprobe can
    # modprobe can-dev
    # modprobe can-raw
    # ip link set can0 up type can bitrate 50000
    # ip link set can1 up type can bitrate 50000
    

    如果两个CAN接口都已加载,请检查CapeManager的输出。

    cat /sys/devices/platform/bone_capemgr/slots
    
    0: PF----  -1
    1: PF----  -1
    2: PF----  -1
    3: PF----  -1
    4: P-O-L-   0 Override Board Name,00A0,Override Manuf, BB-CAN0
    5: P-O-L-   1 Override Board Name,00A0,Override Manuf, BB-CAN1
    

    如果出了问题, dmesg 提供内核消息以分析失败的根源。

  5. References
  6. Acknowledgment
    多亏了汤姆·原森。本节的部分内容摘自他的指南:https://github.com/haramori/rhme3/blob/master/preparation/bbb_can_setup.md

ISO-TP内核模块安装

Linux ISO-TP内核模块可从以下网站下载: https://github.com/hartkopp/can-isotp.git . 文件 README.isotp 在此存储库中,提供下载和构建此内核模块所需的所有信息和必要步骤。iso-tp内核模块也应该添加到 /etc/modules 文件,以便在BBB系统启动时自动加载此模块。

CAN接口设置

作为准备BBB的CAN接口以供使用的最后一步,必须通过一些终端命令设置这些接口。比特率可以选择来适应被测CAN总线的比特率。

ip link set can0 up type can bitrate 500000
ip link set can1 up type can bitrate 500000

树莓pi-some/ip设置

要构建一个小型测试环境,在该环境中,您可以向服务器实例发送一些/IP消息,也可以将自己伪装成服务器,一个Raspberry PI、您的笔记本电脑和vsomeip库就足够了。

  1. 下载图像

    下载最新的Raspbian图像 (https://www.raspberrypi.org/downloads/raspbian/ )安装在树莓上。

  2. VSOIMIP协议

    在rapsberry上下载vsomeip库,应用git补丁,这样它就可以与更新的boost库一起工作,然后安装它。

    git clone https://github.com/GENIVI/vsomeip.git
    cd vsomeip
    wget -O 0001-Support-boost-v1.66.patch.zip \
    https://github.com/GENIVI/vsomeip/files/2244890/0001-Support-boost-v1.66.patch.zip
    unzip 0001-Support-boost-v1.66.patch.zip
    git apply 0001-Support-boost-v1.66.patch
    mkdir build
    cd build
    cmake -DENABLE_SIGNAL_HANDLING=1 ..
    make
    make install
    
  3. 制作应用程序

    编写一些小应用程序,它们可以作为服务或客户机使用,并使用scapy-some/ip实现与客户机或服务器通信。vsomeip应用程序的示例可在vsomeip github wiki页面上找到。 (https://github.com/GENIVI/vsomeip/wiki/vsomeip-in-10-minutes

软件设置

Cannelloni框架安装

ChanelONI框架是一个用C++编写的用于在UDP上传输CAN数据的小应用程序。这样,研究人员可以将远程设备的CAN通信映射到其工作站,甚至可以在其机器上组合多个远程CAN设备。框架可从以下网站下载: https://github.com/mguentner/cannelloni.git . 这个 README.md 文件详细说明了安装和使用。Cannelloni需要操作员机器上的虚拟CAN接口。下一个列表显示虚拟CAN接口的设置。

modprobe vcan

ip link add name vcan0 type vcan
ip link add name vcan1 type vcan

ip link set dev vcan0 up
ip link set dev vcan1 up

tc qdisc add dev vcan0 root tbf rate 300kbit latency 100ms burst 1000
tc qdisc add dev vcan1 root tbf rate 300kbit latency 100ms burst 1000

cannelloni -I vcan0 -R <remote-IP> -r 20000 -l 20000 &
cannelloni -I vcan1 -R <remote-IP> -r 20001 -l 20001 &