备注

本文档位于 Creative Commons Attribution - Non-Commercial - Share Alike 2.5 执照。

汽车专用文档

节作者: Nils Weiss <nils@we155.de>

概述

备注

所有与汽车相关的功能在Linux系统上运行得最好。CANSockets和ISOTPSockets基于Linux内核模块。Python-CAN项目用于在更广泛的操作系统和CAN硬件接口上支持CAN和CANSocket。

协议

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

应用层

协议

欺诈性实施

应用层

UDS(ISO 14229)

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

GMLAN

GMLAN、GMLAN_*、GMLAN [公用事业]

一些/知识产权

索米普

宝马HSFZ

HSFZ、HSFZSocket、UDS_HSFZSocket

OBD

OBD、OBD_S0 [0-9A]

CCP

CCP、DTO、CRO

XCP

XCPOnCAN、XCPOnUDP、XCPOnTCP、CTORequest、CTOResponse、DTO

运输层

ISO-TP(ISO 15765-2)

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

ISOTPSniffer,ISOTPMessageBuilder,ISOTPSession

ISOTPHeader, ISOTPHeaderEA, isotp_scan

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

数据链路层

CAN(ISO 11898)

CAN、CANSocket、rdcandump、CANDUMP读卡器

技术背景

此部分内容已在研究报告中公布 [10].

物理协议

车辆的内部有线通信有20多种不同的通信协议。大多数车辆使用五到十种不同的协议进行内部通信。决定使用原始设备制造商(OEM)的哪种通信协议通常是在通信技术成本和最终汽车价格之间进行权衡的结果。ECU间通信的四种主要通信技术是控制器局域网(CAN)、FlexRay、本地互连网络(LIN)和汽车以太网。出于安全考虑,这些是与车辆有线通信最相关的协议。

LIN

LIN是用于低数据速率的单线通信协议。车辆的致动器和传感器与ECU交换信息,充当LIN主机。可以通过LIN进行软件更新,但LIN从站通常不需要软件更新,因为它们的功能有限。

CAN

CAN是目前车辆ECU间通信中使用最多的通信技术。在较旧或较便宜的车辆中,CAN仍然是车辆主干通信的主要协议。车辆运行期间的安全关键通信、诊断信息和软件更新通过CAN在ECU之间传输。协议本身缺乏安全功能,再加上协议的通用性,使得CAN成为安全调查的主要协议。

FlexRay

FlexRay联盟将FlexRay设计为CAN的继任者。现代车辆对通信带宽有更高的要求。通过设计,FlexRay是一种用于ECU间通信的快速可靠的通信协议。FlexRay组件比CAN组件更贵,导致OEM使用更多选择。

车载以太网

最近的高级车辆采用了车载以太网这一新的车载通信主干技术。快速增长的带宽需求已经取代了FlexRay。这些需求的主要原因是驾驶员辅助和自动驾驶功能。只有开放式系统互联模型的物理层(第1层)将以太网(IEEE 802.3)与汽车以太网(BroadR-REACH)区分开来。这一设计决策带来了多方面的优势。例如,无需修改和路由、过滤和防火墙系统就可以使用操作系统的通信堆栈。汽车以太网组件已经比FlexRay组件便宜,这将导致车辆拓扑,其中CAN和汽车以太网是最常用的通信协议。

拓扑

线路总线

../_images/Simple-CAN-Bus-.png

线路总线网络拓扑

第一批采用CAN总线的车辆使用单一网络,采用线-总线拓扑结构。如今,一些低价车的内部通信仍然使用一到两个共享的CAN总线网络。这种拓扑的缺点是其脆弱性和缺乏网络分离。车辆的所有ECU都连接在共享总线上。由于CAN不支持其协议定义中的安全功能,因此此总线上的任何参与者都可以与所有其他参与者直接通信,这使得攻击者能够通过危害单个ECU来影响所有ECU,甚至包括安全关键型ECU。该网络的整体安全级别是从最弱参与者的安全级别给出的。

中央网关

../_images/ZGW-CAN-Bus-.png

具有中央GW ECU的网络拓扑

中央网关(GW)拓扑可以在价格较高的旧车和中价到低价的近期车中找到。集中式GW ECU分隔特定于域的子网。这允许OEM将具有远程攻击面的所有ECU封装在一个子网中。具有安全关键功能的ECU位于单个CAN网络中。除了CAN之外,FlexRay还可以用作单独网络域内的通信协议。在此拓扑中,安全关键网络的安全性主要取决于中央GW ECU的安全性。该体系结构通过域分离提高了车辆的整体安全级别。攻击者通过任意攻击面成功利用ECU后,需要第二个可利用漏洞或逻辑漏洞来危害车辆内的不同域(安全关键型网络)。第二次利用或逻辑错误对于克服中央GW ECU的网络分离是必要的。

中央网关和域控制器

../_images/DC-ZGW-CAN-Bus-.png

采用汽车以太网主干和DC的网络拓扑

具有中央GW和域控制器(DC)的新拓扑可以在最新的更高价位的汽车中找到。具有自动驾驶和驾驶员辅助功能的现代车辆对带宽的需求不断增加,导致了这种拓扑结构。汽车以太网网络用作整个车辆的通信主干。通过DC与中央GW连接的各个域构成了车辆的主干。各个DC可以控制和调节域和车辆主干之间的数据通信。此拓扑通过强大的网络分离实现了非常高的安全级别,各个DC充当车辆主干网络的网关和防火墙。OEM具有动态信息路由的优势,其次是这一安全改进,它是按需功能(FOD)服务的推动者。

汽车通信协议

本节概述汽车网络安全评估的相关通信协议。与“物理协议”一节不同,本节重点介绍数据通信的属性。

CAN

CAN通信技术发明于1983年,是一种基于消息的健壮的车辆总线通信系统。Robert Bosch GmbH在CAN标准中设计了多种通信功能,以实现适用于控制器区域网络的健壮且计算高效的协议。值得注意的是CAN的通信行为是用于传输错误的内部状态机。此状态机实现故障静默行为,以保护安全关键型网络免受白痴节点的喋喋不休。如果发生特定限制的接收错误(REC)或传输错误(TEC),则CAN驱动器将其状态从错误主动更改为错误被动,最后更改为总线关闭。

../_images/can-bus-states.png

传输错误时的CAN总线状态。接收错误计数器(REC)、发送错误计数器(TEC)

近年来,该协议规范被滥用于对车辆CAN网络的拒绝服务(DoS)攻击和信息收集攻击。Cho等人。通过滥用ECU的总线关闭状态演示了针对CAN网络的DoS攻击 [1]. 在特定节点的CAN帧中注入通信错误会导致被攻击节点中的传输错误计数过高,从而迫使被攻击节点进入总线关闭状态。2019年,Kulandaivel等人。将该攻击与统计分析相结合,实现了车载网络中快速、廉价的网络映射 [2]. 他们结合了对节点实施Bus-Off攻击前后CAN网络流量的统计分析。在ECU受到攻击后,网络流量中丢失的所有CAN帧现在都可以映射到受到攻击的ECU,从而帮助研究人员确定CAN帧的原始ECU。Ken Tindell发表了2019年罐头低级别攻击的全面总结 [3].

../_images/CAN-full-frame.jpg

完整的CAN数据帧结构 [9]

上图显示了通过网络传输的CAN帧及其字段。对于信息交换,只有仲裁、控制和数据字段相关。这些是普通应用软件可以访问的唯一字段。所有其他字段都在硬件层上求值,并且在大多数情况下不会转发到应用程序。数据字段的长度可变,最多可容纳8个字节。数据字段的长度由控制字段内的数据长度代码指定。此示例的重要变体是具有扩展仲裁字段的CAN帧和控制器区域网络灵活数据速率(CAN FD)协议。在Linux上,每个收到的CAN帧都会传递给SocketCAN。SocketCAN允许CAN通过操作系统的网络套接字进行处理。SocketCAN是由Oliver Hartkopp创建的,并添加到Linux内核版本2.6.25中 [4]. 图2.7显示了帧结构,如果用户端应用程序从CAN套接字接收数据,如何对帧进行编码。

../_images/can-frame-socket-can.png

由SocketCAN定义的CAN帧

以上图的比较清楚地显示了物理层驱动器在CAN帧处理过程中的信息丢失。无论应用程序代码是在微控制器上运行还是在Linux内核上运行,几乎每个CAN驱动程序的工作方式都是相同的。这也意味着标准应用程序无法访问循环冗余校验(CRC)字段、确认位或帧结束字段。

通过车辆或独立领域中的CAN通信,ECU交换传感器数据和控制输入;这些数据主要是不安全的,可以被攻击者修改。攻击者可以很容易地在CAN总线上伪造传感器值,从而触发其他ECU的恶意反应。米勒和瓦拉塞克在研究汽车网络时描述了这种欺骗攻击 [5]. 为了防止对通过CAN传输的安全关键数据的攻击,汽车开放系统架构(AUTOSAR)发布了安全车载通信规范 [6].

ISO-TP(ISO 15765-2)

CAN协议仅支持8字节数据。诊断操作或ECU编程等用例需要比CAN协议支持的有效负载高得多的负载。出于这些目的,汽车行业标准化了传输层(ISO-TP)(ISO 15765-2)协议 [7]. ISO-TP是CAN之上的传输层协议。可以在分段为CAN帧的ISO-TP端点之间传输高达4095字节的有效载荷。ISO-TP协议处理需要四种特殊的帧类型。

../_images/isotp-flow.png

ISO-TP分段通信

不同类型的ISO-TP帧如下图所示。CAN帧的有效负载被四个ISO-TP帧中的一个替换。各个ISO-TP帧有不同的用途。单个帧可以传输1到7字节的ISO-TP消息数据。单帧或第一帧的LEN字段指示ISO-TP消息长度。有效载荷数据超过7字节的每个消息必须分段成第一帧,随后是多个连续帧。此通信如上图所示。从发送方发送第一帧后,接收方必须通过流控制帧将其接收能力传达给发送方。只有在接收到该流控制帧之后,才允许发送方根据接收方的能力进行连续帧的通信。

../_images/isotp-frames.png

ISO-TP帧类型

ISO-TP充当通过寻址机制支持定向通信的传输协议。在车辆中,ISO-TP主要用作诊断通信的传输协议。在极少数情况下,ISO-TP还用于在车辆的ECU之间交换更大的数据。由于ISO-TP没有保护其传输数据的能力,因此必须对通过ISO-TP传输的应用层协议应用安全措施。

DoIP

IP诊断(DoIP)首先在采用集中式网关拓扑的汽车网络上实施。集中式GW用作将诊断消息路由到所需网络的DoIP端点,允许制造商并行编程或诊断多个ECU。由于维修厂测试仪和GW之间的网际协议(IP)通信比GW ECU和通过CAN连接的目标ECU之间的通信快许多倍,因此IP通信的剩余带宽可用于启动与不同CAN域中的其他ECU的进一步DoIP连接。DoIP作为自动合成孔径雷达的一部分进行了规定,并在ISO 13400-2中进行了规定。与ISO-TP类似,DoIP没有指定特殊的安全措施。关于安全通信的责任被委派给应用层协议。

诊断协议

诊断协议的两个例子是通用汽车局域网和统一诊断服务(国际标准化组织14229-2)。通用汽车公司使用GMLAN。德国OEM主要使用UDS。从规范的角度来看,这两个协议非常相似,并且两个协议都使用ISO-TP或DoIP消息与目标ECU进行定向通信。由于不同的OEM使用UDS,因此每个制造商都会在标准中添加其定制的附件。此外,每个制造商都使用单独的ISO-TP编址与ECU进行定向通信。与UDS相比,GMLAN包括更精确的关于ECU寻址和ECU内部行为的定义。

UDS和GMLAN遵循树状消息结构,其中第一个字节标识服务。每个服务都由响应应答。标准中定义了两种类型的响应。否定响应通过服务0x7F指示。肯定响应由请求服务标识符加0x40标识。

../_images/diag-stack.png

汽车诊断协议栈

传输层和应用层之间的明确分离允许为两个网络堆栈创建应用层工具。上图概述了相关协议和相应的层。UDS定义了应用层和传输层之间的清晰分离。在基于CAN的网络中,ISO-TP用于此目的。可以将CAN协议视为网络接入协议。这允许将ISO-TP和CAN替换为DoIP或HSFZ和以太网。GMLAN协议结合了与ISO-TP和UDS非常相似的传输层和应用层规范。由于这种相似性,可以应用相同的应用层特定扫描技术。为了克服CAN的带宽限制,最新的车辆架构使用基于以太网的诊断协议(DoIP、HSFZ)与中央网关ECU通信。中央网关ECU将应用层分组从基于以太网的网络路由到基于CAN的车辆内部网络。通常,可以通过UDSonCAN或UDSonIP从OBD连接器访问车辆中所有ECU的诊断功能。

一些/知识产权

IP上可扩展的面向服务的中间件(SOME/IP)定义了汽车网络中数据通信的新理念。一些/IP用于在最新的车辆网络中的网络域控制器之间交换数据。一些/IP支持订阅和通知机制,允许域控制器根据车辆的状态动态订阅由另一个域控制器提供的数据。一些/IP在域控制器和网关之间传输车辆在正常运行期间需要的数据。某些/IP的用例类似于CAN通信的用例。其主要目的是实现ECU之间传感器和执行器数据的信息交换。这种用法强调某些/IP通信是网络攻击的有利可图的目标。

CCP/XCP

通用测量和校准协议(XCP)是CAN校准协议(CCP)的后继者,是2003年由ASAM e.V.标准化的汽车系统校准协议。xCP的主要用途是在ECU或车辆开发的测试和校准阶段。CCP是为在CAN上使用而设计的。CCP中没有消息超过CAN的8字节限制。为了克服这一限制,xCP旨在与广泛的传输协议兼容。xCP可在CAN、CAN FD、串行外设接口(SPI)、以太网、通用串行总线(USB)和FlexRay上使用。CCP和XCP的功能非常相似;但是,XCP的功能范围更大,并且针对数据效率进行了优化。

这两种协议都具有基于会话的通信过程,并支持通过主节点和多个从节点之间的种子和密钥机制进行认证。主节点通常是工程个人计算机(PC)。在车辆中,从节点是用于配置的ECU。xCP还支持模拟。车辆工程师可以通过xCP调试MATLAB SIMULINK模型。在这种情况下,模拟模型充当xCP从节点。CCP和XCP可以读取和写入ECU的内存。另一个主要特点是数据采集。这两种协议都支持允许工程师用感兴趣的存储器地址配置所谓的数据采集列表的过程。此类列表中指定的所有存储器将被定期读取,并在所选通信信道上的CCP或XCP数据采集(DAQ)包中广播。下图概述了xCP中支持的所有通信和数据包类型。在命令传输对象(CTO)区域,所有通信都遵循总是由xCP主机发起的请求和响应程序。命令分组(CMD)可以接收命令响应分组(RES)、错误(ERR)分组、事件分组(EV)或服务请求分组(SERVER)作为响应。通过CTO CMD配置从机后,从机可以监听刺激(STIM)数据包,并定期发送配置的DAQ数据包。下图中的资源部分指出了攻击者可能滥用的此协议的可能攻击面(编程(PGM)、校准(CAL)、DAQ、STIM)。对于车辆的安全和安全来说,在车辆发货给客户之前禁用或删除这些仅在车辆校准和开发期间使用的协议是至关重要的。

../_images/XCP_ReferenceBook.png

xCP主站和xCP从站之间的xCP通信模型。该模型显示了CTO/数据传输对象(DTO)包的通信方向 [8].

References

图层

备注

ATTENTION :下面的动画可能已过时。

CAN

如何

通过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接口发送和接收消息::

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

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

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

socket.sr1(packet)

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

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

CANSockets

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

斯皮坎索克

在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(channel="vcan0")

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

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

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

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

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

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

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

socket = CANSocket(channel='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(channel="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(channel='vcan0')
socket1 = CANSocket(channel='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(channel='vcan0')
    bSocket1 = CANSocket(channel='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

CAN校准协议(CCP)

CCP由CAN派生而来。CAN报头是CCP帧的一部分。CCP有两种类型的消息对象。一种称为命令接收对象(CRO),另一种称为数据传输对象(DTO)。通常将CRO发送到ECU,并从ECU接收DTO。如果一个DTO应答CRO,则通过计数器域(CTR)实现该信息。如果两个对象具有相同的计数器值,则可以从关联CRO对象的命令解释DTO对象的有效负载。

创建CRO消息::

load_contrib('automotive.ccp')
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(bustype='socketcan', channel='vcan0')
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(bustype='socketcan', channel='vcan0', 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对象的命令得到解释。

通用校准和测量协议(XCP)

XCP是中国共产党的接班人。它可以与多种协议一起使用。SCapy包括CAN、UDP和TCP。XCP有两种消息类型:命令传输对象(CTO)和数据传输对象(DTO)。发送到ECU的CTO是请求(命令),ECU必须回复肯定响应或错误。此外,ECU可以发送CTO以通知主机关于异步事件(EV)或请求服务执行(SERVER)。ECU发送的DTO称为DAQ(数据采集),包括测量值。ECU接收的DTO用于周期性刺激,称为STIM(刺激)。

创建CTO消息::

CTORequest() / Connect()
CTORequest() / GetDaqResolutionInfo()
CTORequest() / GetSeed(mode=0x01, resource=0x00)

要通过CAN发送消息,必须添加标题::

pkt = XCPOnCAN(identifier=0x700) / CTORequest() / Connect()
sock = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0'))
sock.send(pkt)

如果我们对ECU的响应感兴趣,我们需要将CANSocket的basecls参数设置为XCPonCAN,并且需要使用SR1:发送CTO消息::

sock = CANSocket(bustype='socketcan', channel='vcan0', basecls=XCPonCAN)
dto = sock.sr1(pkt)

因为SR1调用Answers函数,所以我们的xCP-Response对象的有效负载由CTO对象的命令解释。否则它就不能被解释了。第一条消息应该始终是“CONNECT”消息,ECU的响应决定了消息的读取方式。例如:字节顺序。否则,必须在contrib config::中设置每手DTO和CTO的地址粒度和最大大小

conf.contribs['XCP']['Address_Granularity_Byte'] = 1  # Can be 1, 2 or 4
conf.contribs['XCP']['MAX_CTO'] = 8
conf.contribs['XCP']['MAX_DTO'] = 8

如果您不希望在收到消息后设置此项,也可以禁用该功能:

conf.contribs['XCP']['allow_byte_order_change'] = False
conf.contribs['XCP']['allow_ag_change'] = False
conf.contribs['XCP']['allow_cto_and_dto_change'] = False

要通过TCP或UDP发送PKT,必须使用另一个报头。TCP::

prt1, prt2 = 12345, 54321
XCPOnTCP(sport=prt1, dport=prt2) / CTORequest() / Connect()

UDP::

XCPOnUDP(sport=prt1, dport=prt2) / CTORequest() / Connect()

XCPScanner

XCPScanner是一个实用程序,用于查找支持xCP的ECU的CAN标识符。

命令行用法示例:

python -m scapy.tools.automotive.xcpscanner -h
Finds XCP slaves using the "GetSlaveId"-message(Broadcast) or the "Connect"-message.

positional arguments:
  channel               Linux SocketCAN interface name, e.g.: vcan0

optional arguments:
  -h, --help            show this help message and exit
  --start START, -s START
                        Start identifier CAN (in hex).
                        The scan will test ids between --start and --end (inclusive)
                        Default: 0x00
  --end END, -e END     End identifier CAN (in hex).
                        The scan will test ids between --start and --end (inclusive)
                        Default: 0x7ff
  --sniff_time', '-t'   Duration in milliseconds a sniff is waiting for a response.
                        Default: 100
  --broadcast, -b       Use Broadcast-message GetSlaveId instead of default "Connect"
                        (GetSlaveId is an optional Message that is not always implemented)
  --verbose VERBOSE, -v
                        Display information during scan

    Examples:
        python3.6 -m scapy.tools.automotive.xcpscanner can0
        python3.6 -m scapy.tools.automotive.xcpscanner can0 -b 500
        python3.6 -m scapy.tools.automotive.xcpscanner can0 -s 50 -e 100
        python3.6 -m scapy.tools.automotive.xcpscanner can0 -b 500 -v
交互式shell用法示例:
>>> conf.contribs['CANSocket'] = {'use-python-can': False}
>>> load_layer("can")
>>> load_contrib("automotive.xcp.xcp")
>>> sock = CANSocket("vcan0")
>>> sock.basecls = XCPOnCAN
>>> scanner = XCPOnCANScanner(sock)
>>> result = scanner.start_scan()

结果包括Slave_id(接收xCP消息的ECU的标识符)和response_id(ECU将向其发送xCP消息的标识符)。

ISOTP

同位素信息

创建一条等幅线消息:

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

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

ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, data=b"\x3eabc")

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

ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, ext_address=0x41, data=b"\x3eabc")

从ISOTP消息创建CAN帧::

ISOTP(tx_id=0x241, rx_id=0x641, rx_ext_address=0x41, ext_address=0x55, data=b"\x3eabc" * 10).fragment()

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

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

嗅探ISOTP消息:

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

同位素插座

Scapy提供了两种ISOTP-Socket。一种实现是, ISOTPNativeSocket 正在使用Hartkopp的Linux内核模块。另一个实现是, ISOTPSoftSocket 完全是用Python实现的。此实现可以在Linux、Windows和OSX上使用。

An ISOTPSocket will not respect tx_id, rx_id, rx_ext_address, ext_address of an ISOTP message object.

系统兼容性

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

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

关于ISOTPSocket兼容性的另一个备注。始终使用 with 用于创建套接字。这确保了 ISOTPSoftSocket 对象将正确关闭。示例::

with ISOTPSocket("vcan0", rx_id=0x241, tx_id=0x641) as sock:
    sock.send(...)
ISOTPNativeSocket

要求:

  • Python 3

  • Linux系统

  • Hartkopp的Linux内核模块: https://github.com/hartkopp/can-isotp.git (在5.10中并入主线Linux)

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

conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
load_contrib('isotp')
sock = ISOTPSocket("can0", tx_id=0x641, rx_id=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", tx_id=0x641, rx_id=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(bustype='socketcan', channel="can0"), tx_id=0x641, rx_id=0x241) as sock:
    sock.send(...)

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

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

用桥接和嗅探等电位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

导入模块:

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

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

isoTpSocketVCan0 = ISOTPSocket('vcan0', tx_id=0x241, rx_id=0x641)
isoTpSocketVCan1 = ISOTPSocket('vcan1', tx_id=0x641, rx_id=0x241)

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

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

创建转发数据包的函数:

def forwarding(pkt):
    return pkt

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

def bridge():
    bSocket0 = ISOTPSocket('vcan0', tx_id=0x641, rx_id=0x241)
    bSocket1 = ISOTPSocket('vcan1', tx_id=0x241, rx_id=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)

开始线程::

threadBridge.start()
threadSender.start()

在VCAN1上嗅探:

receive = isoTpSocketVCan1.sniff(timeout=1)

关闭套接字:

isoTpSocketVCan0.close()
isoTpSocketVCan1.close()

isotp_scan and ISOTPScanner

isotp_scan is a utility function to find ISOTP-Endpoints on a CAN-Bus. ISOTPScanner is a commandline-utility for the identical function.

../_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 = isotp_scan(CANSocket("vcan0"), range(0x700, 0x800), 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或HSFZ有效载荷,或者UDS数据包可以直接通过ISOTPSocket发送。每个OEM都有自己的UDS定制。这增加了通用应用的难度,渗透测试需要OEM专业知识。RoutineControl作业和ReadDataByIdentifier/WriteDataByIdentifier服务进行了大量自定义。

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

以下是两个用法示例:

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

自定义自定义项

在现实世界的用例中,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非常相似。这是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对象的当前状态和接收到的消息的踪迹。根据协议的不同,某些消息将更改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

浅析厄瓜多尔与“飞翔”的关系

此示例显示了在sniff中使用厄瓜多尔会话。可以使用返回正确协议的整个消息的ISOTPSocket或任何类似套接字的对象。一个 EcuSession 被用作 ISOTPSession 。要获取 Ecu 对象从 EcuSession ,即 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是在ISOTP之上实现的。使用ISOTPSocket与ECU通信。您应该设置参数 basecls=OBDpadding=True 在您的ISOTPSocket初始化调用中。

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

请求支持的服务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

测试设置教程

ISO-TP内核模块安装

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

CAN接口设置

作为准备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协议

    Download the vsomeip library on the Raspberry, apply the git patch so it can work with the newer boost libraries and then install it.

    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

意大利面卷饼框架

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 &