A Packet Viewer and Manipulator for Scapy

Proudly introducing the Packet Viewer (and Manipulator), a Wireshark-like protocol sniffer and decoder with live packet manipulation, sending and replaying capabilities.

Before diving in, let’s recap some basics first. If you’re familiar with Scapy, feel free to skip to “A TUI for Scapy” right away.

Short introduction to Scapy

Scapy is a framework for network packet manipulation. It is written in Python and supports Python 2 (still) and 3. It is very flexible and can not only be used to receive packets but also to send packets. Simple packet definitions take only a few lines of code which are easily readable.

For example, let’s define a packet which contains only two bytes:

1
2
3
4
5
class Demo(Packet):
    fields_desc=[
        ByteField("field1", 0),
        ByteField("field2", 0),
    ]

We define a byte field using ByteField (wow!) and assign a name (fieldX) to them. The second parameter (0) the default value.

Now we can create a packet with the following line:

1
packet = Demo(field1=0x42)

Finally, we can check which byte string is generated by this packet.

1
2
3
>>> b = bytes(packet)
>>> print(b)
b'B\x00'
Value Interpretation
B This is the ASCII representation of the hex value 0x42.
\x00 This is the default value of “field2”. The \x__ notation is defined by Python, not by Scapy.

This is a very simple example.

Let’s look at a more complex, but known example: UDP.

1
2
3
4
5
class UDP(Packet):
    fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES),
                   ShortEnumField("dport", 53, UDP_SERVICES),
                   ShortField("len", None),
                   XShortField("chksum", None), ]

Instead of ByteFields, it contains ShortFields (2 bytes). Additionally, it includes two special kinds of ShortField: Two ShortEnumFields and one XShortField. UDP_SERVICES is a dictionary with maps ports to their human-readable names. Scapy sets the default port to 53 which is the standard port used for DNS. The value of an XShortField is represented as hex. So these are just eye candy.

Now some code to see it in action:

1
2
3
4
5
6
7
>>> packet = UDP(sport=65000, dport=80)
>>> packet.show()
###[ UDP ]### 
  sport     = 65000
  dport     = www_http
  len       = None
  chksum    = None

Here, the mapping is visible. The port 80 has been translated to “www_http”.

Unfortunately, len and chksum are still None. For sure, they need to be set for a proper UDS packet before sending it. But let’s first check the bytes Scapy generates for this packet.

1
2
3
>>> b = bytes(packet)
>>> print(b)
b'\xfd\xe8\x00P\x00\x08\x00\x00'
Value Interpretation
\xfd\xe8 This is the sport or the source port[, if you’re not a fan]
\x00P The dport or destination port (P = 0x80)
\x00\x08 len
\x00\x00 chksum

Scapy has taken some work off our hands. The len is already set properly. The chksum is 0, because the underlayer to UDP is missing (IP) and thus Scapy doesn’t know how to calculate the checksum.

This can be resolved by stacking an IP and a UDP packet. For this, Scapy uses the division operator.

1
packet = IP() / UDP(sport=65000, dport=80)

After building it, this packet shows the following output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
>>> packet.show()
###[ IP ]### 
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 28
  id        = 1
  flags     = 
  frag      = 0
  ttl       = 64
  proto     = udp
  chksum    = 0x7cce
  src       = 127.0.0.1
  dst       = 127.0.0.1
  \options   \
###[ UDP ]### 
     sport     = 65000
     dport     = www_http
     len       = 8
     chksum    = 0x3a3

There are many default values, e.g. the localhost addresses. Furthermore, chksum has been automatically calculated for us, so no reason to dig into the UDP specification.

We have packets now but we didn’t send them yet. This is a very simple one-liner.

1
send(packet)

Note: This requires root privileges on Linux because it uses a RAW socket, a feature which is only accessible for super users.

To receive packets, we can use the sniff function.

1
sniff(count=1)

The count parameter defines how many packets should be received until this call returns.

A TUI for Scapy

In summary, Scapy offers easy pythonic access to protocols, network layers, sockets, and everything else required for networking tasks. What it lacked - until now - was some sort of Wireshark-like UI, embracing Scapy’s power and flexibility, while offering developers, pentesters and other Scapy users a tool to conveniently view, manipulate and generate traffic.

And that’s exactly what our packet viewer offers. Let’s have a first look at it:

An animation giving an overview of the packet viewer’s TUI.

The packet viewer is a terminal-based UI (TUI), inspired in look-and-feel by our old friend htop. The packet viewer is easily launched right from Scapy’s interactive prompt by a call to the viewer function. All you need to get started is a socket! By passing the basecls of the packets you expect to receive and send via the socket, the viewer can derive useful columns to show for the packets that come in.

The viewer, which is based on urwid, can be navigated by keyboard and mouse, both of which work even via SSH! It comes with a few useful features that are always available regardless of the protocol that is being sent/received:

Button Description
F2 Replay the packet that is currently selected.
F3 Pause and resume the sniffing.
F4 Quit the viewer.
F5 Filter the received packets by some condition. The condition is given as a Python expression, which is called once for each packet and is expected to return either True or False, which results in the packet being checked or not.
F6 Display the currently selected packet as a Python expression, allow to modify that expression, and send the resulting new packet.
F7 Edit the currently selected packet in-place.

The features to provide can be modified, e.g. by passing the views parameter to the call to viewer, but it doesn’t stop here: new features can easily be added using our simple plugin API! We will introduce one such plugin in the next section.

When quitting the viewer, the call to viewer returns both a list of all packets that were received, and the packets that were selected before quitting. That way, the viewer can even be integrated into bigger scripts outside of Scapy’s interactive prompt!

Plugin: CAN Packet Structure Analysis

The first plugin available for the packet viewer is a frontend to our CAN packet structure analysis tool revdbc.

Short Introduction: CAN Packet Structure Analysis

Controller Area Network (CAN) is a message-based protocol used in automotives to connect (some of) the numerous electronic control units (ECUs). While sometimes used directly for communication between ECUs, it’s also the basis for various higher-level protocols built on top of it.

DBC (CAN Database) is a proprietary file format used to describe the structure of CAN packets. The up to eight bytes of data are split into so-called “signals”, which can be simple value signals, enumerations or multiplexers. The specifics are not relevant here, but have a look at that beautiful, amazingly well-readable format!!

1
2
BO_ 500 IO_DEBUG: 4 IO
 SG_ IO_DEBUG_test_unsigned : 0|8@1+ (1,0) [0|0] "" DBG

The meaning of these lines is obvious right away, amirite? (Example taken from here)

Automated DBC Reverse-Engineering

Now, as you might have guessed, automotive producers keep the structure of their CAN packets secret. For security analysts, pentesters or evil hackers, however, the structures are important information and need to be reverse-engineered. There are various approaches to do so, from fully manual observation of traffic to active systems that hook into a car and attempt to trigger packets for more targeted information gathering.

This plugin gives a frontend to revdbc, which tries to automatically reverse-engineer CAN packet structures through statistical analysis. That is, the tool takes a (preferably large) log of CAN packets as input, and tries to deduce packet structure information by observing changes in the bits and bytes over time. For more information, refer to revdbc’s documentation.

And this is what it looks like!

An overview of the structure of the CAN analysis view

As mentioned previously, features can be accessed using keybindings. The default features are bound to the F2 - F7 keys of your keyboard. Features added via the plugin API are automatically bound to the next free F key (e.g. F8). The red box contains the fully expanded view of the plugin. Description of the green boxes:

Number Description
2 Status of the analysis. E.g. Analysis Runing/Done/Outdated
3 The reverse-engineered packet structure as ASCII-art
4 Various actions, e.g. the possibility to save the reverse-engineered structure to a DBC file.
5 A table of the reverse-engineered signals. Fully editable!
6 Multiple tabs to select which data to show in (7)
7 A graph, showing either the interpreted value of the currently selected signal for each packet, the number of bitflips for each bit of the signal, or the correlation between these bitflips. The last two of these are metrics used during analysis to deduce the structure itself.

All of this was built with urwid and on top of the packet viewer’s plugin API. It runs the analysis in a different process and updates the UI asynchronously, to not block user input.