Scapy utilities#

Overview#

  • ISOTP(Packet): Scapy Packet object with fragmentation and defragmentation support

  • ISOTPHeader and ISOTPHeaderEA: Packet classes for parsing CAN-traffic

  • ISOTPMessageBuilder: Helper class for defragmentation of CAN traffic into ISOTP packets

  • ISOTPSession: Scapy Session class to create ISOTP packets on the fly during a sniff()

ISOTP(Packet)#

  • Create an ISOTP packet

from scapy.all import *
from scapy.contrib.isotp import *

p = ISOTP(b"deadbeef", tx_id=0x123, rx_id=0x321)
p.show()
###[ ISOTP ]### 
  data      = 'deadbeef'
  • Fragment ISOTP packet into CAN frames

frames = p.fragment()
frames
[<CAN  identifier=0x321 data='\x10\x08deadbe' |>,
 <CAN  identifier=0x321 data='!ef' |>]
  • Analyze ISOTP frame

ISOTPHeader(bytes(frames[0])).show()
###[ ISOTPHeader ]### 
  flags     = 
  identifier= 0x321
  length    = 8
  reserved  = 0
###[ ISOTPFirstFrame ]### 
     type      = first
     message_size= 8
     data      = 'deadbe'
  • Analyze ISOTP frame

ISOTPHeader(bytes(frames[1])).show()
###[ ISOTPHeader ]### 
  flags     = 
  identifier= 0x321
  length    = 3
  reserved  = 0
###[ ISOTPConsecutiveFrame ]### 
     type      = consecutive
     index     = 1
     data      = 'ef'
  • Defragment CAN frames into ISOTP packet

print(frames)
ISOTP.defragment(frames)
[<CAN  identifier=0x321 data='\x10\x08deadbe' |>, <CAN  identifier=0x321 data='!ef' |>]
<ISOTP  data='deadbeef' |>

ISOTPMessageBuilder#

  • Create ISOTP packet

print(frames)
builder = ISOTPMessageBuilder()
builder.feed(frames)
print(len(builder))
isotp_msg = builder.pop()
print(repr(isotp_msg))
print(len(builder))
[<CAN  identifier=0x321 data='\x10\x08deadbe' |>, <CAN  identifier=0x321 data='!ef' |>]
1
<ISOTP  data='deadbeef' |>
0

ISOTPSession#

  • Create CAN packets

frames = ISOTP(b"This is a long ISOTP message", tx_id=0x123).fragment()
frames += ISOTP(b"short", tx_id=0x123).fragment()
frames += ISOTP(b"And a long message again", tx_id=0x45).fragment()
for f in frames:
    print(repr(f))
<CAN  identifier=None data='\x10\x1cThis i' |>
<CAN  identifier=None data='!s a lon' |>
<CAN  identifier=None data='"g ISOTP' |>
<CAN  identifier=None data='# messag' |>
<CAN  identifier=None data='$e' |>
<CAN  identifier=None data='\x05short' |>
<CAN  identifier=None data='\x10\x18And a ' |>
<CAN  identifier=None data='!long me' |>
<CAN  identifier=None data='"ssage a' |>
<CAN  identifier=None data='#gain' |>
  • Serialize CAN packets to pcap

wrpcap("/tmp/can.pcap", frames)
!ls -lah /tmp/can.pcap
-rw-r--r-- 1 root root 333 Mar 23 06:28 /tmp/can.pcap
  • Use PcapReader as socket and extract ISOTP messages from CAN frames

with PcapReader("/tmp/can.pcap") as sock:
    msgs = sniff(session=ISOTPSession,
                 session_kwargs={"use_ext_addr":False, 
                                 "basecls":ISOTP},
                 count=3, opened_socket=sock)

print(msgs)
print(repr(msgs[0]))
print(repr(msgs[1]))
print(repr(msgs[2]))
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:3>
<ISOTP  data='This is a long ISOTP message' |>
<ISOTP  data='short' |>
<ISOTP  data='And a long message again' |>

ISOTP MITM attack#

Communication overview#

LaTeX terminated with signal -1
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020/Debian) (preloaded format=pdflatex 2022.11.9)  23 MAR 2023 06:28
entering extended mode
 restricted \write18 enabled.
 %&-line parsing enabled.
**tikz.tex
(./tikz.tex
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-01-09> xparse <2020-03-03>

! LaTeX Error: File `standalone.cls' not found.

Type X to quit or <RETURN> to proceed,
or enter new name. (Default extension: cls)

Enter file name: 
! Emergency stop.
<read *> 
         
l.3 \usepackage
               []{tikz}^^M
*** (cannot \read from terminal in nonstop modes)

 
Here is how much of TeX's memory you used:
 21 strings out of 481252
 440 string characters out of 5915888
 266126 words of memory out of 5000000
 17059 multiletter control sequences out of 15000+600000
 403430 words of font info for 27 fonts, out of 8000000 for 9000
 14 hyphenation exceptions out of 8191
 32i,0n,39p,105b,13s stack positions out of 5000i,500n,10000p,200000b,80000s
!  ==> Fatal error occurred, no output PDF file produced!

Scapy example#

In this example, we use vcan0 and vcan1 as interfaces. Source and Destination communicate over the ISOTP addresses 0x241 and 0x641.

  • Setup two CAN interfaces

    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 necessary modules in Scapy

    import threading
    load_contrib('cansocket')
    conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
    load_contrib('isotp')
    
  • Create a forward function. Attack code need to be placed here.

    def forwarding(pkt):
        pkt.data = b"ATTACKED"
        return pkt
    
  • Create attacker sockets and run MITM attack

    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)