Automotive

Overview

Note

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

Protocols

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

OSI层

Protocol

Scapy实现

应用层

UDS(ISO 14229)

UDS,UDS_ *,UDS_TesterPresentSender

GMLAN

GMLAN,GMLAN_ *,GMLAN_TesterPresentSender

SOME/IP

SD,SOMEIP

宝马ENET

ENET,ENETSocket

OBD

OBD,OBD_S0X

CCP

CCP,DTO,CRO

运输层

ISO-TP(ISO 15765-2)

ISOTPSocket,ISOTPNativeSocket,ISOTPSoftSocket

ISOTPSniffer,ISOTPMessageBuilder,ISOTPSession

ISOTPHeader,ISOTPHeaderEA,ISOTPScan

ISOTP,ISOTP_SF,ISOTP_FF,ISOTP_CF,ISOTP_FC

数据链路层

CAN(ISO 11898)

CAN CANSocket,rdcandump,CandumpReader

CAN Layer

How-To

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

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

socket = CANSocket(iface='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(iface=VectorBus(0, bitrate=1000000))
packet = CAN(identifier=0x123, data=b'01020304')

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

socket.sr1(packet)

Tutorials

Linux SocketCAN

本小节总结了有关Linux SocketCAN的一些基础知识. 可以在以下位置找到Oliver Hartkopp的出色概述: https ://wiki.automotivelinux.org/_media/agl-distro/agl2017-socketcan-print.pdf

Virtual CAN Setup

Linux SocketCAN支持虚拟CAN接口. 这些接口是在CAN总线上执行某些第一步的简单方法,而无需特殊的硬件. 除此之外,Scapy单元测试中大量使用虚拟CAN接口来提供与汽车相关的功能.

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

sudo modprobe vcan

为了使用虚拟CAN接口,需要一些其他命令来进行设置. 此代码段为虚拟CAN接口选择名称vcan0 . 可以在这里选择任何名称:

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)

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

ip link set vcan0 type can help  # shows additional information

Linux can-utils

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

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

../_images/animation-cansend.svg

CAN Frame

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

以下示例假定您的Scapy会话中的CAN层已加载. 如果不是,可以在您的Scapy会话中使用以下命令加载CAN层:

>>> load_layer("can")

创建标准的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

CAN Frame in- and export

CAN帧可以写入pcap文件或从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输出和日志文件导入CAN帧. CandumpReader类可以与socket对象相同的方式使用. 这使您可以使用Scapy的sniff和其他功能:

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

Scapy CANSocket

在Scapy中,实现了两种CANSocket. 一种实现称为Native CANSocket ,另一种实现称为Python-can CANSocket .

由于Python 3支持PF_CAN套接字,因此本机CANSocket可以在具有Python 3或更高版本的基于Linux的系统上使用. 这些套接字具有性能优势,因为可以在其上调用select . 这在MITM方案中有很大的影响.

由于兼容性原因, Python-can CANSockets被添加到Scapy. 在Windows或OSX以及所有没有Python 3的系统上,可以通过python-can访问CAN总线. 需要在系统上安装python-canhttps : //github.com/hardbyte/python-can/ Python-can CANSocketsScapy的python-can接口对象的包装. 两个CANSocket提供相同的API,这使得它们在大多数情况下都可以互换. 但是,每种CANSocket类型的某些独特行为都必须得到尊重. 仅Windows支持某些CAN接口,例如Vector硬件. 这些接口可以通过Python-can CANSockets使用 .

Native 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_filters的本机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

Sniff on a CANSocket:

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

CANSocket python-can

在Windows,OSX或Linux上使用各种CAN接口都需要python-can. python-can库通过CANSocket对象使用. 要创建python-can CANSocket对象,必须使用python-can interface.Bus所有参数.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-can的更多详细信息,请检查: https : //python-can.readthedocs.io/

CANSocket MITM attack with bridge and sniff

该示例说明了如何在虚拟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")

创建可以攻击的套接字套接字:

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 File Format and CAN Signals

为了支持DBC文件格式, SignalFieldsSignalPacket类已添加到Scapy. SignalFields仅应在SignalPacket内部使用. 可以通过ConditionalFields创建多路复用器字段(MUX). 以下示例演示了用法:

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

此DBC描述的Scapy实现:

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标识符将报头与有效负载结合在一起. 如果您想直接接收SignalPacketsCANSocket ,提供参数baseclsinit你的函数CANSocket .

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

CAN Calibration Protocol (CCP)

CCP源自CAN. CAN报头是CCP帧的一部分. CCP有两种类型的消息对象. 一个称为命令接收对象(CRO),另一个称为数据传输对象(DTO). 通常,将CRO发送到ECU,并从ECU接收DTO. 如果一个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")

如果我们对ECU的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

System compatibilities

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

Python操作系统

带有can_isotp的Linux

Linux wo can_isotp

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设置为ISOTPNativeSocketISOTPSoftSocket . 该决定取决于配置conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True} (选择ISOTPNativeSocket )或conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} (以选择ISOTPSoftSocket ). 这将允许您编写独立于平台的代码. 在使用load_contrib("isotp")加载ISOTP层之前应用此配置.

关于ISOTPSocket兼容性的另一点评论. 始终与with一起用于套接字创建. 例:

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

ISOTP message

创建一条ISOTP消息:

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()

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

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)

ISOTP MITM attack with bridge and sniff

在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:

首先,请确保您安装了iso-tp内核模块.

当vcan核心模块加载有" sudo modprobe vcan"时,iso-tp模块可以加载到内核.

Therefore navigate to isotp directory, and load module with “sudo insmod ./net/can/can-isotp.ko”. (Tested on Kernel 4.9.135-1-MANJARO)

您可以在https://github.com/hartkopp/can-isotp中找到详细的说明.

导入模块:

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

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

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内核模块. python-can项目用于在Linux之外的其他系统上支持CAN和CANSockets. 本指南介绍了BeagleBone Black的硬件设置. 之所以选择BeagleBone Black,是因为它在主处理器上具有两个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()

ISOTPSocket将不遵守ISOTP消息对象的src, dst, exdst, exsrc .

ISOTP Sockets

Scapy提供了两种ISOTP套接字. 一种实现方式是ISOTPNativeSocket使用Hartkopp的Linux内核模块. 另一种实现是ISOTPSoftSocket完全用Python实现. 此实现可以在Linux,Windows和OSX上使用.

ISOTPNativeSocket

Requires:

  • Python3

  • Linux

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

在渗透测试期间,通常,ISOTPNativeSockets具有更好的性能和可靠性. 如果您使用的是Linux,请考虑以下实现:

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

由于此实现使用的是标准Linux套接字,因此所有Scapy功能(如sniff, sr, sr1, bridge_and_sniff可以直接使用.

ISOTPSoftSocket

ISOTPSoftSockets可以使用任何CANSocket. 这提供了使用所有python-can接口的灵活性. 此外,这些套接字可在Python2和Python3上使用. 在具有本地CANSocket的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对象.

注意: ISOTPSoftSockets的内部实现需要一个后台线程. 为了能够正确关闭该线程,我们建议使用with声明的Python.

ISOTPScan and ISOTPScanner

ISOTPScan是一种实用程序功能,用于在CAN总线上查找ISOTP端点. ISOTPScanner是相同功能的命令行实用程序.

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

Commandline usage example:

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

交互式外壳用法示例:

>>> 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有效负载,也可以直接通过ISOTPSocket发送UDS数据包. 每个OEM都有其自己的UDS定制. 这增加了通用应用程序的难度,并且渗透测试需要OEM特定知识. RoutineControl作业和ReadDataByIdentifier / WriteDataByIdentifier服务是高度自定义的.

basecls=UDSinit函数上使用basecls=UDS参数.

这是两个用法示例:

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

Customization of UDS_RDBI, UDS_WDBI

在实际的用例中,UDS层是高度定制的. OEM定义了自己的数据包子结构. 特别是数据包ReadDataByIdentifier或WriteDataByIdentifier具有非常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非常相似. 这是通用汽车的应用层协议,用于汽车的闪烁,校准和诊断. 在basecls=GMLANinit函数上使用basecls=GMLAN参数.

用法示例:

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

ECU Utility examples

ECU实用程序可用于分析正在调查的ECU的内部状态. 该实用程序在很大程度上取决于所使用协议的支持. 支持UDS .

Log all commands applied to an 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]

Trace all commands applied to an ECU

此示例显示ECU对象的跟踪机制. ECU对象的当前状态和接收到的消息的迹线打印在标准输出上. 根据协议,某些消息将更改ECU的内部状态.

用法示例:

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

Generate supported responses of an 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)

Analyze multiple UDS messages

本示例说明如何从包含CAN消息的.pcap文件中加载UDS消息. PcapReader对象用作套接字, ISOTPSessionCAN帧解析为ISOTP帧,然后通过basecls参数将其baseclsUDS对象

用法示例:

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

Analyze on the fly with ECUSession

此示例显示了嗅探中的ECUSession的用法. 可以使用ISOTPSocket或返回正确协议的完整消息的任何类似于套接字的对象. ECUSession用作ECUSession中的超级ISOTPSession . 要从ECUSession获取ECU对象,必须在嗅探之外创建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)

SOME/IP and SOME/IP SD messages

Creating a SOME/IP message

本示例显示了一条SOME / IP消息,该消息使用方法0x421请求服务0x1234. 不同类型的SOME / 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")

创建SOME / 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

Add the payload:

sip.add_payload(Raw ("Hello"))

堆叠并发送:

p = i/u/sip
send(p)

Creating a SOME/IP SD message

在此示例中,显示了带有IPv4端点的SOME / IP SD提供服务消息. 不同的条目和选项基本上遵循此处所示的相同步骤,并且可以在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端口必须是为SOME / 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

创建options数组输入:

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)

Stack it and send it:

p = i/u/sd
send(p)

OBD

OBD message

OBD是在ISOTP之上实现的. 使用ISOTPSocket与ECU通信. 您应该在ISOTPSocket初始化调用中设置参数basecls=OBDpadding=True .

OBD分为不同的服务组. 以下是一些示例请求:

请求服务0x01支持的PID:

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不需要有效负载. 在CAN上的OBD上未实现服务05. 服务06支持监视标识符(中). 服务08支持测试标识符(tid). 服务09支持信息标识符(iid).

Examples:

请求支持的信息标识符:

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

Test-Setup Tutorials

Hardware Setup

Beagle Bone Black Operating System Setup

  1. 下载图片
    最新的Debian Linux映像可在网站上找到
    https://beagleboard.org/latest-images . 选择BeagleBone Black IoT版本并下载.
    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的以太网连接共享主机的Internet连接. 可以在此页面上找到共享主机网络连接的教程:
    https://elementztechblog.wordpress.com/2014/12/22/sharing-internet -using-network-over-usb-in-beaglebone-black/.
    以root用户身份登录到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
    

Dual-CAN Setup

  1. 设备树设置
    仅当要使用两个CAN接口(DCAN0和DCAN1)时,才需要遵循此部分. 这将禁止I2C2使用DCAN0所需的引脚P9.19和P9.20. 您只需执行一次本节中的步骤.
    警告:本节中的配置将使BBB斗篷无法工作. 每个海角都有一个小的I2C EEPROM,用于存储BBB为了与海角通信而需要知道的信息. 禁用I2C2,并且BBB无法与Cape EEPROM进行通信. 当然,如果您不使用斗篷,那么这不是问题.
    获取与您的内核版本匹配的DTS源. 转到此处 ,切换到代表您的内核版本的分支. 将整个分支下载为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 [email protected] am335x-boneblack.dts
    # cp /boot/dtbs/am335x-boneblack.dtb /boot/dtbs/am335x-boneblack.orig.dtb
    # cp am335x-boneblack.dtb /boot/dtbs/
    Reboot
    
  2. Overlay setup
    本节介绍如何为两个CAN设备(DCAN0和DCAN1)构建设备覆盖. 您只需执行一次本节中的步骤.
    通过以下两种方式之一获取BBB披风覆盖物:
    # 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 [email protected] BB-CAN0-00A0.dts
    # dtc -W no-unit_address_vs_reg -O dtb -o BB-CAN1-00A0.dtbo -b 0 [email protected] 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. 测试双CAN设置
    每次需要CAN时,请执行以下操作,或者根据需要自动执行这些步骤.
    # 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
    感谢Tom Haramori. 本节的部分内容从他的指南中复制而来: https : //github.com/haramori/rhme3/blob/master/Preparation/BBB_CAN_setup.md

ISO-TP Kernel Module Installation

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

CAN-Interface Setup

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

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

Raspberry Pi SOME/IP setup

为了构建一个小型测试环境,您可以在其中向服务器实例发送SOME / IP消息或从服务器实例发送SOME / IP消息,或者伪装成服务器,一个Raspberry Pi,您的便携式计算机和vsomeip库就足够了.

  1. 下载图片

    下载最新的raspbian映像( https://www.raspberrypi.org/downloads/raspbian/ )并将其安装在Raspberry上.

  2. Vsomeip设置

    在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 ).

Software Setup

Cannelloni Framework Installation

Cannelloni框架是一个用C ++编写的小型应用程序,用于通过UDP传输CAN数据. 这样,研究人员可以将远程设备的CAN通信映射到其工作站,甚至可以在其机器上组合多个远程CAN设备. 可以从以下网站下载该框架: https://github.com/mguentner/cannelloni.git : 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 &