Scapy Readthedocs Io en Latest
Scapy Readthedocs Io en Latest
Release 2.6.1
1 Introduction 3
1.1 About Scapy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 What makes Scapy so special . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Quick demo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Learning Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Usage 17
3.1 Starting Scapy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2 Interactive tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3 Simple one-liners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.4 Recipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4 Advanced usage 61
4.1 ASN.1 and SNMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.2 Automata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.3 PipeTools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
i
7.2 Layers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
7.3 Dissecting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
7.4 Building . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
7.5 Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
7.6 Design patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
9 Layers 121
9.1 Automotive-specific Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
9.2 Bluetooth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
9.3 DCE/RPC & [MS-RPCE] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9.4 .NET Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
9.5 GSSAPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
9.6 HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.7 Kerberos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
9.8 LDAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
9.9 Netflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
9.10 PROFINET IO RTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
9.11 SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
9.12 SMB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
9.13 TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
9.14 TUN / TAP Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
10 Troubleshooting 233
10.1 FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
10.2 Getting help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
12 Credits 247
Index 253
ii
Scapy Documentation, Release 2.6.1
Version
2.6.1.dev54
Release
2.6.1
Date
Feb 27, 2025
Scapy’s documentation is under a Creative Commons Attribution - Non-Commercial - Share Alike 2.5
license.
GENERAL DOCUMENTATION 1
Scapy Documentation, Release 2.6.1
2 GENERAL DOCUMENTATION
CHAPTER
ONE
INTRODUCTION
Scapy also performs very well on a lot of other specific tasks that most other tools can’t handle, like
sending invalid frames, injecting your own 802.11 frames, combining techniques (VLAN hopping+ARP
cache poisoning, VOIP decoding on WEP encrypted channel, . . . ), etc.
The idea is simple. Scapy mainly does two things: sending packets and receiving answers. You define a
set of packets, it sends them, receives answers, matches requests with answers and returns a list of packet
couples (request, answer) and a list of unmatched packets. This has the big advantage over tools like
Nmap or hping that an answer is not reduced to open, closed, or filtered, but is the whole packet.
On top of this can be built more high level functions. For example, one that does traceroutes and give as
a result only the start TTL of the request and the source IP of the answer. One that pings a whole network
and gives the list of machines answering. One that does a portscan and returns a LaTeX report.
3
Scapy Documentation, Release 2.6.1
Second, they usually confuse decoding and interpreting. Machines are good at decoding and can help
human beings with that. Interpretation is reserved for human beings. Some programs try to mimic this
behavior. For instance they say “this port is open” instead of “I received a SYN-ACK”. Sometimes they
are right. Sometimes not. It’s easier for beginners, but when you know what you’re doing, you keep on
trying to deduce what really happened from the program’s interpretation to make your own, which is
hard because you lost a big amount of information. And you often end up using tcpdump -xX to decode
and interpret what the tool missed.
Third, even programs which only decode do not give you all the information they received. The vision
of the network they give you is the one their author thought was sufficient. But it is not complete, and
you have a bias. For instance, do you know a tool that reports the Ethernet padding?
Scapy tries to overcome those problems. It enables you to build exactly the packets you want. Even if I
think stacking an 802.1q layer on top of TCP has no sense, it may have some for somebody else working
on some product I don’t know. Scapy has a flexible model that tries to avoid such arbitrary limits. You’re
free to put any value you want in any field you want and stack them like you want. You’re an adult after
all.
In fact, it’s like building a new tool each time, but instead of dealing with a hundred line C program, you
only write 2 lines of Scapy.
After a probe (scan, traceroute, etc.) Scapy always gives you the full decoded packets from the probe,
before any interpretation. That means that you can probe once and interpret many times. Ask for a
traceroute and look at the padding, for instance.
4 Chapter 1. Introduction
Scapy Documentation, Release 2.6.1
data visualized as the result of the port scan. The data could then also be visualized with respect to the
TTL of the response packet. A new probe need not be initiated to adjust the viewpoint of the data.
Implicit packet set
stimulus
network
Result
match
response
sr()
Unanswered packets
# ./run_scapy -s mysession
New session [mysession]
Welcome to Scapy (2.4.0)
>>> IP()
<IP |>
>>> target="www.target.com/30"
>>> ip=IP(dst=target)
>>> ip
<IP dst=<Net www.target.com/30> |>
>>> [p for p in ip]
[<IP dst=207.171.175.28 |>, <IP dst=207.171.175.29 |>,
<IP dst=207.171.175.30 |>, <IP dst=207.171.175.31 |>]
>>> ^D
# ./run_scapy -s mysession
Using session [mysession]
Welcome to Scapy (2.4.0)
>>> ip
<IP dst=<Net www.target.com/30> |>
>>> IP()
<IP |>
>>> a=IP(dst="172.16.1.40")
>>> a
<IP dst=172.16.1.40 |>
>>> a.dst
'172.16.1.40'
>>> a.ttl
64
Let’s say I want a broadcast MAC address, and IP payload to ketchup.com and to mayo.com, TTL value
from 1 to 9, and an UDP payload:
>>> Ether(dst="ff:ff:ff:ff:ff:ff")
/IP(dst=["ketchup.com","mayo.com"],ttl=(1,9))
/UDP()
6 Chapter 1. Introduction
Scapy Documentation, Release 2.6.1
Other fields’ default values are chosen to be the most useful ones:
• TCP source port is 20, destination port is 80.
• UDP source and destination ports are 53.
• ICMP type is echo request.
8 Chapter 1. Introduction
CHAPTER
TWO
2.1 Overview
0. Install Python 3.7+.
1. Download and install Scapy.
2. Follow the platform-specific instructions (dependencies).
3. (Optional): Install additional software for special features.
4. Run Scapy with root privileges.
Each of these steps can be done in a different way depending on your platform and on the version of
Scapy you want to use. Follow the platform-specific instructions for more detail.
ò Note
Scapy 2.5.0 was the last version to support Python 2.7 !
ò Note
The following steps apply to Unix-like operating systems (Linux, BSD, Mac OS X). For Windows,
see the special chapter below.
9
Scapy Documentation, Release 2.6.1
ò Note
To get the latest versions, with bugfixes and new features, but maybe not as stable, see the development
version.
Use pip:
ò Note
If you don’t want to clone Scapy, you can install the development version in one line using:
$ pip install https://github.com/secdev/scapy/archive/refs/heads/master.zip
$ pip install .
3. If you used Git, you can always update to the latest version afterwards:
$ git pull
$ pip install .
ò Note
You can run scapy without installing it using the run_scapy (unix) or run_scapy.bat (Windows)
script.
>>> p=sniff(count=50)
>>> p.plot(lambda x:len(x))
• 2D graphics. psdump() and pdfdump() need PyX which in turn needs a LaTeX distribution:
texlive (Unix) or MikTex (Windows).
You can install pyx using pip install pyx
>>> p=IP()/ICMP()
>>> p.pdfdump("test.pdf")
>>> p=rdpcap("myfile.pcap")
>>> p.conversations(type="jpg", target="> test.jpg")
ò Note
Graphviz and ImageMagick need to be installed separately, using your platform-specific pack-
age manager.
• WEP decryption. unwep() needs cryptography. Example using a Weplap test file:
Cryptography is installable via pip install cryptography
>>> enc=rdpcap("weplab-64bit-AA-managed.pcap")
>>> enc.show()
>>> enc[0]
>>> conf.wepkey="AA\x00\x00\x00"
>>> dec=Dot11PacketList(enc).toEthernet()
>>> dec.show()
>>> dec[0]
>>> load_module("nmap")
>>> nmap_fp("192.168.0.1")
Begin emission:
Finished to send 8 packets.
(continues on next page)
2.5.2 Debian/Ubuntu/Fedora
Make sure libpcap is installed:
• Debian/Ubuntu:
• Fedora:
Then install Scapy via pip or apt (bundled under python3-scapy) All dependencies may be installed
either via the platform-specific installer, or via PyPI. See Optional Dependencies for more information.
2.5.3 Mac OS X
On Mac OS X, Scapy DOES work natively since the recent versions. However, you may want to make
Scapy use libpcap. You can choose to install it using either Homebrew or MacPorts. They both work
fine, yet Homebrew is used to run unit tests with Travis CI.
ò Note
Libpcap might already be installed on your platform (for instance, if you have tcpdump). This is the
case of OSX
$ brew update
2. Install libpcap:
Enable it In Scapy:
conf.use_pcap = True
2. Install libpcap:
Enable it In Scapy:
conf.use_pcap = True
2.5.4 OpenBSD
In a similar manner, to install Scapy on OpenBSD 5.9+, you may want to install libpcap, if you do not
want to use the native extension:
Then install Scapy via pip or pkg_add (bundled under python-scapy) All dependencies may be in-
stalled either via the platform-specific installer, or via PyPI. See Optional Dependencies for more infor-
mation.
ò Note
In fact, Solaris doesn’t support AF_PACKET, which Scapy uses on Linux, but rather uses its own sys-
tem DLPI. See this page. We prefer using the very universal libpcap that spending time implementing
support for DLPI.
2.5.6 Windows
You need to install Npcap in order to install Scapy on Windows (should also work with Winpcap, but
unsupported nowadays):
• Download link: Npcap: the latest version
• During installation:
– we advise to turn off the Winpcap compatibility mode
– if you want to use your wifi card in monitor mode (if supported), make sure you enable
the 802.11 option
Once that is done, you can continue with Scapy’s installation.
You should then be able to open a cmd.exe and just call scapy. If not, you probably haven’t enabled
the “Add Python to PATH” option when installing Python. You can follow the instructions over here to
change that (or add it manually).
Screenshots
(activate a virtualenv)
pip install sphinx
cd doc/scapy
make html
You can now open the resulting HTML file _build/html/index.html in your favorite web browser.
To use the ReadTheDocs’ template, you will have to install the corresponding theme with:
(activate a virtualenv)
pip install pylint
cd scapy/
pyreverse -o png -p fields scapy/fields.py
This will generate a classes_fields.png picture containing the inheritance hierarchy. Note that you
can provide as many modules or packages as you want, but the result will quickly get unreadable.
To see the dependencies between the DHCP layer and the ansmachine module, you can run:
In this case, Pyreverse will also generate a packages_dhcp_ans.png showing the link between the
different python modules provided.
THREE
USAGE
$ sudo scapy -H
Welcome to Scapy (2.4.0)
>>>
On Windows, please open a command prompt (cmd.exe) and make sure that you have administrator
privileges:
C:\>scapy
Welcome to Scapy (2.4.0)
>>>
If you do not have all optional packages installed, Scapy will inform you that some features will not be
available:
The basic features of sending and receiving packets should still work, though.
ò Note
You can configure the Scapy terminal by modifying the ~/.config/scapy/prestart.py file.
17
Scapy Documentation, Release 2.6.1
>>> a=IP(ttl=10)
>>> a
< IP ttl=10 |>
>>> a.src
’127.0.0.1’
>>> a.dst="192.168.1.1"
>>> a
< IP ttl=10 dst=192.168.1.1 |>
>>> a.src
’192.168.8.14’
>>> del(a.ttl)
>>> a
< IP dst=192.168.1.1 |>
>>> a.ttl
64
>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>
Each packet can be built or dissected (note: in Python _ (underscore) is the latest result):
>>> raw(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'
>>> IP(_)
<IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=IP
chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>
(continues on next page)
18 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
>>> hexdump(a)
00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E.
00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 [email protected]<....B#
FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P.
20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index
2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 .
0A .
>>> b=raw(a)
>>> b
'\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\
˓→x06x<\xc0
\xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00
\xbb9\x00\x00GET /index.html HTTP/1.0 \n\n'
>>> c=Ether(b)
>>> c
<Ether dst=00:02:15:37:a2:44 src=00:ae:f3:52:aa:d1 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=67 id=1 flags= frag=0L ttl=64 proto=TCP chksum=0x783c
src=192.168.5.21 dst=66.35.250.151 options='' |<TCP sport=20 dport=80 seq=0L
ack=0L dataofs=5L reserved=0L flags=S window=8192 chksum=0xbb39 urgptr=0
options=[] |<Raw load='GET /index.html HTTP/1.0 \n\n' |>>>>
We see that a dissected packet has all its fields filled. That’s because I consider that each field has its
value imposed by the original string. If this is too verbose, the method hide_defaults() will delete every
field that has the same value as the default:
>>> c.hide_defaults()
>>> c
<Ether dst=00:0f:66:56:fa:d2 src=00:ae:f3:52:aa:d1 type=0x800 |<IP ihl=5L␣
˓→len=67
>>> a=rdpcap("/spare/captures/isakmp.cap")
>>> a
<isakmp.cap: UDP:721 TCP:0 ICMP:0 Other:0>
>>> a[423].pdfdump(layer_shift=1)
>>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1)
Command Effect
raw(pkt) assemble the packet
hexdump(pkt) have a hexadecimal dump
ls(pkt) have the list of fields values
pkt.summary() for a one-line summary
pkt.show() for a developed view of the packet
pkt.show2() same as show but on the assembled packet (checksum is calculated, for in-
stance)
pkt.sprintf() fills a format string with fields values of the packet
pkt.decode_payload_as() changes the way the payload is decoded
pkt.psdump() draws a PostScript diagram with explained dissection
pkt.pdfdump() draws a PDF with explained dissection
pkt.command() return a Scapy command that can generate the packet
pkt.json() return a JSON string representing the packet
20 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
>>> a=IP(dst="www.slashdot.org/30")
>>> a
<IP dst=Net('www.slashdot.org/30') |>
>>> [p for p in a]
[<IP dst=66.35.250.148 |>, <IP dst=66.35.250.149 |>,
<IP dst=66.35.250.150 |>, <IP dst=66.35.250.151 |>]
>>> b=IP(ttl=[1,2,(5,9)])
>>> b
<IP ttl=[1, 2, (5, 9)] |>
>>> [p for p in b]
[<IP ttl=1 |>, <IP ttl=2 |>, <IP ttl=5 |>, <IP ttl=6 |>,
<IP ttl=7 |>, <IP ttl=8 |>, <IP ttl=9 |>]
>>> c=TCP(dport=[80,443])
>>> [p for p in a/c]
[<IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.148 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.149 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.150 |<TCP dport=443 |>>,
<IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=80 |>>,
<IP frag=0 proto=TCP dst=66.35.250.151 |<TCP dport=443 |>>]
Some operations (like building the string from a packet) can’t work on a set of packets. In these cases,
if you forgot to unroll your set of packets, only the first element of the list you forgot to generate will be
used to assemble the packet.
On the other hand, it is possible to move sets of packets into a PacketList object, which provides some
operations on lists of packets.
>>> p = PacketList(a)
>>> p
<PacketList: TCP:0 UDP:0 ICMP:0 Other:4>
>>> p = PacketList([p for p in a/c])
>>> p
<PacketList: TCP:8 UDP:0 ICMP:0 Other:0>
Command Effect
summary() displays a list of summaries of each packet
nsummary() same as previous, with the packet number
conversations() displays a graph of conversations
show() displays the preferred representation (usually nsummary())
filter() returns a packet list filtered with a lambda function
hexdump() returns a hexdump of all packets
hexraw() returns a hexdump of the Raw layer of all packets
padding() returns a hexdump of packets with padding
nzpadding() returns a hexdump of packets with non-zero padding
plot() plots a lambda function applied to the packet list
make_table() displays a table according to a lambda function
>>> send(IP(dst="1.2.3.4")/ICMP())
.
Sent 1 packets.
>>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1")
....
Sent 4 packets.
>>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2)
................^C
Sent 16 packets.
>>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay
...........
Sent 11 packets.
ò Note
This feature is only available since Scapy 2.6.0.
If you try to use multicast addresses (IPv4) or link-local addresses (IPv6), you’ll notice that Scapy follows
the routing table and takes the first entry. In order to specify which interface to use when looking through
the routing table, Scapy supports scope identifiers (similar to RFC6874 but for both IPv6 and IPv4).
22 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
You can use both %eth0 format or %15 (the interface id) format. You can query those using conf.
ifaces.
ò Note
3.2.8 Fuzzing
The function fuzz() is able to change any default value that is not to be calculated (like checksums) by
an object whose value is random and whose type is adapted to the field. This enables quickly building
fuzzing templates and sending them in a loop. In the following example, the IP layer is normal, and the
UDP and NTP layers are fuzzed. The UDP checksum will be correct, the UDP destination port will be
overloaded by NTP to be 123 and the NTP version will be forced to be 4. All the other ports will be
randomized. Note: If you use fuzz() in IP layer, src and dst parameter won’t be random so in order to do
that use RandIP().:
>>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1)
................^C
Sent 16 packets.
>>> p = sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX")
Begin emission:
...Finished to send 1 packets.
.*
Received 5 packets, got 1 answers, remaining 0 packets
>>> p
<IP version=4L ihl=5L tos=0x0 len=39 id=15489 flags= frag=0L ttl=42 proto=ICMP
chksum=0x51dd src=66.35.250.151 dst=192.168.5.21 options='' |<ICMP type=echo-
˓→reply
A DNS query (rd = recursion desired). The host 192.168.5.1 is my DNS server. Note the non-null
padding coming from my Linksys having the Etherleak flaw:
>>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org
˓→")))
Begin emission:
Finished to send 1 packets.
..*
Received 3 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x0 len=78 id=0 flags=DF frag=0L ttl=64 proto=UDP␣
˓→chksum=0xaf38
24 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
The “send’n’receive” functions family is the heart of Scapy. They return a couple of two lists. The first
element is a list of couples (packet sent, answer), and the second element is the list of unanswered packets.
These two elements are lists, but they are wrapped by an object to present them better, and to provide
them with some methods that do most frequently needed actions:
>>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23]))
Received 6 packets, got 3 answers, remaining 0 packets
(<Results: UDP:0 TCP:3 ICMP:0 Other:0>, <Unanswered: UDP:0 TCP:0 ICMP:0␣
˓→Other:0>)
If there is a limited rate of answers, you can specify a time interval (in seconds) to wait between two
packets with the inter parameter. If some packets are lost or if specifying an interval is not enough, you
can resend all the unanswered packets, either by calling the function again, directly with the unanswered
list, or by specifying a retry parameter. If retry is 3, Scapy will try to resend unanswered packets 3 times.
If retry is -3, Scapy will resend unanswered packets until no more answer is given for the same set of
unanswered packets 3 times in a row. The timeout parameter specify the time to wait after the last packet
has been sent:
>>> sr(IP(dst="172.20.29.5/30")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,
˓→timeout=1)
Begin emission:
Finished to send 12 packets.
Begin emission:
Finished to send 9 packets.
Begin emission:
Finished to send 9 packets.
>>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S"))
The above will send a single SYN packet to Google’s port 80 and will quit after receiving a single re-
sponse:
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4L ihl=5L tos=0x20 len=44 id=33529 flags= frag=0L ttl=244
proto=TCP chksum=0x6a34 src=72.14.207.99 dst=192.168.1.100 options=// |
<TCP sport=www dport=ftp-data seq=2487238601L ack=1 dataofs=6L reserved=0L
flags=SA window=8190 chksum=0xcdc7 urgptr=0 options=[('MSS', 536)] |
<Padding load='V\xf7' |>>>
From the above output, we can see Google returned “SA” or SYN-ACK flags indicating an open port.
Use either notations to scan ports 440 through 443 on the system:
>>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S"))
or
>>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],
˓→flags="S"))
The above will display stimulus/response pairs for answered probes. We can display only the information
we are interested in by using a simple loop:
Even better, a table can be built using the make_table() function to display information about multiple
targets:
26 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
Begin emission:
.......*.**.......Finished to send 9 packets.
**.*.*..*..................
Received 362 packets, got 8 answers, remaining 1 packets
>>> ans.make_table(
... lambda s,r: (s.dst, s.dport,
... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - % ICMP.type%}")))
66.35.250.150 192.168.1.1 216.109.112.135
22 66.35.250.150 - dest-unreach RA -
80 SA RA SA
443 SA SA SA
The above example will even print the ICMP error type if the ICMP packet was received as a response
instead of expected TCP.
For larger scans, we could be interested in displaying only certain responses. The example below will
only display packets with the “SA” flag set:
In case we want to do some expert analysis of responses, we can use the following command to indicate
which ports are open:
https is open
If all of the above methods were not enough, Scapy includes a report_ports() function which not only
automates the SYN scan, but also produces a LaTeX output with collected results:
>>> report_ports("192.168.1.1",(440,443))
Begin emission:
...*.**Finished to send 4 packets.
*
Received 8 packets, got 4 answers, remaining 0 packets
'\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440
& closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed &
TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n'
Note that the TCP traceroute and some other high-level functions are already coded:
>>> lsc()
sr : Send and receive packets at layer 3
sr1 : Send packets at layer 3 and return only the first answer
srp : Send and receive packets at layer 2
srp1 : Send and receive packets at layer 2 and return only the␣
˓→first answer
srloop : Send a packet at layer 3 in loop and print the answer each␣
˓→time
srploop : Send a packet at layer 2 in loop and print the answer each␣
˓→time
28 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
Scapy may also use the GeoIP2 module, in combination with matplotlib and cartopy to generate fancy
graphics such as below:
In this example, we used the traceroute_map() function to print the graphic. This method is a shortcut
which uses the world_trace of the TracerouteResult objects. It could have been done differently:
or such as above:
To use those functions, it is required to have installed the geoip2 module, its database (direct download)
but also the cartopy module.
This will automatically update the sockets pointing to conf.L2socket and conf.L3socket.
If you want to manually set them, you have a bunch of sockets available, depending on your platform.
For instance, you might want to use:
3.2.14 Sniffing
We can easily capture some packets or even clone tcpdump or tshark. Either one interface or a list of
interfaces to sniff on can be provided. If no interface is given, sniffing will happen on conf.iface:
30 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding
802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding
802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw
802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw
>>> sniff(iface="eth1", prn=lambda x: x.show())
---[ Ethernet ]---
dst = 00:ae:f3:52:aa:d1
src = 00:02:15:37:a2:44
type = 0x800
---[ IP ]---
version = 4L
ihl = 5L
tos = 0x0
len = 84
id = 0
flags = DF
frag = 0L
ttl = 64
proto = ICMP
chksum = 0x3831
src = 192.168.5.21
dst = 66.35.250.151
options = ''
---[ ICMP ]---
type = echo-request
code = 0
chksum = 0x89d9
id = 0xc245
seq = 0x0
---[ Raw ]---
load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\
˓→x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,
˓→-./01234567'
˓→-./01234567'
For even more control over displayed information we can use the sprintf() function:
>>> p
<Ether dst=00:10:4b:b3:7d:4e src=00:40:33:96:7b:60 type=0x800 |<IP version=4L
ihl=5L tos=0x0 len=60 id=61681 flags=DF frag=0L ttl=64 proto=TCP␣
˓→chksum=0xb85e
32 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
ò Note
When sniffing on several interfaces (e.g. iface=["eth0", ...]), you can check what interface a
packet was sniffed on by using the sniffed_on attribute, as shown in one of the examples above.
ò Note
Asynchronous sniffing is only available since Scapy 2.4.3
. Warning
Asynchronous sniffing does not necessarily improves performance (it’s rather the opposite). If you
want to sniff on multiple interfaces / socket, remember you can pass them all to a single sniff() call
It is possible to sniff asynchronously. This allows to stop the sniffer programmatically, rather than with
ctrl^C. It provides start(), stop() and join() utils.
The basic usage would be:
>>> t = AsyncSniffer()
>>> t.start()
>>> print("hey")
hey
[...]
>>> results = t.stop()
The AsyncSniffer class has a few useful keys, such as results (the packets collected) or running,
that can be used. It accepts the same arguments than sniff() (in fact, their implementations are merged).
For instance:
ò Note
Sessions are only available since Scapy 2.4.3
sniff() also provides Sessions, that allows to dissect a flow of packets seamlessly. For instance, you
may want your sniff(prn=...) function to automatically defragment IP packets, before executing the
prn.
Scapy includes some basic Sessions, but it is possible to implement your own. Available by default:
• IPSession -> defragment IP packets on-the-fly, to make a stream usable by prn.
• TCPSession -> defragment certain TCP protocols. Currently supports:
– HTTP 1.0
– TLS
– Kerberos
– LDAP
– SMB
– DCE/RPC
– Postgres
– DOIP
– and maybe other protocols if this page isn’t up to date.
• TLSSession -> matches TLS sessions on the flow.
• NetflowSession -> resolve Netflow V9 packets from their NetflowFlowset information objects
Those sessions can be used using the session= parameter of sniff(). Examples:
34 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
ò Note
To implement your own Session class, in order to support another flow-based protocol, start by
copying a sample from scapy/sessions.py Your custom Session class only needs to extend the
DefaultSession class, and implement a process or a recv function, such as in the examples.
. Warning
The inner workings of Session is currently UNSTABLE: custom Sessions may break in the future.
class TLS(Packet):
[...]
@classmethod
def tcp_reassemble(cls, data, metadata, session):
length = struct.unpack("!H", data[3:5])[0] + 5
if len(data) == length:
return TLS(data)
In this example, we first get the total length of the TLS payload announced by the TLS header, and we
compare it to the length of the data. When the data reaches this length, the packet is complete and can be
returned. When implementing tcp_reassemble, it’s usually a matter of detecting when a packet isn’t
missing anything else.
The data argument is bytes and the metadata argument is a dictionary which keys are as follow:
• metadata["pay_class"]: the TCP payload class (here TLS)
• metadata.get("tcp_psh", False): will be present if the PUSH flag is set
• metadata.get("tcp_end", False): will be present if the END or RESET flag is set
3.2.17 Filters
Demo of both bpf filter and sprintf() method:
>>> srloop(IP(dst="www.target.com/30")/TCP())
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding
fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S
IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S
>>> wrpcap("temp.cap",pkts)
36 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
or
Hexdump
Scapy allows you to export recorded packets in various hex formats.
Use hexdump() to display one or more packets using classic hexdump format:
>>> hexdump(pkt)
0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E.
0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@[email protected]|......
0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI..
0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................
0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$%
0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345
0060 36 37 67
\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e
\x1f !"#$%&\'()*+,-./01234567' |>>>>
Binary string
You can also convert entire packet into a binary string using the raw() function:
We can reimport the produced binary string by selecting the appropriate first layer (e.g. Ether()).
\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e
\x1f !"#$%&\'()*+,-./01234567' |>>>>
Base64
Using the export_object() function, Scapy can export a base64 encoded Python data structure repre-
senting a packet:
>>> pkt
<Ether dst=00:50:56:fc:ce:50 src=00:0c:29:2b:53:19 type=0x800 |<IP ␣
˓→version=4L
\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\
˓→x1e\x1f
!"#$%&\'()*+,-./01234567' |>>>>
>>> export_object(pkt)
eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST
OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao
bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT
WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6
...
The output above can be reimported back into Scapy using import_object():
38 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\
˓→x1e\x1f
!"#$%&\'()*+,-./01234567' |>>>>
Sessions
At last Scapy is capable of saving all session variables using the save_session() function:
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_raw',
˓→ 'pkts']
>>> save_session("session.scapy")
Next time you start Scapy you can load the previous saved session using the load_session() command:
>>> dir()
['__builtins__', 'conf']
>>> load_session("session.scapy")
>>> dir()
['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_raw',
˓→ 'pkts']
Here is a more complex example to distinguish machines or their IP stacks from their IPID field. We can
see that 172.20.80.200:22 is answered by the same IP stack as 172.20.80.201 and that 172.20.80.197:25
is not answered by the same IP stack as other ports on the same IP.
It can help identify network topologies very easily when playing with TTL, displaying received TTL, etc.
3.2.21 Routing
Now Scapy has its own routing table, so that you can have your packets routed differently than the system:
>>> conf.route
Network Netmask Gateway Iface
127.0.0.0 255.0.0.0 0.0.0.0 lo
192.168.8.0 255.255.255.0 0.0.0.0 eth0
0.0.0.0 0.0.0.0 192.168.8.1 eth0
>>> conf.route.delt(net="0.0.0.0/0",gw="192.168.8.1")
>>> conf.route.add(net="0.0.0.0/0",gw="192.168.8.254")
>>> conf.route.add(host="192.168.1.1",gw="192.168.8.1")
>>> conf.route
Network Netmask Gateway Iface
127.0.0.0 255.0.0.0 0.0.0.0 lo
192.168.8.0 255.255.255.0 0.0.0.0 eth0
0.0.0.0 0.0.0.0 192.168.8.254 eth0
192.168.1.1 255.255.255.255 192.168.8.1 eth0
>>> conf.route.resync()
>>> conf.route
Network Netmask Gateway Iface
127.0.0.0 255.0.0.0 0.0.0.0 lo
192.168.8.0 255.255.255.0 0.0.0.0 eth0
0.0.0.0 0.0.0.0 192.168.8.1 eth0
3.2.22 Matplotlib
We can easily plot some harvested values using Matplotlib. (Make sure that you have matplotlib in-
stalled.) For example, we can observe the IP ID patterns to know how many distinct IP stacks are used
behind a load balancer:
40 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
>>> a, b = sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000))
>>> a.plot(lambda x:x[1].id)
[<matplotlib.lines.Line2D at 0x2367b80d6a0>]
>>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.
˓→copernic.com"],maxttl=20)
The last line is in fact the result of the function : a traceroute result object and a packet list of unanswered
packets. The traceroute result is a more specialised version (a subclass, in fact) of a classic result object.
We can save it to consult the traceroute result again a bit later, or to deeply inspect one of the answers,
for example to check padding.
42 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
Traceroute result object also have a very neat feature: they can make a directed graph from all the routes
they got, and cluster them by AS (Autonomous System). You will need graphviz. By default, ImageMag-
ick is used to display the graph.
44 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
If you have VPython installed, you also can have a 3D representation of the traceroute. With the right
button, you can rotate the scene, with the middle button, you can zoom, with the left button, you can
move the scene. If you click on a ball, it’s IP will appear/disappear. If you Ctrl-click on a ball, ports 21,
22, 23, 25, 80 and 443 will be scanned and the result displayed:
>>> res.trace3D()
ò Note
See the TroubleShooting section for more information on the usage of Monitor mode among Scapy.
Provided that your wireless card and driver are correctly configured for frame injection, you can have a
kind of FakeAP:
>>> sendp(RadioTap()/
Dot11(addr1="ff:ff:ff:ff:ff:ff",
addr2="00:01:02:03:04:05",
addr3="00:01:02:03:04:05")/
Dot11Beacon(cap="ESS", timestamp=1)/
(continues on next page)
46 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
Depending on the driver, the commands needed to get a working frame injection interface may vary. You
may also have to replace the first pseudo-layer (in the example RadioTap()) by PrismHeader(), or by
a proprietary pseudo-layer, or even to remove it.
3.3.3 IP Scan
A lower level IP Scan can be used to enumerate supported protocols:
Scapy also includes a built-in arping() function which performs similar to the above two commands:
>>> arping("192.168.1.0/24")
Any response to our probes will indicate a live host. We can collect results with the following command:
>>> ans.an[0].rdata
'217.25.178.5'
SOA request:
48 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
>>> ans.an[0].mname
b'dns.ovh.net.'
>>> ans.an[0].rname
b'tech.ovh.net.'
MX request:
Nestea attack:
>>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135))
You can also use relay=True to replace the joker behavior with a forward to a server included in conf.
nameservers.
50 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
>>> sr1(IP(dst="192.168.0.255")/UDP()/NBNSHeader()/NBNSQueryRequest(QUESTION_
˓→NAME="DC1"))
˓→multi=True, timeout=2)
>>> ans.show()
ò Note
As you can see, we used a scope identifier (%eth0) to specify on which interface we want to use the
above multicast IP.
192.168.1.1 time-exceeded
68.86.90.162 time-exceeded
4.79.43.134 time-exceeded
4.79.43.133 time-exceeded
4.68.18.126 time-exceeded
4.68.123.38 time-exceeded
4.2.2.1 SA
UDP traceroute
Tracerouting an UDP application like we do with TCP is not reliable, because there’s no handshake. We
need to give an applicative payload (DNS, ISAKMP, NTP, etc.) to deserve an answer:
DNS traceroute
We can perform a DNS traceroute by specifying a complete packet in l4 parameter of traceroute()
function:
Begin emission:
..*....******...******.***...****Finished to send 30 packets.
*****...***...............................
Received 75 packets, got 28 answers, remaining 2 packets
4.2.2.1:udp53
1 192.168.1.1 11
4 68.86.90.162 11
5 4.79.43.134 11
6 4.79.43.133 11
7 4.68.18.62 11
8 4.68.123.6 11
9 4.2.2.1
...
52 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
3.3.22 Etherleaking
>>> sr1(IP(dst="172.16.1.232")/ICMP())
<IP src=172.16.1.232 proto=1 [...] |<ICMP code=0 type=0 [...]|
<Padding load=’0O\x02\x01\x00\x04\x06public\xa2B\x02\x02\x1e’ |>>>
>>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP())
ò Note
On Windows and OSX, you will need to also use monitor=True, which only works on scapy>2.4.0
(2.4.0dev+). This might require you to manually toggle monitor mode.
The above command will produce output similar to the one below:
3.4 Recipes
3.4.1 Simplistic ARP Monitor
This program uses the sniff() callback (parameter prn). The store parameter is set to 0 so that the
sniff() function will not store anything (as it would do otherwise) and thus can run forever. The filter
parameter is used for better performances on high load : the filter is applied inside the kernel and Scapy
will only see ARP traffic.
3.4. Recipes 53
Scapy Documentation, Release 2.6.1
#! /usr/bin/env python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% % ARP.psrc%")
Solution
Use Scapy to send a DHCP discover request and analyze the replies:
˓→"message-type","discover"),"end"])
Begin emission:
Finished to send 1 packets.
.*...*..
Received 8 packets, got 2 answers, remaining 0 packets
In this case we got 2 replies, so there were two active DHCP servers on the test network:
>>> ans.summary()
Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==>␣
˓→Ether / IP / UDP 192.168.1.1:bootps > 255.255.255.255:bootpc / BOOTP / DHCP
54 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
Discussion
We specify multi=True to make Scapy wait for more answer packets after the first response is received.
This is also the reason why we can’t use the more convenient dhcp_request() function and have to
construct the DHCP packet manually: dhcp_request() uses srp1() for sending and receiving and
thus would immediately return after the first answer packet.
Moreover, Scapy normally makes sure that replies come from the same IP address the stimulus was sent
to. But our DHCP packet is sent to the IP broadcast address (255.255.255.255) and any answer packet
will have the IP address of the replying DHCP server as its source IP address (e.g. 192.168.1.1). Because
these IP addresses don’t match, we have to disable Scapy’s check with conf.checkIPaddr = False
before sending the stimulus.
See also
http://en.wikipedia.org/wiki/Rogue_DHCP
3.4.3 Firewalking
TTL decrementation after a filtering operation only not filtered packets generate an ICMP TTL exceeded
Find subnets on a multi-NIC firewall only his own NIC’s IP are reachable with this TTL:
Solution
To allow Scapy to reach target destination additional options must be used:
>>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',
˓→(0,0))]))
3.4. Recipes 55
Scapy Documentation, Release 2.6.1
Solution
That’s what wireshark() is for!
Discussion
wireshark(pktlist, ...)
With a Packet or PacketList, serialises your packets, and streams this into Wireshark via stdin
as if it were a capture device.
Because this uses pcap format to serialise the packets, there are some limitations:
• Packets must be all of the same linktype.
For example, you can’t mix Ether and IP at the top layer.
• Packets must have an assigned (and supported) DLT_* constant for the linktype. An un-
supported linktype is replaced with DLT_EN10MB (Ethernet), and will display incorrectly
in Wireshark.
For example, can’t pass a bare ICMP packet, but you can send it as a payload of an IP or IPv6
packet.
With a filename (passed as a string), this loads the given file in Wireshark. This needs to be in a
format that Wireshark supports.
You can tell Scapy where to find the Wireshark executable by changing the conf.prog.
wireshark configuration setting.
This accepts the same extra parameters as tcpdump().
ã See also
WiresharkSink
A PipeTools sink for live-streaming packets.
wireshark(1)
Additional description of Wireshark’s functionality, and its command-line arguments.
Wireshark’s website
For up-to-date releases of Wireshark.
Wireshark Protocol Reference
Contains detailed information about Wireshark’s protocol dissectors, and reference documen-
tation for various network protocols.
56 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
ò Note
Please bear in mind that Scapy is not designed to be blazing fast, but rather easily hackable & exten-
sible. The packet model makes it VERY easy to create new layers, compared to pretty much all other
alternatives, but comes with a performance cost. Of course, we still do our best to make Scapy as fast
as possible, but it’s not the absolute main goal.
Solution
There are quite a few ways of speeding up scapy’s dissection. You can use all of them
• Using a BPF filter: The OS is faster than Scapy. If you make the OS filter the packets instead
of Scapy, it will only handle a fraction of the load. Use the filter= argument of the sniff()
function.
• By disabling layers you don’t use: If you are not using some layers, why dissect them? You can
let Scapy know which layers to dissect and all the others will simply be parsed as Raw. This comes
with a great performance boost but requires you to know what you’re doing.
Solution
Disable the auto-loading of the routing tables:
CLI: in ~/.config/scapy/prestart.py add:
conf.route_autoload = False
conf.route6_autoload = False
Programmatically:
3.4. Recipes 57
Scapy Documentation, Release 2.6.1
At anytime, you can trigger the routes loading using conf.route.resync() or conf.route6.
resync(), or add the routes yourself as shown here.
3.4.8 OS Fingerprinting
ISN
Scapy can be used to analyze ISN (Initial Sequence Number) increments to possibly discover vulnerable
systems. First we will collect target responses by sending a number of SYN probes in a loop:
Once we obtain a reasonable number of responses we can start analyzing collected data with something
like this:
>>> temp = 0
>>> for s, r in ans:
... temp = r[TCP].seq - temp
... print("%d\t+%d" % (r[TCP].seq, temp))
...
4278709328 +4275758673
4279655607 +3896934
4280642461 +4276745527
4281648240 +4902713
4282645099 +4277742386
4283643696 +5901310
nmap_fp
Nmap fingerprinting (the old “1st generation” one that was done by Nmap up to v4.20) is supported in
Scapy. In Scapy v2 you have to load an extension module first:
>>> load_module("nmap")
If you have Nmap installed you can use it’s active os fingerprinting database with Scapy. Make sure that
version 1 of signature database is located in the path specified by:
>>> conf.nmap_base
Then you can use the nmap_fp() function which implements same probes as in Nmap’s OS Detection
engine:
>>> nmap_fp("192.168.1.1",oport=443,cport=1)
Begin emission:
.****..**Finished to send 8 packets.
*................................................
Received 58 packets, got 7 answers, remaining 1 packets
(1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch',
'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86)
w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11'])
58 Chapter 3. Usage
Scapy Documentation, Release 2.6.1
p0f
If you have p0f installed on your system, you can use it to guess OS name and version right from Scapy
(only SYN database is used). First make sure that p0f database exists in the path specified by:
>>> conf.p0f_base
>>> sniff(prn=prnp0f)
192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs)
-> 74.125.19.104:www (distance 0)
<Sniffed: TCP:339 UDP:2 ICMP:0 Other:156>
3.4. Recipes 59
Scapy Documentation, Release 2.6.1
60 Chapter 3. Usage
CHAPTER
FOUR
ADVANCED USAGE
ò Note
This is only my view on ASN.1, explained as simply as possible. For more theoretical or academic
views, I’m sure you’ll find better on the Internet.
ASN.1 is a notation whose goal is to specify formats for data exchange. It is independent of the way data
is encoded. Data encoding is specified in Encoding Rules.
The most used encoding rules are BER (Basic Encoding Rules) and DER (Distinguished Encoding
Rules). Both look the same, but the latter is specified to guarantee uniqueness of encoding. This property
is quite interesting when speaking about cryptography, hashes, and signatures.
ASN.1 provides basic objects: integers, many kinds of strings, floats, booleans, containers, etc. They
are grouped in the so-called Universal class. A given protocol can provide other objects which will be
grouped in the Context class. For example, SNMP defines PDU_GET or PDU_SET objects. There are
also the Application and Private classes.
Each of these objects is given a tag that will be used by the encoding rules. Tags from 1 are used for
Universal class. 1 is boolean, 2 is an integer, 3 is a bit string, 6 is an OID, 48 is for a sequence. Tags
from the Context class begin at 0xa0. When encountering an object tagged by 0xa0, we’ll need to know
the context to be able to decode it. For example, in SNMP context, 0xa0 is a PDU_GET object, while in
X509 context, it is a container for the certificate version.
Other objects are created by assembling all those basic brick objects. The composition is done using
sequences and arrays (sets) of previously defined or existing objects. The final object (an X509 certificate,
a SNMP packet) is a tree whose non-leaf nodes are sequences and sets objects (or derived context objects),
and whose leaf nodes are integers, strings, OID, etc.
61
Scapy Documentation, Release 2.6.1
ASN.1 engine
Note: many of the classes definitions presented here use metaclasses. If you don’t look precisely at the
source code and you only rely on my captures, you may think they sometimes exhibit a kind of magic
behavior. `` Scapy ASN.1 engine provides classes to link objects and their tags. They inherit from the
ASN1_Class. The first one is ASN1_Class_UNIVERSAL, which provide tags for most Universal objects.
Each new context (SNMP, X509) will inherit from it and add its own objects.
class ASN1_Class_UNIVERSAL(ASN1_Class):
name = "UNIVERSAL"
# [...]
BOOLEAN = 1
INTEGER = 2
BIT_STRING = 3
# [...]
class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL):
name="SNMP"
PDU_GET = 0xa0
PDU_NEXT = 0xa1
PDU_RESPONSE = 0xa2
class ASN1_Class_X509(ASN1_Class_UNIVERSAL):
name="X509"
CONT0 = 0xa0
CONT1 = 0xa1
# [...]
All ASN.1 objects are represented by simple Python instances that act as nutshells for the raw values.
The simple logic is handled by ASN1_Object whose they inherit from. Hence they are quite simple:
class ASN1_INTEGER(ASN1_Object):
tag = ASN1_Class_UNIVERSAL.INTEGER
class ASN1_STRING(ASN1_Object):
tag = ASN1_Class_UNIVERSAL.STRING
class ASN1_BIT_STRING(ASN1_STRING):
tag = ASN1_Class_UNIVERSAL.BIT_STRING
>>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_
˓→BOOLEAN(False)])])
>>> x
<ASN1_SEQUENCE[[<ASN1_INTEGER[7]>, <ASN1_STRING['egg']>, <ASN1_SEQUENCE[[
˓→<ASN1_BOOLEAN[False]>]]>]]>
>>> x.show()
# ASN1_SEQUENCE:
<ASN1_INTEGER[7]>
<ASN1_STRING['egg']>
# ASN1_SEQUENCE:
(continues on next page)
Encoding engines
As with the standard, ASN.1 and encoding are independent. We have just seen how to create a com-
pounded ASN.1 object. To encode or decode it, we need to choose an encoding rule. Scapy provides
only BER for the moment (actually, it may be DER. DER looks like BER except only minimal encoding
is authorised which may well be what I did). I call this an ASN.1 codec.
Encoding and decoding are done using class methods provided by the codec. For example the
BERcodec_INTEGER class provides a .enc() and a .dec() class methods that can convert between
an encoded string and a value of their type. They all inherit from BERcodec_Object which is able to
decode objects from any type:
>>> BERcodec_INTEGER.enc(7)
'\x02\x01\x07'
>>> BERcodec_BIT_STRING.enc("egg")
'\x03\x03egg'
>>> BERcodec_STRING.enc("egg")
'\x04\x03egg'
>>> BERcodec_STRING.dec('\x04\x03egg')
(<ASN1_STRING['egg']>, '')
>>> BERcodec_STRING.dec('\x03\x03egg')
Traceback (most recent call last):
File "<console>", line 1, in ?
File "/usr/bin/scapy", line 2099, in dec
return cls.do_dec(s, context, safe)
File "/usr/bin/scapy", line 2178, in do_dec
l,s,t = cls.check_type_check_len(s)
File "/usr/bin/scapy", line 2076, in check_type_check_len
l,s3 = cls.check_type_get_len(s)
File "/usr/bin/scapy", line 2069, in check_type_get_len
s2 = cls.check_type(s)
File "/usr/bin/scapy", line 2065, in check_type
(cls.__name__, ord(s[0]), ord(s[0]),cls.tag), remaining=s)
BER_BadTag_Decoding_Error: BERcodec_STRING: Got tag [3/0x3] while expecting
˓→<ASN1Tag STRING[4]>
ASN.1 objects are encoded using their .enc() method. This method must be called with the codec we
want to use. All codecs are referenced in the ASN1_Codecs object. raw() can also be used. In this case,
the default codec (conf.ASN1_default_codec) will be used.
>>> x.enc(ASN1_Codecs.BER)
'0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00'
(continues on next page)
>>> remain
''
By default, decoding is done using the Universal class, which means objects defined in the Context
class will not be decoded. There is a good reason for that: the decoding depends on the context!
>>> cert="""
... MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC
... VVMxHTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNB
... bWVyaWNhIE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIg
... Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAw
... MFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRB
... T0wgVGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUg
... SW5jLjE3MDUGA1UEAxMuQU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNh
... dGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
... ggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8TQ2FTBVs
... RotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWs
... i4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg
... /BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ
... 2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbqJS5Gr42whTg0
... ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rSAG2X
... +Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxml
... J85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh
... EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNo
... Kk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJ
... Kg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex
... MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB
... Af8wHQYDVR0OBBYEFE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaA
... FE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
... 9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0
... cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRF
... ASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIY
... vbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/
... drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ClTluUI8JPu3B5wwn3la
... 5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3p+v9WAks
... mWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5
... O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD
... 062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41
... xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H
... hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOL
(continues on next page)
˓→Authority 20\x1e\x17\r020529060000Z\x17\r370928234300Z0\x81\x831\x0b0\t\x06\
˓→x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x02\x0f\x000\x82\x02\n\x02\x82\x02\
˓→x01\x00\xb47Z\x08\x16\x99\x14\xe8U\xb1\x1b$k\xfc\xc7\x8b\xe6\x87\xa9\x89\
˓→xee\x8b\x99\xcdO@\x86\xa4\xb6M\xc9\xd9\xb1\xdc<M\r\x85L\x15lF\x8bRx\x9f\xf8
˓→#\xfdg\xf5$:h]\xd0\xf7daAT\xa3\x8b\xa5\x08\xd2)[\x9b`O&\x83\xd1c\x12VIv\xa4\
˓→x16\xc2\xa5\x9dE\xac\x8b\x84\x95\xa8\x16\xb1\xec\x9f\xea$\x1a\xef\xb9W\\\x9a
˓→$!,M\x0eq\x1f\xa6\xac]Et\x03\x98\xc4T\x8c\x16JAw\x86\x95u\x0cG\x01f`\xfc\
˓→x15\xf1\x0f\xea\xf5\x14x\xc7\x0e\xd7n\x81\x1c^\xbf^\xe7:*\xd8\x97\x170|\x00\
˓→xad\x08\x9d3\xaf\xb8\x99a\x80\x8b\xa8\x95~\x14\xdc\x12l\xa4\xd0\xd8\xef@I\
˓→x026\xf9n\xa9\xd6\x1d\x96V\x04\xb2\xb3-\x16V\x86\x8f\xd9 W\x80\xcdg\x10m\
˓→xb0L\xf0\xdaF\xb6\xea%.F\xaf\x8d\xb0\x8584\x8b\x14&\x82+\xac\xae\x99\x0b\
˓→x9f\xc8?\x06\xc5\xd2\xe9\x12F\x00N{\x08\xebB=+Hn\x9dg\xddK\x02\xe4D\xf3\x93\
˓→x19\xa5\'\xceiz\xbeg\xd3\xfcP\xa4,\xab\xc3k\xb9\xe3\x80L\xcf\x05aK+\xdc\x1b\
˓→xb9\xa6\xd2\xd0\xaa\xf5+s\xfb\xce\x905\x9f\x0cR\x1c\xbf\\!a\x11[\x15K\xa9$Q\
˓→xfc\xa4\\\xf7\x17\x9d\xb0\xd2\xfa\x07\xe9\x8fV\xe4\x1a\x8ch\x8a\x04\xd3|Z\
˓→xe3\x9e\xa2\xa1\xcaq[\xa2\xd4\xa0\xe7)\x85]\x03h*O\xd2\x06\xd7=\xf9\xc3\x03/
˓→?e\xf9g\x1eG@\xd3c\x0f\xe3\xd5\x8e\xf9\x85\xab\x97L\xb3\xd7&\xeb\x96\n\x94\
˓→xde\x856\x9c\xc8\x7f\x81\t\x02I*\x0e\xf5d2\x0c\x82\xd1\xbaj\x82\x1b\xb3Kt\
˓→x11\xf3\x8cw\xd6\x9f\xbf\xdc7\xa4\xa7U\x04/\xd41\xe8\xd3F\xb9\x03|\xda\
˓→x12NYd\xb7Q11P\xa0\xca\x1c\'\xd9\x10.\xad\xd6\xbd\x10f+\xc3\xb0"J\x12[\x02\
˓→x03\x01\x00\x01\xa3c0a0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\
˓→x01\xff0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14Oim\x03~\x9d\x9f\x07\x18C\xbc\
˓→xb7\x10N\xd5\xbf\xa9\xc4 (0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14Oim\x03~\
˓→x9d\x9f\x07\x18C\xbc\xb7\x10N\xd5\xbf\xa9\xc4 (0\x0e\x06\x03U\x1d\x0f\x01\
˓→x01\xff\x04\x04\x03\x02\x01\x860\r\x06\t*\x86H\x86\xf7\r\x01\x01\x05\x05\
˓→x00\x03\x82\x02\x01\x00;\xf3\xae\xca\xe8.\x87\x85\xfbeY\xe7\xad\x11\x14\
˓→xa5W\xbcX\x9f$\x12W\xbb\xfb?4\xda\xee\xadz*4rp1k\xc7\x19\x98\x80\xc9\x82\
˓→xde7w^T\x8b\x8e\xf2\xeagO\xc9t\x84\x91V\t\xd5\xe5z\x9a\x81\xb6\x81\xc2\xad6\
˓→xe4\xf1T\x11S\xf34E\x01&\xc8\xe5\x1a\xbc4D!\xde\xad%\xfcv\x16w!\x90\x80\
˓→x98W\x9dN\xea\xec/\xaa<\x14{W\xc1~\x18\x14g\xee$\xc6\xbd\xba\x15\xb0\xd2\
˓→x18\xbd\xb7U\x81\xacS\xc0\xe8\xddi\x12\x13B\xb7\x02\xb5\x05A\xcayPn\x82\
˓→x0eqr\x93F\xe8\x9d\r]\xbd\xae\xce)\xadc\xd5U\x16\x800\'\xffv\xba\xf7\xb8\
˓→xd6J\xe3\xd9\xb5\xf9R\xd0N@\xa9\xc7\xe5\xc22\xc7\xaav$\xe1k\x05P\xeb\xc5\
˓→xbf\nT\xe5\xb9B<$\xfb\xb7\x07\x9c0\x9fyZ\xe6\xe0@R\x15\xf4\xfc\xaa\xf4V\
˓→xf9D\x97\x87\xed\x0eer^\xbe&\xfbM\xa4-\x08\x07\xde\xd8\\\xa0\xdc\x813\x99\
˓→x18%\x11w\xa7\xeb\xfdX\t,\x99k\x1b\x8a\xf3R?\x1aMH`\xf1\xa0\xf63\x02S\x8b\
˓→xed%\t\xb8\r-\xed\x97s\xec\xd7\x96\x1f\x8e`\x0e\xda\x10\x9b/\x18$\xf6\xa6M\
˓→n\xf9;\xcbu\xc2\xcc/\xce$i\xc9\n"\x8eY\xa7\xf7\x82\x0c\xd7\xd7k5\x9cC\x00j\
˓→xc4\x95g\xba\x9cE\xcb\xb8\x0e7\xf7\xdcN\x01O\xbe\n\xb6\x03\xd3\xad\x8aE\xf7\
˓→xda\'M)\xb1H\xdf\xe4\x11\xe4\x96F\xbdl\x02>\xd6Q\xc8\x95\x17\x01\x15\xa9\
˓→xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\
˓→xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01<Q\x08.(\x18\xa4\x16\x0f1\xfd:l#\
˓→x93 v\xe1\xfd\x07\x85\xd1[?\xd2\x1cs2\xdd\xfa\xb9\xf8\x8c\xcf\x02\x87z\x9a\
˓→x96\xe4\xedO\x89\x8dSC\xab\x0e\x13\xc0\x01\x15\xb4y8\xdb\xfcn=\x9eQ\xb6\xb8\
˓→x13\x8bg\xcf\xf9|\xd9"\x1d\xf6]\xc5\x1c\x01/\x98\xe8z$\x18\xbc\x84\xd7\xfa\
˓→xdcr[\xf7\xc1:h'
# ASN1_SEQUENCE:
<ASN1_UTC_TIME['020529060000Z']>
<ASN1_UTC_TIME['370928234300Z']>
# ASN1_SEQUENCE:
# ASN1_SET:
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.4.6']>
<ASN1_PRINTABLE_STRING['US']>
# ASN1_SET:
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.4.10']>
<ASN1_PRINTABLE_STRING['AOL Time Warner Inc.']>
# ASN1_SET:
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.4.11']>
<ASN1_PRINTABLE_STRING['America Online Inc.']>
# ASN1_SET:
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.4.3']>
<ASN1_PRINTABLE_STRING['AOL Time Warner Root Certification␣
˓→Authority 2']>
# ASN1_SEQUENCE:
# ASN1_SEQUENCE:
<ASN1_OID['.1.2.840.113549.1.1.1']>
<ASN1_NULL[0L]>
<ASN1_BIT_STRING['\x000\x82\x02\n\x02\x82\x02\x01\x00\xb47Z\x08\x16\x99\
˓→x14\xe8U\xb1\x1b$k\xfc\xc7\x8b\xe6\x87\xa9\x89\xee\x8b\x99\xcdO@\x86\xa4\
˓→xb6M\xc9\xd9\xb1\xdc<M\r\x85L\x15lF\x8bRx\x9f\xf8#\xfdg\xf5$:h]\xd0\xf7daAT\
˓→xa3\x8b\xa5\x08\xd2)[\x9b`O&\x83\xd1c\x12VIv\xa4\x16\xc2\xa5\x9dE\xac\x8b\
˓→x84\x95\xa8\x16\xb1\xec\x9f\xea$\x1a\xef\xb9W\\\x9a$!,M\x0eq\x1f\xa6\xac]Et\
˓→x03\x98\xc4T\x8c\x16JAw\x86\x95u\x0cG\x01f`\xfc\x15\xf1\x0f\xea\xf5\x14x\
˓→x96V\x04\xb2\xb3-\x16V\x86\x8f\xd9 W\x80\xcdg\x10m\xb0L\xf0\xdaF\xb6\xea%.F\
˓→xaf\x8d\xb0\x8584\x8b\x14&\x82+\xac\xae\x99\x0b\x8e\x14\xd7R\xbd\x9ei\xc3\
˓→x86\x02\x0b\xeavu1\t\xce3\x19!\x85C\xe6\x89-\x9f%7g\xf1#j\xd2\x00m\x97\xf9\
˓→x9f\xe7)\xca\xdd\x1f\xd7\x06\xea\xb8\xc9\xb9\t!\x9f\xc8?\x06\xc5\xd2\xe9\
˓→x12F\x00N{\x08\xebB=+Hn\x9dg\xddK\x02\xe4D\xf3\x93\x19\xa5\'\xceiz\xbeg\xd3\
˓→xfcP\xa4,\xab\xc3k\xb9\xe3\x80L\xcf\x05aK+\xdc\x1b\xb9\xa6\xd2\xd0\xaa\
˓→xf5+s\xfb\xce\x905\x9f\x0cR\x1c\xbf\\!a\x11[\x15K\xa9$Q\xfc\xa4\\\xf7\x17\
˓→x9d\xb0\xd2\xfa\x07\xe9\x8fV\xe4\x1a\x8ch\x8a\x04\xd3|Z\xe3\x9e\xa2\xa1\
˓→xcaq[\xa2\xd4\xa0\xe7)\x85]\x03h*O\xd2\x06\xd7=\xf9\xc3\x03/?e\xf9g\x1eG@\
˓→xd3c\x0f\xe3\xd5\x8e\xf9\x85\xab\x97L\xb3\xd7&\xeb\x96\n\x94\xde\x856\x9c\
˓→xc8\x7f\x81\t\x02I*\x0e\xf5d2\x0c\x82\xd1\xbaj\x82\x1b\xb3Kt\x11\xf3\x8cw\
˓→xd6\x9f\xbf\xdc7\xa4\xa7U\x04/\xd41\xe8\xd3F\xb9\x03|\xda\x12NYd\xb7Q11P\
˓→xa0\xca\x1c\'\xd9\x10.\xad\xd6\xbd\x10f+\xc3\xb0"J\x12[\x02\x03\x01\x00\x01
˓→']>
# ASN1_X509_CONT3:
# ASN1_SEQUENCE:
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.29.19']>
<ASN1_BOOLEAN[-1L]>
<ASN1_STRING['0\x03\x01\x01\xff']>
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.29.14']>
<ASN1_STRING['\x04\x14Oim\x03~\x9d\x9f\x07\x18C\xbc\xb7\x10N\xd5\
˓→xbf\xa9\xc4 (']>
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.29.35']>
<ASN1_STRING['0\x16\x80\x14Oim\x03~\x9d\x9f\x07\x18C\xbc\xb7\x10N\
˓→xd5\xbf\xa9\xc4 (']>
# ASN1_SEQUENCE:
<ASN1_OID['.2.5.29.15']>
<ASN1_BOOLEAN[-1L]>
<ASN1_STRING['\x03\x02\x01\x86']>
# ASN1_SEQUENCE:
<ASN1_OID['.1.2.840.113549.1.1.5']>
<ASN1_NULL[0L]>
<ASN1_BIT_STRING['\x00;\xf3\xae\xca\xe8.\x87\x85\xfbeY\xe7\xad\x11\x14\xa5W\
˓→xbcX\x9f$\x12W\xbb\xfb?4\xda\xee\xadz*4rp1k\xc7\x19\x98\x80\xc9\x82\xde7w^T\
˓→x8b\x8e\xf2\xeagO\xc9t\x84\x91V\t\xd5\xe5z\x9a\x81\xb6\x81\xc2\xad6\xe4\
˓→xf1T\x11S\xf34E\x01&\xc8\xe5\x1a\xbc4D!\xde\xad%\xfcv\x16w!\x90\x80\x98W\
˓→x9dN\xea\xec/\xaa<\x14{W\xc1~\x18\x14g\xee$\xc6\xbd\xba\x15\xb0\xd2\x18\xbd\
˓→xb7U\x81\xacS\xc0\xe8\xddi\x12\x13B\xb7\x02\xb5\x05A\xcayPn\x82\x0eqr\x93F\
˓→xe8\x9d\r]\xbd\xae\xce)\xadc\xd5U\x16\x800\'\xffv\xba\xf7\xb8\xd6J\xe3\xd9\
˓→xb5\xf9R\xd0N@\xa9\xc7\xe5\xc22\xc7\xaav$\xe1k\x05P\xeb\xc5\xbf\nT\xe5\xb9B<
˓→$\xfb\xb7\x07\x9c0\x9fyZ\xe6\xe0@R\x15\xf4\xfc\xaa\xf4V\xf9D\x97\x87\xed\
˓→x0eer^\xbe&\xfbM\xa4-\x08\x07\xde\xd8\\\xa0\xdc\x813\x99\x18%\x11w\xa7\xeb\
˓→xfdX\t,\x99k\x1b\x8a\xf3R?\x1aMH`\xf1\xa0\xf63\x02S\x8b\xed%\t\xb8\r-\xed\
˓→x97s\xec\xd7\x96\x1f\x8e`\x0e\xda\x10\x9b/\x18$\xf6\xa6M\n\xf9;\xcbu\xc2\
˓→xdf\xe4\x11\xe4\x96F\xbdl\x02>\xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\
˓→xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\
˓→x9cp*O*9\x18\x95\x1e~\xfba\x01<Q\x08.(\x18\xa4\x16\x0f1\xfd:l#\x93 v\xe1\
˓→xfd\x07\x85\xd1[?\xd2\x1cs2\xdd\xfa\xb9\xf8\x8c\xcf\x02\x87z\x9a\x96\xe4\
˓→xedO\x89\x8dSC\xab\x0e\x13\xc0\x01\x15\xb4y8\xdb\xfcn=\x9eQ\xb6\xb8\x13\
˓→x8bg\xcf\xf9|\xd9"\x1d\xf6]\xc5\x1c\x01/\x98\xe8z$\x18\xbc\x84\xd7\xfa\
˓→xdcr[\xf7\xc1:h']>
ASN.1 layers
While this may be nice, it’s only an ASN.1 encoder/decoder. Nothing related to Scapy yet.
ASN.1 fields
Scapy provides ASN.1 fields. They will wrap ASN.1 objects and provide the necessary logic to bind a
field name to the value. ASN.1 packets will be described as a tree of ASN.1 fields. Then each field name
will be made available as a normal Packet object, in a flat flavor (ex: to access the version field of a
SNMP packet, you don’t need to know how many containers wrap it).
Each ASN.1 field is linked to an ASN.1 object through its tag.
ASN.1 packets
ASN.1 packets inherit from the Packet class. Instead of a fields_desc list of fields, they define
ASN1_codec and ASN1_root attributes. The first one is a codec (for example: ASN1_Codecs.BER),
the second one is a tree compounded with ASN.1 fields.
class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL):
name="SNMP"
PDU_GET = 0xa0
PDU_NEXT = 0xa1
PDU_RESPONSE = 0xa2
PDU_SET = 0xa3
PDU_TRAPv1 = 0xa4
PDU_BULK = 0xa5
PDU_INFORM = 0xa6
PDU_TRAPv2 = 0xa7
These objects are PDU, and are in fact new names for a sequence container (this is generally the case for
context objects: they are old containers with new names). This means creating the corresponding ASN.1
objects and BER codecs is simplistic:
class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE):
tag = ASN1_Class_SNMP.PDU_GET
# [...]
class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE):
tag = ASN1_Class_SNMP.PDU_GET
class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE):
tag = ASN1_Class_SNMP.PDU_NEXT
# [...]
Metaclasses provide the magic behind the fact that everything is automatically registered and that ASN.1
objects and BER codecs can find each other.
The ASN.1 fields are also trivial:
class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE):
ASN1_tag = ASN1_Class_SNMP.PDU_GET
class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE):
ASN1_tag = ASN1_Class_SNMP.PDU_NEXT
# [...]
SNMP_error = { 0: "no_error",
1: "too_big",
# [...]
}
SNMP_trap_types = { 0: "cold_start",
1: "warm_start",
# [...]
}
class SNMPvarbind(ASN1_Packet):
ASN1_codec = ASN1_Codecs.BER
ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid","1.3"),
ASN1F_field("value",ASN1_NULL(0))
)
class SNMPget(ASN1_Packet):
ASN1_codec = ASN1_Codecs.BER
ASN1_root = ASN1F_SNMP_PDU_GET( ASN1F_INTEGER("id",0),
ASN1F_enum_INTEGER("error",0, SNMP_error),
ASN1F_INTEGER("error_index",0),
(continues on next page)
class SNMPnext(ASN1_Packet):
ASN1_codec = ASN1_Codecs.BER
ASN1_root = ASN1F_SNMP_PDU_NEXT( ASN1F_INTEGER("id",0),
ASN1F_enum_INTEGER("error",0, SNMP_
˓→error),
ASN1F_INTEGER("error_index",0),
ASN1F_SEQUENCE_OF("varbindlist", [],␣
˓→SNMPvarbind)
)
# [...]
class SNMP(ASN1_Packet):
ASN1_codec = ASN1_Codecs.BER
ASN1_root = ASN1F_SEQUENCE(
ASN1F_enum_INTEGER("version", 1, {0:"v1", 1:"v2c", 2:"v2", 3:"v3"}),
ASN1F_STRING("community","public"),
ASN1F_CHOICE("PDU", SNMPget(),
SNMPget, SNMPnext, SNMPresponse, SNMPset,
SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2)
)
def answers(self, other):
return ( isinstance(self.PDU, SNMPresponse) and
( isinstance(other.PDU, SNMPget) or
isinstance(other.PDU, SNMPnext) or
isinstance(other.PDU, SNMPset) ) and
self.PDU.id == other.PDU.id )
# [...]
bind_layers( UDP, SNMP, sport=161)
bind_layers( UDP, SNMP, dport=161)
That wasn’t that much difficult. If you think that can’t be that short to implement SNMP encod-
ing/decoding and that I may have cut too much, just look at the complete source code.
Now, how to use it? As usual:
... SNMPvarbind(oid="3.2.1",value=
˓→"hello")]))
>>> a.show()
###[ SNMP ]###
version= v3
community= 'public'
\PDU\
|###[ SNMPget ]###
| id= 0
(continues on next page)
>>> o1=ASN1_OID("2.5.29.10")
>>> o2=ASN1_OID("1.2.840.113549.1.1.1")
>>> o1,o2
(<ASN1_OID['.2.5.29.10']>, <ASN1_OID['.1.2.840.113549.1.1.1']>)
Loading a MIB
Scapy can parse MIB files and become aware of a mapping between an OID and its name:
>>> load_mib("mib/*")
>>> o1,o2
(continues on next page)
>>> conf.mib.sha1_with_rsa_signature
'1.2.840.113549.1.1.5'
or to resolve an OID:
>>> conf.mib._oidname("1.2.3.6.1.4.1.5")
'enterprises.5'
>>> conf.mib._make_graph()
4.2 Automata
Scapy enables to create easily network automata. Scapy does not stick to a specific model like Moore or
Mealy automata. It provides a flexible way for you to choose your way to go.
An automaton in Scapy is deterministic. It has different states. A start state and some end and error states.
There are transitions from one state to another. Transitions can be transitions on a specific condition,
transitions on the reception of a specific packet or transitions on a timeout. When a transition is taken,
one or more actions can be run. An action can be bound to many transitions. Parameters can be passed
from states to transitions, and from transitions to states and actions.
From a programmer’s point of view, states, transitions and actions are methods from an Automaton
subclass. They are decorated to provide meta-information needed in order for the automaton to work.
class HelloWorld(Automaton):
@ATMT.state(initial=1)
def BEGIN(self):
print("State=BEGIN")
@ATMT.condition(BEGIN)
def wait_for_nothing(self):
print("Wait for nothing...")
raise self.END()
@ATMT.action(wait_for_nothing)
def on_nothing(self):
(continues on next page)
4.2. Automata 73
Scapy Documentation, Release 2.6.1
@ATMT.state(final=1)
def END(self):
print("State=END")
>>> a=HelloWorld()
>>> a.run()
State=BEGIN
Wait for nothing...
Action on 'nothing' condition
State=END
>>> a.destroy()
>>> HelloWorld.graph()
ò Note
An Automaton can be reset using restart(). It is then possible to run it again.
. Warning
Remember to call destroy() once you’re done using an Automaton. (especially on PyPy)
@ATMT.state()
def MY_STATE(self, param1, param2):
print("state=MY_STATE. param1=%r param2=%r" % (param1, param2))
@ATMT.receive_condition(ANOTHER_STATE)
def received_ICMP(self, pkt):
if ICMP in pkt:
raise self.MY_STATE("got icmp", pkt[ICMP].type)
Let’s suppose we want to bind an action to this transition, that will also need some parameters:
@ATMT.action(received_ICMP)
def on_ICMP(self, icmp_type, icmp_code):
self.retaliate(icmp_type, icmp_code)
@ATMT.receive_condition(ANOTHER_STATE)
def received_ICMP(self, pkt):
if ICMP in pkt:
raise self.MY_STATE("got icmp", pkt[ICMP].type).action_
˓→parameters(pkt[ICMP].type, pkt[ICMP].code)
4.2. Automata 75
Scapy Documentation, Release 2.6.1
class TFTP_read(Automaton):
def parse_args(self, filename, server, sport = None, port=69, **kargs):
Automaton.parse_args(self, **kargs)
self.filename = filename
self.server = server
self.port = port
self.sport = sport
# BEGIN
@ATMT.state(initial=1)
def BEGIN(self):
self.blocksize=512
self.my_tid = self.sport or RandShort()._fix()
bind_bottom_up(UDP, TFTP, dport=self.my_tid)
self.server_tid = None
self.res = b""
self.send(self.last_packet)
self.awaiting=1
raise self.WAITING()
# WAITING
(continues on next page)
@ATMT.receive_condition(WAITING)
def receive_data(self, pkt):
if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting:
if self.server_tid is None:
self.server_tid = pkt[UDP].sport
self.l3[UDP].dport = self.server_tid
raise self.RECEIVING(pkt)
@ATMT.action(receive_data)
def send_ack(self):
self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting)
self.send(self.last_packet)
@ATMT.receive_condition(WAITING, prio=1)
def receive_error(self, pkt):
if TFTP_ERROR in pkt:
raise self.ERROR(pkt)
@ATMT.timeout(WAITING, 3)
def timeout_waiting(self):
raise self.WAITING()
@ATMT.action(timeout_waiting)
def retransmit_last_packet(self):
self.send(self.last_packet)
# RECEIVED
@ATMT.state()
def RECEIVING(self, pkt):
recvd = pkt[Raw].load
self.res += recvd
self.awaiting += 1
if len(recvd) == self.blocksize:
raise self.WAITING()
raise self.END()
# ERROR
@ATMT.state(error=1)
def ERROR(self,pkt):
split_bottom_up(UDP, TFTP, dport=self.my_tid)
return pkt[TFTP_ERROR].summary()
#END
@ATMT.state(final=1)
def END(self):
split_bottom_up(UDP, TFTP, dport=self.my_tid)
return self.res
4.2. Automata 77
Scapy Documentation, Release 2.6.1
States are methods decorated by the result of the ATMT.state function. It can take 4 optional parameters,
initial, final, stop and error, that, when set to True, indicating that the state is an initial, final,
stop or error state.
ò Note
The initial state is called while starting the automata. The final step will tell the automata has
reached its end. If you call atmt.stop(), the automata will move to the stop step whatever its
current state is. The error state will mark the automata as errored. If no stop state is specified,
calling stop and forcestop will be equivalent.
class Example(Automaton):
@ATMT.state(initial=1)
def BEGIN(self):
pass
@ATMT.state()
def SOME_STATE(self):
pass
@ATMT.state(final=1)
def END(self):
return "Result of the automaton: 42"
@ATMT.state(stop=1)
def STOP(self):
print("SHUTTING DOWN...")
# e.g. close sockets...
@ATMT.condition(STOP)
def is_stopping(self):
raise self.END()
@ATMT.state(error=1)
def ERROR(self):
return "Partial result, or explanation"
# [...]
The START event is initial=1, the STOP event is stop=1 and the CLOSED event is final=1.
class Example(Automaton):
@ATMT.state()
def WAITING(self):
pass
@ATMT.condition(WAITING)
def it_is_raining(self):
if not self.have_umbrella:
raise self.ERROR_WET()
@ATMT.receive_condition(WAITING, prio=1)
def it_is_ICMP(self, pkt):
if ICMP in pkt:
raise self.RECEIVED_ICMP(pkt)
@ATMT.receive_condition(WAITING, prio=2)
def it_is_IP(self, pkt):
if IP in pkt:
raise self.RECEIVED_IP(pkt)
@ATMT.timeout(WAITING, 10.0)
def waiting_timeout(self):
raise self.ERROR_TIMEOUT()
Actions are methods that are decorated by the return of ATMT.action function. This function takes the
transition method it is bound to as first parameter and an optional priority prio as a second parameter.
The default priority is 0. An action method can be decorated many times to be bound to many transitions.
4.2. Automata 79
Scapy Documentation, Release 2.6.1
class Example(Automaton):
@ATMT.state(initial=1)
def BEGIN(self):
pass
@ATMT.state(final=1)
def END(self):
pass
@ATMT.condition(BEGIN, prio=1)
def maybe_go_to_end(self):
if random() > 0.5:
raise self.END()
@ATMT.condition(BEGIN, prio=2)
def certainly_go_to_end(self):
raise self.END()
@ATMT.action(maybe_go_to_end)
def maybe_action(self):
print("We are lucky...")
@ATMT.action(certainly_go_to_end)
def certainly_action(self):
print("We are not lucky...")
@ATMT.action(maybe_go_to_end, prio=1)
@ATMT.action(certainly_go_to_end, prio=1)
def always_action(self):
print("This wasn't luck!...")
>>> a=Example()
>>> a.run()
We are not lucky...
This wasn't luck!...
>>> a.run()
We are lucky...
This wasn't luck!...
>>> a.destroy()
ò Note
If you want to pass a parameter to an action, you can use the action_parameters function while
raising the next state.
In the following example, the send_copy action takes a parameter passed by is_fin:
class Example(Automaton):
@ATMT.state()
def WAITING(self):
pass
@ATMT.state()
def FIN_RECEIVED(self):
pass
@ATMT.receive_condition(WAITING)
def is_fin(self, pkt):
if pkt[TCP].flags.F:
raise self.FIN_RECEIVED().action_parameters(pkt)
@ATMT.action(is_fin)
def send_copy(self, pkt):
send(pkt)
Methods to overload
Two methods are hooks to be overloaded:
• The parse_args() method is called with arguments given at __init__() and run(). Use that
to parametrize the behavior of your automaton.
• The master_filter() method is called each time a packet is sniffed and decides if it is interesting
for the automaton. When working on a specific protocol, this is where you will ensure the packet
belongs to the connection you are being part of, so that you do not need to make all the sanity
checks in each transition.
Timer configuration
Some protocols allow timer configuration. In order to configure timeout values during class initialization
one may use timer_by_name() method, which returns Timer object associated with the given function
name:
class Example(Automaton):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
timer = self.timer_by_name("waiting_timeout")
timer.set(1)
@ATMT.state(initial=1)
def WAITING(self):
pass
@ATMT.state(final=1)
def END(self):
pass
@ATMT.timeout(WAITING, 10.0)
(continues on next page)
4.2. Automata 81
Scapy Documentation, Release 2.6.1
4.3 PipeTools
Scapy’s pipetool is a smart piping system allowing to perform complex stream data management.
The goal is to create a sequence of steps with one or several inputs and one or several outputs, with a
bunch of blocks in between. PipeTools can handle varied sources of data (and outputs) such as user input,
pcap input, sniffing, wireshark. . . A pipe system is implemented by manually linking all its parts. It is
possible to dynamically add an element while running or set multiple drains for the same source.
ò Note
Pipetool default objects are located inside scapy.pipetool
source = SniffSource(iface=conf.iface)
wire = WiresharkSink()
def transf(pkt):
if not pkt or IP not in pkt:
return pkt
pkt[IP].src = "1.1.1.1"
pkt[IP].dst = "2.2.2.2"
return pkt
When running, a pipetool engine waits for any available data from the Source, and send it in the Drains
linked to it. The data then goes from Drains to Drains until it arrives in a Sink, the final state of this data.
Let’s see with a basic demo how to build a pipetool system.
>>> s = CLIFeeder()
>>> s2 = CLIHighFeeder()
>>> d1 = Drain()
>>> d2 = TransformDrain(lambda x: x[::-1])
>>> si1 = ConsoleSink()
>>> si2 = QueueSink()
>>>
>>> s > d1
>>> d1 > si1
>>> d1 > si2
>>>
>>> s2 >> d1
>>> d1 >> d2
>>> d2 >> si1
>>>
>>> p = PipeEngine()
>>> p.add(s)
>>> p.add(s2)
>>> p.graph(target="> the_above_image.png")
>>> p.start()
4.3. PipeTools 83
Scapy Documentation, Release 2.6.1
>>> s.send("foo")
>'foo'
>>> s2.send("bar")
>>'rab'
>>> s.send("i like potato")
>'i like potato'
>>> print(si2.recv(), ":", si2.recv())
foo : i like potato
>>> help(ConsoleSink)
Help on class ConsoleSink in module scapy.pipetool:
class ConsoleSink(Sink)
| Print messages on low and high entries
| +-------+
| >>-|--. |->>
| | print |
| >-|--' |->
| +-------+
|
[...]
Sources
A Source is a class that generates some data.
There are several source types integrated with Scapy, usable as-is, but you may also create yours.
For any of those class, have a look at help([theclass]) to get more information or the required pa-
rameters.
• CLIFeeder : a source especially used in interactive software. its send(data) generates the event
data on the lower canal
• CLIHighFeeder : same than CLIFeeder, but writes on the higher canal
• PeriodicSource : Generate messages periodically on the low canal.
• AutoSource: the default source, that must be extended to create custom sources.
ò Note
Do NOT use the default Source class except if you are really sure of what you are doing: it is only
used internally, and is missing some implementation. The AutoSource is made to be used.
To send data through it, the object must call its self._gen_data(msg) or self.
_gen_high_data(msg) functions, which send the data into the PipeEngine.
The Source should also (if possible), set self.is_exhausted to True when empty, to allow the clean
stop of the PipeEngine. If the source is infinite, it will need a force-stop (see PipeEngine below)
For instance, here is how CLIHighFeeder is implemented:
class CLIFeeder(CLIFeeder):
def send(self, msg):
self._gen_high_data(msg)
def close(self):
self.is_exhausted = True
Drains
Default Drain classes
Drains need to be linked on the entry that you are using. It can be either on the lower one (using >) or
the upper one (using >>). See the basic example above.
• Drain : the most basic Drain possible. Will pass on both low and high entry if linked properly.
• TransformDrain : Apply a function to messages on low and high entry
• UpDrain : Repeat messages from low entry to high exit
• DownDrain : Repeat messages from high entry to low exit
class TransformDrain(Drain):
def __init__(self, f, name=None):
Drain.__init__(self, name=name)
(continues on next page)
4.3. PipeTools 85
Scapy Documentation, Release 2.6.1
Sinks
Sinks are destinations for messages.
A Sink receives data like a Drain, but doesn’t send any messages after it.
Messages on the low entry come from push(), and messages on the high entry come from high_push().
To create a custom sink, one must extend Sink and implement push() and/or high_push().
This is a simplified version of ConsoleSink:
class ConsoleSink(Sink):
def push(self, msg):
print(">%r" % msg)
def high_push(self, msg):
print(">>%r" % msg)
>>> a = CLIFeeder()
>>> b = Drain()
>>> c = ConsoleSink()
>>> a > b > c
>>> p = PipeEngine()
>>> p.add(a)
This wouldn’t link the high entries, so something like this would do nothing:
>>> a2 = CLIHighFeeder()
>>> a2 >> b
>>> a2.send("hello")
Because b (Drain) and c (scapy.pipetool.ConsoleSink) are not linked on the high entry.
However, using a DownDrain would bring the high messages from CLIHighFeeder to the lower channel:
>>> a2 = CLIHighFeeder()
>>> b2 = DownDrain()
>>> a2 >> b2
>>> b2 > b
>>> a2.send("hello")
ò Note
Unlike the previous objects, those are not located in scapy.pipetool but in scapy.scapypipes
Now that you know the default PipeTool objects, here are some more advanced ones, based on packet
functionalities.
• SniffSource : Read packets from an interface and send them to low exit.
• RdpcapSource : Read packets from a PCAP file send them to low exit.
• InjectSink : Packets received on low input are injected (sent) to an interface
• WrpcapSink : Packets received on low input are written to PCAP file
• UDPDrain : UDP payloads received on high entry are sent over UDP (complicated, have a look at
help(UDPDrain))
• FDSourceSink : Use a file descriptor as source and sink
• TCPConnectPipe: TCP connect to addr:port and use it as source and sink
4.3. PipeTools 87
Scapy Documentation, Release 2.6.1
• TCPListenPipe : TCP listen on [addr:]port and use the first connection as source and sink (com-
plicated, have a look at help(TCPListenPipe))
4.3.6 Triggering
Some special sort of Drains exists: the Trigger Drains.
Trigger Drains are special drains, that on receiving data not only pass it by but also send a “Trigger”
input, that is received and handled by the next triggered drain (if it exists).
For example, here is a basic TriggerDrain usage:
>>> a = CLIFeeder()
>>> d = TriggerDrain(lambda msg: True) # Pass messages and trigger when a␣
˓→condition is met
>>> d2 = TriggeredValve()
>>> s = ConsoleSink()
>>> a > d > d2 > s
>>> d ^ d2 # Link the triggers
>>> p = PipeEngine(s)
>>> p.start()
INFO: Pipe engine thread started.
>>>
>>> a.send("this will be printed")
>'this will be printed'
>>> a.send("this won't, because the valve was switched")
>>> a.send("this will, because the valve was switched again")
>'this will, because the valve was switched again'
>>> p.stop()
Several triggering Drains exist, they are pretty explicit. It is highly recommended to check the doc using
help([the class])
• TriggeredMessage : Send a preloaded message when triggered and trigger in chain
• TriggerDrain : Pass messages and trigger when a condition is met
• TriggeredValve : Let messages alternatively pass or not, changing on trigger
• TriggeredQueueingValve : Let messages alternatively pass or queued, changing on trigger
• TriggeredSwitch : Let messages alternatively high or low, changing on trigger
FIVE
Scapy maintains its own network stack, which is independent from the one of your operating system. It
possesses its own interfaces list, routing table, ARP cache, IPv6 neighbour cache, nameservers config. . .
and so on, all of which is configurable.
Here are a few examples of where this is used:
- When you use ``sr()/send()``, Scapy will use internally its own routing␣
˓→table (``conf.route``) in order to find which interface to use, and␣
- When using ``dns_resolve()``, Scapy uses its own nameservers list (``conf.
˓→nameservers``) to perform the request
- etc.
ò Note
What’s important to note is that Scapy initializes its own tables by querying the OS-specific ones. It
has therefore implemented bindings for Linux/Windows/BSD.. in order to retrieve such data, which
may also be used as a high-level API, documented below.
>>> conf.ifaces
Source Index Name MAC IPv4 IPv6
sys 1 lo 00:00:00:00:00:00 127.0.0.1 ::1
sys 2 eth0 Microsof:12:cb:ef 10.0.0.5 fe80::10a:2bef:dc12:afae
>>> conf.ifaces.dev_from_index(2)
<NetworkInterface eth0 [UP+BROADCAST+RUNNING+SLAVE]>
You can also use the older get_if_list() function in order to only get the interface names.
>>> get_if_list()
['lo', 'eth0']
89
Scapy Documentation, Release 2.6.1
>>> load_extcap()
>>> conf.ifaces
Source Index Name Address
ciscodump 100 Cisco remote capture ciscodump
dpauxmon 100 DisplayPort AUX channel monitor capture dpauxmon
randpktdump 100 Random packet generator randpkt
sdjournal 100 systemd Journal Export sdjournal
sshdump 100 SSH remote capture sshdump
udpdump 100 UDP Listener remote capture udpdump
wifidump 100 Wi-Fi remote capture wifidump
Source Index Name MAC IPv4 IPv6
sys 1 lo 00:00:00:00:00:00 127.0.0.1 ::1
sys 2 eth0 Microsof:12:cb:ef 10.0.0.5 fe80::10a:2bef:dc12:afae
Here’s an example of how to use sshdump. As you can see you can pass arguments that are properly
converted:
>>> load_extcap()
>>> sniff(
... iface="sshdump",
... prn=lambda x: x.summary(),
... remote_host="192.168.0.1",
... remote_username="root",
... remote_password="SCAPY",
... )
>>> conf.ifaces.dev_from_networkname("sshdump").get_extcap_config()
ò Note
If you want to change or edit the routes, have a look at the “Routing” section in Usage
The routes are stores in conf.route. You can use it to display the routes, or get specific routing
>>> conf.route
Get the route for a specific IP: conf.route.route() will return (interface, outgoing_ip,
gateway)
>>> conf.route.route("127.0.0.1")
('lo', '127.0.0.1', '0.0.0.0')
SIX
You can use Scapy to make your own automated tools. You can also extend Scapy without having to edit
its source file.
If you have built some interesting tools, please contribute back to the github wiki !
#! /usr/bin/env python
import sys
from scapy.all import sr1,IP,ICMP
p=sr1(IP(dst=sys.argv[1])/ICMP())
if p:
p.show()
import logging
logging.getLogger("scapy").setLevel(logging.CRITICAL)
To disable almost all logs. (Scapy simply won’t work properly if a CRITICAL failure occurs)
ò Note
On interactive mode, the default log level is INFO
93
Scapy Documentation, Release 2.6.1
#! /usr/bin/env python
# arping2tex : arpings a network and outputs a LaTeX table as a result
import sys
if len(sys.argv) != 2:
print("Usage: arping2tex <net>\n eg: arping2tex 192.168.1.0/24")
sys.exit(1)
print(r"\begin{tabular}{|l|l|}")
print(r"\hline")
print(r"MAC & IP\\")
print(r"\hline")
for snd,rcv in ans:
print(rcv.sprintf(r"%Ether.src% & %ARP.psrc%\\"))
print(r"\hline")
print(r"\end{tabular}")
Here is another tool that will constantly monitor all interfaces on a machine and print all ARP request it
sees, even on 802.11 frames from a Wi-Fi card in monitor mode. Note the store=0 parameter to sniff()
to avoid storing all packets in memory for nothing:
#! /usr/bin/env python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% % ARP.psrc%")
For a real life example, you can check Wifitap. Sadly, Wifitap is no longer maintained but nonetheless
demonstrates Scapy’s Wi-Fi capabilities. The code can be retrieved from github.
#! /usr/bin/env python
class Test(Packet):
name = "Test packet"
fields_desc = [ ShortField("test1", 1),
ShortField("test2", 2) ]
def make_test(x,y):
return Ether()/IP()/Test(test1=x,test2=y)
if __name__ == "__main__":
interact(mydict=globals(), mybanner="Test add-on v3.14")
If you put the above listing in the test_interact.py file and make it executable, you’ll get:
# ./test_interact.py
Welcome to Scapy (0.9.17.109beta)
Test add-on v3.14
>>> make_test(42,666)
<Ether type=0x800 |<IP |<Test test1=42 test2=666 |>>>
SEVEN
Adding a new protocol (or more correctly: a new layer) in Scapy is very easy. All the magic is in the
fields. If the fields you need are already there and the protocol is not too brain-damaged, this should be
a matter of minutes.
class Disney(Packet):
name = "DisneyPacket "
fields_desc=[ ShortField("mickey",5),
XByteField("minnie",3) ,
IntEnumField("donald" , 1 ,
{ 1: "happy", 2: "cool" , 3: "angry" } ) ]
In this example, our layer has three fields. The first one is a 2-byte integer field named mickey and whose
default value is 5. The second one is a 1-byte integer field named minnie and whose default value is
3. The difference between a vanilla ByteField and an XByteField is only the fact that the preferred
human representation of the field’s value is in hexadecimal. The last field is a 4-byte integer field named
donald. It is different from a vanilla IntField by the fact that some of the possible values of the field
have literate representations. For example, if it is worth 3, the value will be displayed as angry. Moreover,
if the “cool” value is assigned to this field, it will understand that it has to take the value 2.
If your protocol is as simple as this, it is ready to use:
>>> d=Disney(mickey=1)
>>> ls(d)
mickey : ShortField = 1 (5)
minnie : XByteField = 3 (3)
donald : IntEnumField = 1 (1)
>>> d.show()
###[ Disney Packet ]###
mickey= 1
minnie= 0x3
donald= happy
>>> d.donald="cool"
(continues on next page)
97
Scapy Documentation, Release 2.6.1
This chapter explains how to build a new protocol within Scapy. There are two main objectives:
• Dissecting: this is done when a packet is received (from the network or a file) and should be
converted to Scapy’s internals.
• Building: When one wants to send such a new packet, some stuff needs to be adjusted automatically
in it.
7.2 Layers
Before digging into dissection itself, let us look at how packets are organized.
>>> p = IP()/TCP()/"AAAA"
>>> p
<IP frag=0 proto=TCP |<TCP |<Raw load='AAAA' |>>>
>>> p.summary()
'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw'
ò Note
There is an optional argument (nb) which returns the nb th layer of required protocol.
Let’s put everything together now, playing with the TCP layer:
>>> tcp=p[TCP]
>>> tcp.underlayer
<IP frag=0 proto=TCP |<TCP |<Raw load='AAAA' |>>>
>>> tcp.payload
<Raw load='AAAA' |>
As expected, tcp.underlayer points to the beginning of our IP packet, and tcp.payload to its pay-
load.
class UDP(Packet):
name = "UDP"
fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES),
ShortEnumField("dport", 53, UDP_SERVICES),
ShortField("len", None),
XShortField("chksum", None), ]
And you are done! There are many fields already defined for convenience, look at the doc``^W`` sources
as Phil would say.
So, defining a layer is simply gathering fields in a list. The goal is here to provide the efficient default
values for each field so the user does not have to give them when he builds a packet.
The main mechanism is based on the Field structure. Always keep in mind that a layer is just a little
more than a list of fields, but not much more.
So, to understand how layers are working, one needs to look quickly at how the fields are handled.
class StrFixedLenField(StrField):
def addfield(self, pkt, s, val):
return s+struct.pack("%is"%self.length,self.i2m(pkt, val))
• getfield(self, pkt, s): extract from the raw packet s the field value belonging to layer pkt.
It returns a list, the 1st element is the raw packet string after having removed the extracted field,
the second one is the extracted field itself in internal representation:
7.2. Layers 99
Scapy Documentation, Release 2.6.1
class StrFixedLenField(StrField):
def getfield(self, pkt, s):
return s[self.length:], self.m2i(pkt,s[:self.length])
When defining your own layer, you usually just need to define some *2*() methods, and sometimes also
the addfield() and getfield().
def vlenq2str(l):
s = []
s.append(l & 0x7F)
l = l >> 7
while l > 0:
s.append( 0x80 | (l & 0x7F) )
l = l >> 7
s.reverse()
return bytes(bytearray(s))
def str2vlenq(s=b""):
i = l = 0
while i < len(s) and ord(s[i:i+1]) & 0x80:
l = l << 7
l = l + (ord(s[i:i+1]) & 0x7F)
i = i + 1
if i == len(s):
warning("Broken vlenq: no ending byte")
l = l << 7
l = l + (ord(s[i:i+1]) & 0x7F)
return s[i+1:], l
We will define a field which computes automatically the length of an associated string, but used that
encoding format:
class VarLenQField(Field):
""" variable length quantities """
__slots__ = ["fld"]
class FOO(Packet):
name = "FOO"
fields_desc = [ VarLenQField("len", None, "data"),
StrLenField("data", "", length_from=lambda pkt: pkt.len) ]
>>> f = FOO(data="A"*129)
>>> f.show()
###[ FOO ]###
len= None
data=
˓→'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
˓→'
Here, len has yet to be computed and only the default value is displayed. This is the current internal
representation of our layer. Let’s force the computation now:
>>> f.show2()
###[ FOO ]###
len= 129
data=
˓→'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
˓→'
The method show2() displays the fields with their values as they will be sent to the network, but in a
human readable way, so we see len=129. Last but not least, let us look now at the machine representation:
>>> raw(f)
'\x81\
˓→x01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
˓→'
7.3 Dissecting
Layers only are list of fields, but what is the glue between each field, and after, between each layer. These
are the mysteries explain in this section.
When called, s is a string containing what is going to be dissected. self points to the current layer.
>>> p=IP("A"*20)/TCP("B"*32)
WARNING: bad dataofs (4). Assuming dataofs=5
>>> p
<IP version=4L ihl=1L tos=0x41 len=16705 id=16705 flags=DF frag=321L ttl=65␣
˓→proto=65 chksum=0x4141
At the end, all the layers in the packet are dissected, and glued together with their known types.
So, it takes the raw string packet, and feed each field with it, as long as there are data or fields remaining:
>>> FOO("\xff\xff"+"B"*8)
<FOO len=2097090 data='BBBBBBB' |>
>>> vlenq2str(2097090)
'\xff\xffB'
Then, the next field is extracted the same way, until 2097090 bytes are put in FOO.data (or less if 2097090
bytes are not available, as here).
If there are some bytes left after the dissection of the current layer, it is mapped in the same way to the
what the next is expected to be (Raw by default):
>>> FOO("\x05"+"B"*8)
<FOO len=5 data='BBBBB' |<Raw load='BBB' |>>
Each time a packet ProtoA()/ProtoB() will be created, the FieldToBind of ProtoA will be equal to
Value.
For instance, if you have a class HTTP, you may expect that all the packets coming from or going to port
80 will be decoded as such. This is simply done that way:
That’s all folks! Now every packet related to port 80 will be associated to the layer HTTP, whether it is
read from a pcap file or received from the network.
class Dot11(Packet):
def guess_payload_class(self, payload):
if self.FCfield & 0x40:
return Dot11WEP
else:
return Packet.guess_payload_class(self, payload)
Now every packet with source port 53 will not be handled as DNS, but whatever you specify instead.
>>> p=TCP()
>>> p.payload_guess
[({'dport': 2000}, <class 'scapy.Skinny'>), ({'sport': 2000}, <class 'scapy.
˓→Skinny'>), ... )]
Then, when it needs to guess the next layer class, it calls the default method Packet.
guess_payload_class(). This method runs through each element of the list payload_guess, each
element being a tuple:
• the 1st value is a field to test ('dport': 2000)
• the 2nd value is the guessed class if it matches (Skinny)
So, the default guess_payload_class() tries all element in the list, until one matches. If no element
are found, it then calls default_payload_class(). If you have redefined this method, then yours is
called, otherwise, the default one is called, and Raw type is returned.
Packet.guess_payload_class()
• test what is in field guess_payload
• call overloaded guess_payload_class()
7.4 Building
Building a packet is as simple as building each layer. Then, some magic happens to glue everything.
Let’s do magic then.
>>> p = IP()/TCP()
>>> hexdump(p)
0000 45 00 00 28 00 01 00 00 40 06 7C CD 7F 00 00 01 E..(....@.|.....
0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........
0020 50 02 20 00 91 7C 00 00 P. ..|..
In fact, using raw() rather than show2() or any other method is not a random choice as all the functions
building the packet calls Packet.__str__() (or Packet.__bytes__() under Python 3). However,
__str__() calls another method: build():
def __str__(self):
return next(iter(self)).build()
What is important also to understand is that usually, you do not care about the machine representation,
that is why the human and internal representations are here.
So, the core method is build() (the code has been shortened to keep only the relevant parts):
def build(self,internal=0):
pkt = self.do_build()
pay = self.build_payload()
p = self.post_build(pkt,pay)
if not internal:
pkt = self
while pkt.haslayer(Padding):
pkt = pkt.getlayer(Padding)
p += pkt.load
pkt = pkt.payload
return p
So, it starts by building the current layer, then the payload, and post_build() is called to update some
late evaluated fields (like checksums). Last, the padding is added to the end of the packet.
Of course, building a layer is the same as building each of its fields, and that is exactly what do_build()
does.
def do_build(self):
p=""
for f in self.fields_desc:
p = f.addfield(self, p, self.getfieldval(f))
return p
The core function to build a field is addfield(). It takes the internal view of the field and put it at
the end of p. Usually, this method calls i2m() and returns something like p.self.i2m(val) (where
val=self.getfieldval(f)).
If val is set, then i2m() is just a matter of formatting the value the way it must be. For instance, if a
byte is expected, struct.pack("B", val) is the right way to convert it.
However, things are more complicated if val is not set, it means no default value was provided earlier,
and thus the field needs to compute some “stuff” right now or later.
“Right now” means thanks to i2m(), if all pieces of information are available. For instance, if you have
to handle a length until a certain delimiter.
Ex: counting the length until a delimiter
class XNumberField(FieldLenField):
In this example, in i2m(), if x has already a value, it is converted to its hexadecimal value. If no value
is given, a length of “0” is returned.
The glue is provided by Packet.do_build() which calls Field.addfield() for each field in the
layer, which in turn calls Field.i2m(): the layer is built IF a value was available.
class Foo(Packet):
fields_desc = [
ByteField("type", 0),
XNumberField("len", None, "\r\n"),
StrFixedLenField("sep", "\r\n", 2)
]
When post_build() is called, p is the current layer, pay the payload, that is what has already been built.
We want our length to be the full length of the data put after the separator, so we add its computation in
post_build().
>>> p = Foo()/("X"*32)
>>> p.show2()
###[ Foo ]###
type= 0
len= 32
sep= '\r\n'
###[ Raw ]###
load= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
>>> hexdump(raw(p))
0000 00 32 30 0D 0A 58 58 58 58 58 58 58 58 58 58 58 .20..XXXXXXXXXXX
0010 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX
0020 58 58 58 58 58 XXXXX
class Bar1(Packet):
fields_desc = [
IntField("val", 0),
]
class Bar2(Packet):
fields_desc = [
IPField("addr", "127.0.0.1")
]
If we use these classes with nothing else, we will have trouble when dissecting the packets as nothing
binds Foo layer with the multiple Bar* even when we explicitly build the packet through the call to
show2():
>>> p = Foo()/Bar1(val=1337)
>>> p
<Foo |<Bar1 val=1337 |>>
>>> p.show2()
###[ Foo ]###
type= 0
len= 4
(continues on next page)
Problems:
1. type is still equal to 0 while we wanted it to be automatically set to 1. We could of course have
built p with p = Foo(type=1)/Bar0(val=1337) but this is not very convenient.
2. the packet is badly dissected as Bar1 is regarded as Raw. This is because no links have been set
between Foo() and Bar*().
In order to understand what we should have done to obtain the proper behavior, we must look at how
the layers are assembled. When two independent packets instances Foo() and Bar1(val=1337) are
compounded with the ‘/’ operator, it results in a new packet where the two previous instances are cloned
(i.e. are now two distinct objects structurally different, but holding the same values):
The right-hand side of the operator becomes the payload of the left-hand side. This is performed through
the call to add_payload(). Finally, the new packet is returned.
Note: we can observe that if other isn’t a Packet but a string, the Raw class is instantiated to form the
payload. Like in this example:
>>> IP()/"AAAA"
<IP |<Raw load='AAAA' |>>
Well, what add_payload() should implement? Just a link between two packets? Not only, in our case,
this method will appropriately set the correct value to type.
Instinctively we feel that the upper layer (the right of ‘/’) can gather the values to set the fields to the lower
layer (the left of ‘/’). Like previously explained, there is a convenient mechanism to specify the bindings
in both directions between two neighboring layers.
Once again, these information must be provided to bind_layers(), which will internally call
bind_top_down() in charge to aggregate the fields to overload. In our case what we need to specify is:
Then, add_payload() iterates over the overload_fields of the upper packet (the payload), get the
fields associated to the lower packet (by its type) and insert them in overloaded_fields.
For now, when the value of this field will be requested, getfieldval() will return the value inserted in
overloaded_fields.
The fields are dispatched between three dictionaries:
• fields: fields whose the value have been explicitly set, like pdst in TCP (pdst='42')
• overloaded_fields: overloaded fields
• default_fields: all the fields with their default value (these fields
are initialized according to fields_desc by the constructor by calling init_fields() ).
In the following code, we can observe how a field is selected and its value returned:
Fields inserted in fields have the higher priority, then overloaded_fields, then finally
default_fields. Hence, if the field type is set in overloaded_fields, its value will be returned
instead of the value contained in default_fields.
We are now able to understand all the magic behind it!
>>> p = Foo()/Bar1(val=0x1337)
>>> p
<Foo type=1 |<Bar1 val=4919 |>>
>>> p.show()
###[ Foo ]###
type= 1
len= 4
sep= '\r\n'
###[ Bar1 ]###
val= 4919
Our 2 problems have been solved without us doing much: so good to be lazy :)
>>> hexdump(raw(p))
Packet.str=Foo
Packet.iter=Foo
Packet.iter=Bar1
Packet.build=Foo
Packet.build=Bar1
Packet.post_build=Bar1
Packet.post_build=Foo
As you can see, it first runs through the list of each field, and then build them starting from the beginning.
Once all layers have been built, it then calls post_build() starting from the end.
7.5 Fields
Here’s a list of fields that Scapy supports out of the box:
ByteField
XByteField
ShortField
SignedShortField
LEShortField
XShortField
IntField
SignedIntField
LEIntField
LESignedIntField
XIntField
LongField
SignedLongField
LELongField
LESignedLongField
XLongField
LELongField
IEEEFloatField
IEEEDoubleField
BCDFloatField # binary coded decimal
BitField
XBitField
7.5.2 Enumerations
Possible field values are taken from a given enumeration (list, dictionary, . . . ) e.g.:
ByteEnumField("code", 4, {1:"REQUEST",2:"RESPONSE",3:"SUCCESS",4:"FAILURE"})
7.5.3 Strings
StrField(name, default, fmt="H", remain=0, shift=0)
StrLenField(name, default, fld=None, length_from=None, shift=0):
StrFixedLenField
StrNullField
StrStopField
# A list assembled and dissected with many times the same field type
Problems arise when you realize that the relation between lenfield and varfield is not always straightfor-
ward. Sometimes, lenfield indicates a length in bytes, sometimes a number of objects. Sometimes the
length includes the header part, so that you must subtract the fixed header length to deduce the varfield
length. Sometimes the length is not counted in bytes but in 16bits words. Sometimes the same lenfield is
used by two different varfields. Sometimes the same varfield is referenced by two lenfields, one in bytes
one in 16bits words.
First, a lenfield is declared using FieldLenField (or a derivate). If its value is None when assembling a
packet, its value will be deduced from the varfield that was referenced. The reference is done using either
the length_of parameter or the count_of parameter. The count_of parameter has a meaning only
when varfield is a field that holds a list (PacketListField or FieldListField). The value will be
the name of the varfield, as a string. According to which parameter is used the i2len() or i2count()
method will be called on the varfield value. The returned value will the be adjusted by the function
provided in the adjust parameter. adjust will be applied to 2 arguments: the packet instance and the value
returned by i2len() or i2count(). By default, adjust does nothing:
adjust=lambda pkt,x: x
or
For the PacketListField and FieldListField and their derivatives, they work as above when they
need a length. If they need a number of elements, the length_from parameter must be ignored and the
count_from parameter must be used instead. For instance:
Examples
class TestSLF(Packet):
fields_desc=[ FieldLenField("len", None, length_of="data"),
StrLenField("data", "", length_from=lambda pkt:pkt.len) ]
class TestPLF(Packet):
fields_desc=[ FieldLenField("len", None, count_of="plist"),
PacketListField("plist", None, IP, count_from=lambda␣
˓→pkt:pkt.len) ]
class TestFLF(Packet):
fields_desc=[
FieldLenField("the_lenfield", None, count_of="the_varfield"),
FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"),
count_from = lambda pkt: pkt.the_lenfield) ]
class TestPkt(Packet):
fields_desc = [ ByteField("f1",65),
ShortField("f2",0x4244) ]
def extract_padding(self, p):
return "", p
class TestPLF2(Packet):
fields_desc = [ FieldLenField("len1", None, count_of="plist",fmt="H",␣
˓→adjust=lambda pkt,x:x+2),
>>> TestFLF("\x00\x02ABCDEFGHIJKL")
<TestFLF the_lenfield=2 the_varfield=['65.66.67.68', '69.70.71.72'] |<Raw ␣
˓→load='IJKL' |>>
7.5.5 Special
Emph # Wrapper to emphasize field when printing, e.g. Emph(IPField("dst",
˓→"127.0.0.1")),
ActionField
ConditionalField(fld, cond)
# Wrapper to make field 'fld' only appear if
# function 'cond' evals to True, e.g.
# ConditionalField(XShortField("chksum",None),lambda pkt:pkt.
˓→chksumpresent==1)
# When hidden, it won't be built nor dissected and the stored value␣
˓→will be 'None'
(continues on next page)
BitExtendedField(extension_bit)
# Field with a variable number of bytes. Each byte is made of:
# - 7 bits of data
# - 1 extension bit:
# * 0 means that it is the last byte of the field ("stopping bit")
# * 1 means that there is another byte after this one ("forwarding␣
˓→bit")
7.5.6 TCP/IP
IPField
SourceIPField
IPoptionsField
TCPOptionsField
MACField
DestMACField(MACField)
SourceMACField(MACField)
ICMPTimeStampField
7.5.7 802.11
Dot11AddrMACField
Dot11Addr2MACField
Dot11Addr3MACField
Dot11Addr4MACField
Dot11SCField
7.5.8 DNS
DNSStrField
DNSRRCountField
DNSRRField
DNSQRField
7.5.9 ASN.1
ASN1F_element
ASN1F_field
ASN1F_INTEGER
ASN1F_enum_INTEGER
ASN1F_STRING
ASN1F_OID
ASN1F_SEQUENCE
ASN1F_SEQUENCE_OF
ASN1F_PACKET
ASN1F_CHOICE
# scapy.contrib.description = [...]
# scapy.contrib.status = [...]
# scapy.contrib.name = [...] (optional)
• If the contrib module does not contain any packets, and should not be indexed in explore(), then
you should instead set:
# scapy.contrib.status = skip
• A layer module must have a docstring, in which the first line shortly describes the module.
EIGHT
This section provides some examples that show how to benefit from Scapy functions in your own code.
assert(checksum_scapy == chksum)
119
Scapy Documentation, Release 2.6.1
NINE
LAYERS
ò Note
This document is under a Creative Commons Attribution - Non-Commercial - Share Alike 2.5 license.
9.1.1 Overview
ò Note
All automotive-related features work best on Linux systems. CANSockets and ISOTPSockets are
based on Linux kernel modules. The python-can project is used to support CAN and CANSockets on
a wider range of operating systems and CAN hardware interfaces.
Protocols
The following table should give a brief overview of all the automotive-related capabilities of Scapy. Most
application layer protocols have many specialized Packet classes. These special-purpose Packets are
not part of this overview. Use the explore() function to get all information about one specific protocol.
121
Scapy Documentation, Release 2.6.1
Physical Protocols
More than 20 different communication protocols exist for the vehicle’s internal wired communication.
Most vehicles make use of five to ten different protocols for their internal communication. The decision
which communication protocol is used from an Original Equipment Manufacturer (OEM) is usually
made by the trade-off between the costs for communication technology and the final car price. The four
major communication technologies for inter-ECU communication are Controller Area Network (CAN),
FlexRay, Local Interconnect Network (LIN), and Automotive Ethernet. For security considerations, these
are the most relevant protocols for wired communication in vehicles.
LIN
LIN is a single wire communication protocol for low data rates. Actuators and sensors of a vehicle
exchange information with an ECU, acting as a LIN master. Software updates over LIN are possible, but
the LIN slaves usually do not need software updates because of their limited functionality.
CAN
CAN is by far the most used communication technology for inter-ECU communication in vehicles. In
older or cheaper vehicles, CAN is still the primary protocol for a vehicle’s backbone communication.
Safety-critical communication during a vehicle’s operation, diagnostic information, and software updates
are transferred between ECUs over CAN. The lack of security features in the protocol itself, combined
with the general use, makes CAN the primary protocol for security investigations.
10
Nils Weiss. Security Testing in Safety-Critical Networks. PhD Study Report. http://www.kiv.zcu.cz/site/documents/
verejne/vyzkum/publikace/technicke-zpravy/2020/Rigo_Weiss_2020_2.pdf
FlexRay
The FlexRay consortium designed FlexRay as a successor of CAN. Modern vehicles have higher demands
on communication bandwidth. By design, FlexRay is a fast and reliable communication protocol for
inter-ECU communication. FlexRay components are more expensive than CAN components, leading to
a more selective use by OEMs.
Automotive Ethernet
Recent upper-class vehicles implement Automotive Ethernet, the new backbone technology for internal
vehicle communication. The rapidly grown bandwidth demands already replace FlexRay. The primary
reasons for these demands are driver-assistant and autonomous-driving features. Only the physical layer
(layer 1) of the Open Systems Interconnection (OSI) model distinguishes Ethernet (IEEE 802.3) from
Automotive Ethernet (BroadR-Reach). This design decision leads to multiple advantages. For example,
communication stacks of operating systems can be used without modification and routing, filtering, and
firewall systems. Automotive Ethernet components are already cheaper than FlexRay components, which
will lead to vehicle topologies, where CAN and Automotive Ethernet are the most used communication
protocols.
Topologies
Line-Bus
The first vehicles with CAN bus used a single network with a line-bus topology. Some lower-priced
vehicles still use one or two shared CAN bus networks for their internal communication nowadays. The
downside of this topology is its vulnerability and the lack of network separation. All ECUs of a vehicle
are connected on a shared bus. Since CAN does not support security features from its protocol definition,
any participant on this bus can communicate directly with all other participants, which allows an attacker
to affect all ECUs, even safety-critical ones, by compromising one single ECU. The overall security level
of this network is given from the security level of the weakest participant.
Central Gateway
The central Gateway (GW) topology can be found in higher-priced older cars and medium-priced to
lower-priced recent cars. A centralized GW ECU separates domain-specific sub-networks. This allows an
OEM to encapsulate all ECUs with remote attack surfaces in one sub-network. ECUs with safety-critical
functionalities are located in an individual CAN network. Next to CAN, FlexRay might also be used as
a communication protocol inside a separate network domain. The security of a safety-critical network in
this topology depends mainly on the central GW ECU’s security. This architecture increases the overall
security level of a vehicle through domain separation. After an attacker successfully exploited an ECU
through an arbitrary attack surface, a second exploitable vulnerability or a logical bug is necessary to
compromise a different domain, a safety-critical network, inside a vehicle. This second exploit or logical
bug is necessary to overcome the network separation of the central GW ECU.
A new topology with central GW and Domain Controllers (DCs) can be found in the latest higher-priced
vehicles. The increasing demand for bandwidth in modern vehicles with autonomous driving and driver
assistant features led to this topology. An Automotive Ethernet network is used as a communication
backbone for the entire vehicle. Individual domains, connected through a DC with the central GW, form
the vehicle’s backbone. The individual DCs can control and regulate the data communication between a
domain and the vehicle’s backbone. This topology achieves a very-high security level through a strong
network separation with individual DCs, acting as gateway and firewall, to the vehicle’s backbone net-
work. OEMs have the advantage of dynamic information routing next to this security improvement, an
enabler for Feature on Demand (FoD) services.
CAN
The CAN communication technology was invented in 1983 as a message-based robust vehicle bus com-
munication system. The Robert Bosch GmbH designed multiple communication features into the CAN
standard to achieve a robust and computation efficient protocol for controller area networks. Remark-
able for the communication behavior of CAN is the internal state machine for transmission errors. This
state machine implements a fail silent behavior to protect a safety-critical network from babbling idiot
nodes. If a specific limit of reception errors (REC) or transmission errors (TEC) occurred, the CAN
driver changes its state from error-active to error-passive and finally to bus-off.
Fig. 4: CAN bus states on transmission errors. Receive Error Counter (REC), Transmit Error Counter
(TEC)
In recent years, this protocol specification was abused for Denial of Service (DoS) attacks and informa-
tion gathering attacks on the CAN network of a vehicle. Cho et al. demonstrated a DoS attack against
CAN networks by abusing the bus-off state of ECUs1 . Injections of communication errors in CAN frames
of one specific node caused a high transmission error count in the node under attack, forcing the attacked
node to enter the bus-off state. In 2019 Kulandaivel et al. combined this attack with statistical analysis to
achieve a fast and inexpensive network mapping in vehicular networks2 . They combined statistical analy-
sis of the CAN network traffic before and after the bus-off attack was applied to a node. All missing CAN
frames in the network traffic after an ECU was attacked could now be mapped to the ECU under attack,
helping researchers identify the origin ECU of a CAN frame. Ken Tindell published a comprehensive
1
Kyong-Tak Cho and Kang G. Shin. Error handling of in-vehicle networks makes them vulnerable. In Proceedings of the
2016 ACM SIGSAC Conference on Computer and Communications Security, CCS ’16, page 1044–1055, New York, NY, USA,
2016. Association for Computing Machinery.
2
Sekar Kulandaivel, Tushar Goyal, Arnav Kumar Agrawal, and Vyas Sekar. Canvas: Fast and inexpensive automotive
network mapping. In 28th USENIX Security Symposium (USENIX Security 19), pages 389–405, Santa Clara, CA, August
2019. USENIX Association.
The above figure shows a CAN frame and its fields as it is transferred over the network. For information
exchange, only the fields arbitration, control, and data are relevant. These are the only fields to which
a usual application software has access. All other fields are evaluated on a hardware-layer and, in most
cases, are not forwarded to an application. The data field has a variable length and can hold up to eight
bytes. The length of the data field is specified by the data length code inside the control field. Impor-
tant variations of this example are CAN-frames with extended arbitration fields and the Controller Area
Network Flexible Data-Rate (CAN FD) protocol. On Linux, every received CAN frame is passed to
SocketCAN. SocketCAN allows the CAN handling via network sockets of the operating system. Socket-
CAN was created by Oliver Hartkopp and added to the Linux Kernel version 2.6.254 . Figure 2.7 shows
the frame structure, how CAN frames are encoded if a user-land application receives data from a CAN
socket.
The comparison of above figures clearly shows the loss of information during the CAN frame processing
from a physical layer driver. Almost every CAN driver acts in the same way, whether an application code
runs on a microcontroller or a Linux kernel. This also means that a standard application does not have
access to the Cyclic Redundancy Check (CRC) field, the acknowledgment bit, or the end-of-frame field.
Through the CAN communication in a vehicle or a separated domain, ECUs exchange sensor-data and
control inputs; this data is mainly not secured and can be modified by assailants. Attackers can easily
spoof sensor values on a CAN bus to trigger malicious reactions of other ECUs. Miller and Valasek
described this spoofing attack during their studies on automotive networks5 . To prevent attacks on safety-
critical data transferred over CAN, Automotive Open System Architecture (AUTOSAR) released a secure
onboard communication specification6 .
3
Ken Tindell. CAN Bus Security - Attacks on CAN bus and their mitigations, 2019. https://canislabs.com/wp-content/
uploads/2020/12/2020-02-14-White-Paper-CAN-Security.pdf
9
Pico Technology Ltd. Complete CAN data frame structure, 2020 (accessed February 14, 2020). https://www.picotech.
com/images/uploads/library/topics/_med/CAN-full-frame.jpg
4
Oliver Hartkopp. Readme file for the Controller Area Network Protocol Family (aka SocketCAN), 2020 (accessed January
29, 2020). https://www.kernel.org/doc/Documentation/networking/can.txt
5
Dr. Charlie Miller and Chris Valasek. Adventures in Automotive Networks and Control Units. DEF CON 21 Hacking
Conference. Las Vegas, NV: DEF CON, August 2013. http://illmatics.com/car_hacking.pdf (accessed 2020-05-27)
6
AUTOSAR. Specification of Secure Onboard Communication, 2020 (accessed January 31, 2020). https://www.autosar.
The CAN protocol supports only eight bytes of data. Use-cases like diagnostic operations or ECU pro-
gramming require much higher payloads than the CAN protocol supports. For these purposes, the au-
tomotive industry standardized the Transport Layer (ISO-TP) (ISO 15765-2) protocol7 . ISO-TP is a
transportation layer protocol on top of CAN. Payloads with up to 4095 bytes can be transferred between
ISO-TP endpoints fragmented in CAN frames. The ISO-TP protocol handling requires four special frame
types.
The different types of ISO-TP frames are shown in the following figure. The payload of a CAN frame
gets replaced by one of the four ISO-TP frames. The individual ISO-TP frames have different purposes.
A single frame can transfer between 1 and 7 bytes of ISO-TP message data. The len field of a Single
Frame or a First Frame indicates the ISO-TP message length. Every message with more than 7 bytes
of payload data must be fragmented into a First Frame, followed by multiple Consecutive Frames. This
communication is illustrated in the above figure. After the First Frame is sent from a sender, the receiver
has to communicate its reception capabilities through a Flow Control Frame to the sender. Only after this
Flow Control Frame is received, the sender is allowed to communicate the Consecutive Frames according
to the receiver’s capabilities.
ISO-TP acts as a transport protocol with the support of directed communication through addressing
mechanisms. In vehicles, ISO-TP is mainly used as a transport protocol for diagnostic communication.
In rare cases, ISO-TP is also used to exchange larger data between ECUs of a vehicle. Security measures
have to be applied to the application layer protocol transported through ISO-TP since ISO-TP has no
capabilities to secure its transported data.
org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_SecureOnboardCommunication.pdf
7
ISO Central Secretary. Road vehicles – Diagnostic communication over Controller Area Network (DoCAN) – Part 2:
Transport protocol and network layer services. Standard ISO 15765-2:2016, International Organization for Standardization,
Geneva, CH, 2016.
DoIP
Diagnostic over IP (DoIP) was first implemented on automotive networks with a centralized gateway
topology. A centralized GW functions as a DoIP endpoint that routes diagnostic messages to the desired
network, allowing manufacturers to program or diagnose multiple ECUs in parallel. Since the Internet
Protocol (IP) communication between a repair-shop tester and the GW is many times faster than the
communication between the GW ECU and a target ECU connected over CAN, the remaining bandwidth
of the IP communication can be used to start further DoIP connections to other ECUs in different CAN
domains. DoIP is specified as part of AUTOSAR and in ISO 13400-2. Similar to ISO-TP, DoIP does
not specify special security measures. The responsibility regarding secured communication is delegated
to the application layer protocol.
Diagnostic Protocols
Two examples of diagnostic protocols are General Motor Local Area Network (GMLAN) and Unified
Diagnostic Service (UDS) (ISO 14229-2). The General Motors Cooperation uses GMLAN. German
OEMs mainly use UDS. Both protocols are very similar from a specification point of view, and both
protocols use either ISO-TP or DoIP messages for a directed communication with a target ECU. Since
different OEMs use UDS, every manufacturer adds its custom additions to the standard. Also, every
manufacturer uses individual ISO-TP addressing for the directed communication with an ECU. GMLAN
includes more precise definitions about ECU addressing and an ECUs internal behavior compared to
UDS.
UDS and GMLAN follow a tree-like message structure, where the first byte identifies the service. Every
service is answered by a response. Two types of responses are defined in the standard. Negative responses
are indicated through the service 0x7F. Positive responses are identified by the request service identifier
incremented with 0x40.
A clear separation between the transport and the application layer allows creating application layer tools
for both network stacks. The figure above provides an overview of relevant protocols and the corre-
sponding layers. UDS defines a clean separation between application and transport layer. On CAN based
networks, ISO-TP is used for this purpose. The CAN protocol can be treated as the network access
protocol. This allows to replace ISO-TP and CAN with DoIP or HSFZ and Ethernet. The GMLAN pro-
tocol combines transport and application layer specifications very similar to ISO-TP and UDS. Because
of that similarity, identical application layer-specific scan techniques can be applied. To overcome the
bandwidth limitations of CAN, the latest vehicle architectures use an Ethernet-based diagnostic protocol
(DoIP, HSFZ) to communicate with a central gateway ECU. The central gateway ECU routes application
layer packets from an Ethernet-based network to a CAN based vehicle internal network. In general, the
diagnostic functions of all ECUs in a vehicle can be accessed from the OBD connector over UDSonCAN
or UDSonIP.
SOME/IP
Scalable service-Oriented MiddlewarE over IP (SOME/IP) defines a new philosophy of data communi-
cation in automotive networks. SOME/IP is used to exchange data between network domain controllers
in the latest vehicle networks. SOME/IP supports subscription and notification mechanisms, allowing
domain controllers to dynamically subscribe to data provided by another domain controller dependent on
the vehicle’s state. SOME/IP transports data between domain controllers and the gateway that a vehicle
needs during its regular operation. The use-cases of SOME/IP are similar to the use-cases of CAN com-
munication. The main purpose is the information exchange of sensor and actuator data between ECUs.
This usage emphasizes SOME/IP communication as a rewarding target for cyber-attacks.
CCP/XCP
Universal Measurement and Calibration Protocol (XCP), the CAN Calibration Protocol (CCP) successor,
is a calibration protocol for automotive systems, standardized by ASAM e.V. in 2003. The primary usage
of XCP is during the testing and calibration phase of ECU or vehicle development. CCP is designed for
use on CAN. No message in CCP exceeds the 8-byte limitation of CAN. To overcome this restriction,
XCP was designed to aim for compatibility with a wide range of transport protocols. XCP can be used
on top of CAN, CAN FD, Serial Peripheral Interface (SPI), Ethernet, Universal Serial Bus (USB), and
FlexRay. The features of CCP and XCP are very similar; however, XCP has a larger functional scope and
optimizations for data efficiency.
Both protocols have a session-based communication procedure and support authentication through seed
and key mechanisms between a master and multiple slave nodes. A master node is typically an engineer-
ing Personal Computer (PC). In vehicles, slave nodes are ECUs for configuration. XCP also supports
simulation. A vehicle engineer can debug a MATLAB Simulink model through XCP. In this case, the
simulated model acts as the XCP slave node. CCP and XCP can read and write to the memory of an ECU.
Another main feature is data acquisition. Both protocols support a procedure that allows an engineer to
configure a so-called data acquisition list with memory addresses of interest. All memory specified in
such a list will be read periodically and be broadcast in a CCP or XCP Data Acquisition (DAQ) packet on
the chosen communication channel. The following figure gives an overview of all supported communi-
cation and packet types in XCP. In the Command Transfer Object (CTO) area, all communication follows
a request and response procedure always initiated by the XCP master. A Command Packet (CMD) can
receive a Command Response Packet (RES), an Error (ERR) packet, an Event Packet (EV), or a Service
Request Packet (SERV) as a response. After the configuration of a slave through CTO CMDs, a slave can
listen for Stimulation (STIM) packets and periodically send configured DAQ packets. The resources sec-
tion in the following figure indicates the possible attack surfaces of this protocol (Programming (PGM),
Calibration (CAL), DAQ, STIM) which an attacker could abuse. It is crucial for a vehicle’s security and
safety that such protocols, which have their use only during calibration and development of a vehicle, are
disabled or removed before a vehicle is shipped to a customer.
Fig. 10: XCP communication model between XCP Master and XCP Slave. This model shows the com-
munication direction for CTO/Data Transfer Object (DTO) packagesPage 132, 8 .
References
9.1.3 Layers
ò Note
ATTENTION: Animations below might be outdated.
CAN
How-To
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)
load_layer("can")
conf.contribs['CANSocket'] = {'use-python-can' : True}
load_contrib('cansocket')
socket.send(packet)
rx_packet = socket.recv()
socket.sr1(packet)
CAN Frame
>>> load_layer("can")
>>> frame.show()
###[ CAN ]###
flags= extended
identifier= 0x10010000
length= 8
reserved= 0
data= '\x01\x02\x03\x04\x05\x06\x07\x08'
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)
Additionally CAN Frames can be imported from candump output and log files. The CandumpReader
class can be used in the same way as a socket object. This allows you to use sniff and other functions
from Scapy:
In order to support the DBC file format, SignalFields and the SignalPacket classes were added to
Scapy. SignalFields should only be used inside a SignalPacket. Multiplexer fields (MUX) can be
created through ConditionalFields. The following example demonstrates the usage:
DBC Example:
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),
class testFrameFloat(SignalPacket):
fields_desc = [
LEFloatSignalField("floatSignal2", default=0, start=32),
BEFloatSignalField("floatSignal1", default=0, start=7)
]
pkt = SignalHeader()/testFrameFloat(floatSignal2=3.4)
dbc_sock.send(pkt)
This example uses the class SignalHeader as header. The payload is specified by individual
SignalPackets. bind_layers combines the header with the payload dependent on the CAN iden-
tifier. If you want to directly receive SignalPackets from your CANSocket, provide the parameter
basecls to the init function of your CANSocket.
Canmatrix supports the creation of Scapy files from DBC or AUTOSAR XML files https://github.com/
ebroecker/canmatrix
CANSockets
Linux SocketCAN
This subsection summarizes some basics about Linux SocketCAN. An excellent overview
from Oliver Hartkopp can be found here: https://wiki.automotivelinux.org/_media/agl-distro/
agl2017-socketcan-print.pdf
Linux SocketCAN supports virtual CAN interfaces. These interfaces are an easy way to do some first
steps on a CAN-Bus without the requirement of special hardware. Besides that, virtual CAN interfaces
are heavily used in Scapy unit tests for automotive-related contributions.
Virtual CAN sockets require a special Linux kernel module. The following shell command loads the
required module:
In order to use a virtual CAN interface some additional commands for setup are required. This snippet
chooses the name vcan0 for the virtual CAN interface. Any name can be chosen here:
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)
If it’s required, a CAN interface can be set into a listen-only or loopback mode with ip link set
commands:
Linux can-utils
As part of Linux SocketCAN, some very useful command line tools are provided from Oliver Hartkopp:
https://github.com/linux-can/can-utils
The following example shows the basic functions of Linux can-utils. These utilities are very handy for
quick checks, dumping, sending, or logging of CAN messages from the command line.
Scapy CANSocket
In Scapy, two kind of CANSockets are implemented. One implementation is called Native CANSocket,
the other implementation is called Python-can CANSocket.
Since Python 3 supports PF_CAN sockets, Native CANSockets can be used on a Linux based system with
Python 3 or higher. These sockets have a performance advantage because select is callable on them.
This has a big effect in MITM scenarios.
For compatibility reasons, Python-can CANSockets were added to Scapy. On Windows or OSX and
on all systems without Python 3, CAN buses can be accessed through python-can. python-can needs
to be installed on the system: https://github.com/hardbyte/python-can/ Python-can CANSockets are a
wrapper of python-can interface objects for Scapy. Both CANSockets provide the same API which makes
them exchangeable under most conditions. Nevertheless some unique behaviours of each CANSocket
type has to be respected. Some CAN-interfaces, like Vector hardware is only supported on Windows.
These interfaces can be used through Python-can CANSockets.
Native CANSocket
# Simple Socket
socket = CANSocket(channel="vcan0")
Creating a native CANSocket only listen for messages with Id >= 0x200 and Id <= 0x2ff:
Sniff on a CANSocket:
CANSocket python-can
python-can is required to use various CAN-interfaces on Windows, OSX or Linux. The python-can
library is used through a CANSocket object. To create a python-can CANSocket object, all parameters
of a python-can interface.Bus object has to be used for the initialization of the CANSocket.
Ways of creating a python-can CANSocket:
This example shows how to use bridge and sniff on virtual CAN interfaces. For real world applications,
use real CAN interfaces. Set up two vcans on Linux terminal:
Import modules:
import threading
load_contrib('cansocket')
load_layer("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()
Sniff packets:
packets = socket1.sniff(timeout=0.3)
socket0.close()
socket1.close()
load_contrib('automotive.ccp')
CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02)
(continues on next page)
If we aren’t interested in the DTO of an Ecu, we can just send a CRO message like this: Sending a CRO
message:
pkt = CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02)
sock = CANSocket(bustype='socketcan', channel='vcan0')
sock.send(pkt)
If we are interested in the DTO of an Ecu, we need to set the basecls parameter of the CANSocket to
CCP and we need to use sr1: Sending a CRO message:
cro = CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\
˓→x11\x12")
Since sr1 calls the answers function, our payload of the DTO objects gets interpreted with the command
of our CRO object.
CTORequest() / Connect()
CTORequest() / GetDaqResolutionInfo()
CTORequest() / GetSeed(mode=0x01, resource=0x00)
sock.send(pkt)
If we are interested in the response of an Ecu, we need to set the basecls parameter of the CANSocket to
XCPonCAN and we need to use sr1: Sending a CTO message:
Since sr1 calls the answers function, our payload of the XCP-response objects gets interpreted with the
command of our CTO object. Otherwise it could not be interpreted. The first message should always be
the “CONNECT” message, the response of the Ecu determines how the messages are read. E.g.: byte
order. Otherwise, one must set the address granularity, and max size of the DTOs and CTOs per hand in
the contrib config:
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
If you do not want this to be set after receiving the message you can also disable that feature:
conf.contribs['XCP']['allow_byte_order_change'] = False
conf.contribs['XCP']['allow_ag_change'] = False
conf.contribs['XCP']['allow_cto_and_dto_change'] = False
To send a pkt over TCP or UDP another header must be used. TCP:
UDP:
XCPScanner
The XCPScanner is a utility to find the CAN identifiers of ECUs that support XCP.
Commandline usage example:
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
(continues on next page)
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"
--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
The result includes the slave_id (the identifier of the Ecu that receives XCP messages), and the re-
sponse_id (the identifier that the Ecu will send XCP messages to).
ISOTP
ISOTP message
load_contrib('isotp')
ISOTP(tx_id=0x241, rx_id=0x641, data=b"\x3eabc")
ISOTP Sockets
Scapy provides two kinds of ISOTP-Sockets. One implementation, the ISOTPNativeSocket is using the
Linux kernel module from Hartkopp. The other implementation, the ISOTPSoftSocket is completely
implemented in Python. This implementation can be used on Linux, Windows, and OSX.
An ISOTPSocket will not respect tx_id, rx_id, rx_ext_address, ext_address of an ISOTP
message object.
System compatibilities
ISOTPNativeSocket
Requires:
• Python3
• Linux
• Hartkopp’s Linux kernel module: https://github.com/hartkopp/can-isotp.git (merged
into mainline Linux in 5.10)
During pentests, the ISOTPNativeSockets has a better performance and reliability, usually. If you are
working on Linux, consider this implementation:
Since this implementation is using a standard Linux socket, all Scapy functions like sniff, sr, sr1,
bridge_and_sniff work out of the box.
ISOTPSoftSocket
ISOTPSoftSockets can use any CANSocket. This gives the flexibility to use all python-can interfaces.
Additionally, these sockets work on Python2 and Python3. Usage on Linux with native CANSockets:
sock.send(...)
Import modules:
import threading
load_contrib('cansocket')
conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': True}
load_contrib('isotp')
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)
Start threads:
threadBridge.start()
threadSender.start()
Sniff on vcan1:
receive = isoTpSocketVCan1.sniff(timeout=1)
Close sockets:
isoTpSocketVCan0.close()
isoTpSocketVCan1.close()
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]
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)
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 --
(continues on next page)
Python3 on Linux:
python3 -m scapy.tools.automotive.isotpscanner --channel can0 --start 0 --
˓→end 100
>>> socks
[<<ISOTPNativeSocket: read/write packets at a given CAN interface using CAN_
˓→ISOTP socket > at 0x7f98e27c8210>,
UDS
The main usage of UDS is flashing and diagnostic of an Ecu. UDS is an application layer protocol and
can be used as a DoIP or HSFZ payload or a UDS packet can directly be sent over an ISOTPSocket.
Every OEM has its own customization of UDS. This increases the difficulty of generic applications and
OEM specific knowledge is required for penetration tests. RoutineControl jobs and ReadDataByIdenti-
fier/WriteDataByIdentifier services are heavily customized.
Use the argument basecls=UDS on the init function of an ISOTPSocket.
Here are two usage examples:
In real-world use-cases, the UDS layer is heavily customized. OEMs define their own substructure of
packets. Especially the packets ReadDataByIdentifier or WriteDataByIdentifier have a very OEM or even
Ecu specific substructure. Therefore a StrField dataRecord is not added to the field_desc. The
intended usage is to create Ecu or OEM specific description files, which extend the general UDS layer of
Scapy with further protocol implementations.
Customization example:
cat scapy/contrib/automotive/OEM-XYZ/car-model-xyz.py
#! /usr/bin/env python
class DBI_IP(Packet):
name = 'DataByIdentifier_IP_Packet'
fields_desc = [
ByteField('ADDRESS_FORMAT_ID', 0),
IPField('IP', ''),
IPField('SUBNETMASK', ''),
IPField('DEFAULT_GATEWAY', '')
]
UDS_RDBI.dataIdentifiers[0x172b] = 'GatewayIP'
If one wants to work with this custom additions, these can be loaded at runtime to the Scapy interpreter:
>>> load_contrib('automotive.uds')
>>> load_contrib('automotive.OEM-XYZ.car-model-xyz')
>>> pkt.show()
###[ UDS ]###
service= WriteDataByIdentifier
###[ WriteDataByIdentifier ]###
dataIdentifier= GatewayIP
dataRecord= 0
###[ DataByIdentifier_IP_Packet ]###
ADDRESS_FORMAT_ID= 0
IP= 192.168.2.1
(continues on next page)
>>> hexdump(pkt)
0000 2E 17 2B 00 C0 A8 02 01 FF FF FF 00 C0 A8 02 01 ..+.............
GMLAN
GMLAN is very similar to UDS. It’s GMs application layer protocol for flashing, calibration and diag-
nostic of their cars. Use the argument basecls=GMLAN on the init function of an ISOTPSocket.
Usage example:
This example shows the logging mechanism of an Ecu object. The log of an Ecu is a dictionary of applied
UDS commands. The key for this dictionary is the UDS service name. The value consists of a list of
tuples, containing a timestamp and a log value
Usage example:
This example shows the trace mechanism of an Ecu object. Traces of the current state of the Ecu object
and the received message are printed on stdout. Some messages, depending on the protocol, will change
the internal state of the Ecu.
Usage example:
This example shows a mechanism to clone a real world Ecu by analyzing a list of Packets.
Usage example:
This example shows how to load UDS messages from a .pcap file containing CAN messages. A
PcapReader object is used as socket and an ISOTPSession parses CAN frames to ISOTP frames which
are then casted to UDS objects through the basecls parameter
Usage example:
ecu = Ecu()
ecu.update(udsmsgs)
print(ecu.log)
print(ecu.supported_responses)
assert len(ecu.log["TransferData"]) == 2
This example shows the usage of an EcuSession in sniff. An ISOTPSocket or any socket like object which
returns entire messages of the right protocol can be used. An EcuSession is used as supersession in
an ISOTPSession. To obtain the Ecu object from an EcuSession, the EcuSession has to be created
outside of sniff.
Usage example:
session = EcuSession()
ecu = session.ecu
print(ecu.log)
print(ecu.supported_responses)
This example shows a SOME/IP message which requests a service 0x1234 with the method 0x421. Dif-
ferent types of SOME/IP messages follow the same procedure and their specifications can be seen here
http://www.some-ip.com/papers/cache/AUTOSAR_TR_SomeIpExample_4.2.1.pdf.
load_contrib('automotive.someip')
u = UDP(sport=30509, dport=30509)
Create IP package:
i = IP(src="192.168.0.13", dst="192.168.0.10")
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)
In this example a SOME/IP SD offer service message is shown with an IPv4 endpoint.
Different entries and options basically follow the same procedure as shown here and can
be seen at https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/
AUTOSAR_SWS_ServiceDiscovery.pdf.
Load the contribution:
load_contrib('automotive.someip')
u = UDP(sport=30490, dport=30490)
The UDP port must be the one which was chosen for the SOME/IP SD transmission.
Create IP package:
i = IP(src="192.168.0.13", dst="224.224.224.245")
The IP source must be from the service and the destination address needs to be the chosen multicast
address.
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 defines the protocol for the communication with the endpoint, UDP in this case.
Create the SD package and put in the inputs:
sd = SD()
sd.set_entryArray(ea)
sd.set_optionArray(oa)
p = i/u/sd
send(p)
OBD
OBD is implemented on top of ISOTP. Use an ISOTPSocket for the communication with an Ecu. You
should set the parameters basecls=OBD and padding=True in your ISOTPSocket init call.
OBD is split into different service groups. Here are some example requests:
Request supported PIDs of service 0x01:
req = OBD()/OBD_S01(pid=[0x00])
The response will contain a PacketListField, called data_records. This field contains the actual response:
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 ]###
(continues on next page)
Let’s assume our Ecu under test supports the 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 %
The different services in OBD support different kinds of data. Service 01 and Service 02 support Param-
eter Identifiers (pid). Service 03, 07 and 0A support Diagnostic Trouble codes (dtc). Service 04 doesn’t
require a payload. Service 05 is not implemented on OBD over CAN. Service 06 supports Monitoring
Identifiers (mid). Service 08 supports Test Identifiers (tid). Service 09 supports Information Identifiers
(iid).
Examples:
req = OBD()/OBD_S09(iid=[0x00])
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']
Generation
Truncation
• MAC and freshness value are transferred in truncated format to save bandwidth
Verification
Profiles
AutoSAR specifies three profiles for truncated freshness value and MAC sizes. All use CMAC with
AES128:
• Profile 1 (24Bit-CMAC-8Bit-FV) - Algorithm: CMAC/AES-128 - Freshness value: 8 bits - MAC:
24 bits
• Profile 2 (24Bit-CMAC-No-FV) - Algorithm: CMAC/AES-128 - Freshness value: 0 bits - MAC:
24 bits - No freshness values used
• Profile 3 (JASPAR) - Algorithm: CMAC/AES-128 - Freshness value: 64 bits - Truncated Fresh-
ness value: 4 bits - MAC: 28 bits
Freshness Value
Protects against replay attacks. AUTOSAR recommends a structure for the freshness value, commonly
distributed via authenticated PDUs.
Sync Message
Synchronizes the ‘Trip Counter’ and ‘Reset Counter’ across all ECUs to maintain a consistent freshness
value.
• Sync message sent when ‘Message Counter’ overflows
• Security recommendation: Use broadcast or multicast to prevent DoS attacks
SecOC in Scapy
Scapy supports the dissection, building, verification, and authentication of SecOC messages sent via
AUTOSAR PDUs or CANFD packets. The implementation is designed to be vendor-independent and
easily customizable, addressing common challenges such as handling freshness values and differentiating
between SecOC and non-SecOC PDUs.
Customization
Scapy SecOC Packets provide three stub functions that need to be customized to handle SecOC properly:
class My_SecOC_CANFD(SecOC_CANFD):
"""
freshness_value = trip_count + reset_counter + message_count + self.
˓→tfv
return bytes(freshness_value)
Preparation
To properly dissect SecOC and non-SecOC AUTOSAR PDUs or CANFD frames, SecOC PDUs need
to be registered. This registration informs the dissector whether to use SecOC variants or non-SecOC
variants of the packet for dissection.
My_SecOC_CANFD.register_secoc_protected_pdu(pdu_id=0x123)
The above code registers the PDU with identifier 0x123 as a SecOC_CANFD packet. All other packets
will be interpreted as regular CANFD packets.
Once you have obtained a SecOC packet from a socket or a PCAP file, you can use the SecOC-related
functions to handle authentication and verification.
pkt.secoc_authenticate()
To begin, we need to power cycle our simulated ECU by creating a simple automaton with two states:
ON and OFF. Before building the actual ECU automaton, we require a power supply interface.
Power Supply
The power supply object serves as the interface to power cycle our ECU automaton. It enables communi-
cation between the automaton and the power supply to accurately simulate the ECU’s power consumption.
For multiprocessing support, file descriptors and multiprocessing Values are used. Here’s how to set it
up:
import logging
import sys
from multiprocessing import Value, Pipe
from multiprocessing.sharedctypes import Synchronized
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
class AutomatonPowerSupply():
def __init__(self) -> None:
super().__init__()
self.logger = logging.getLogger("AutomatonPowerSupply")
self.logger.info("Init done")
self.voltage_on: Synchronized[int] = Value("i", 0)
self.current_noise: Synchronized[int] = Value("i", 0)
self.current_on: Synchronized[int] = Value("i", 0)
self.delay_off = 0.001
self.delay_on = 0.001
self.read_pipe, self.write_pipe = Pipe()
self.closed = False
This code establishes the power supply, enabling it to control the power state of the ECU automaton. The
on, off, and reset methods manage state transitions, while Pipe and Value ensure inter-process commu-
nication and synchronization. This setup guarantees accurate modeling and control of the ECU’s power
consumption within a multiprocessing environment.
ECU Automaton
Now that we have a power supply, we can start modeling our ECU automaton, which can be turned on
and off.
class EcuAutomaton(Automaton):
def __init__(self, *args: Any, power_supply: AutomatonPowerSupply,␣
˓→**kargs: Any) -> None:
self.power_supply = power_supply
super().__init__(*args,
external_fd={"power_supply_fd": self.power_supply.
˓→read_pipe.fileno()},
**kargs)
This code defines an EcuAutomaton class that models an ECU with two states: ON and OFF. It uses
Scapy’s automaton framework to handle the state transitions based on the power supply’s status. The
event_voltage_changed_on and event_voltage_changed_off methods listen for voltage changes to switch
states, while action_consumption_on and action_consumption_off manage the power consumption be-
havior. This setup allows for a robust simulation of an ECU’s power cycling behavior.
Let’s give it a shot:
import threading
import time
from scapy.contrib.cansocket import NativeCANSocket
from scapy.error import log_runtime
ps = AutomatonPowerSupply()
cs = NativeCANSocket("vcan0")
automaton = EcuAutomaton(debug=1, power_supply=ps, sock=cs)
automaton.runbg()
ps.on()
time.sleep(0.1)
print(f"Current consumption {ps.current_on.value}")
ps.off()
time.sleep(0.1)
print(f"Current consumption {ps.current_on.value}")
automaton.stop()
This code sets up and tests our ECU automaton. We import the necessary modules and initialize the
power supply and CAN socket. We then create an instance of EcuAutomaton with debugging enabled,
and run it in the background.
We power on the ECU and wait a bit to let it stabilize. Then, we print the current consumption, turn off
the power, wait again, and print the current consumption once more. Finally, we stop the automaton.
By running this code, you should see the current consumption values change as the ECU powers on and
off, demonstrating our automaton in action.
Simulating UDS
Next up, we want to communicate with our automaton over UDS (Unified Diagnostic Services), aiming
to implement complex state machines like Security Access. Let’s start with a simpler example. The
following function allows us to receive and send packets from the automaton’s socket, as provided in the
init function.
class EcuAutomaton(Automaton):
case 0x3E, _, _:
return UDS() / UDS_NR(requestServiceId=service,
negativeResponseCode=
˓→"incorrectMessageLengthOrInvalidFormat")
case 0x27, _, _:
return UDS() / UDS_NR(requestServiceId=service,
negativeResponseCode=
˓→"incorrectMessageLengthOrInvalidFormat")
case _:
return UDS() / UDS_NR(requestServiceId=service,␣
˓→negativeResponseCode="serviceNotSupported")
By using Python’s match-case operator, we can craft a very elegant UDS answering machine. ECUs are
usually precise with their negative response codes, and modeling this becomes straightforward with the
match operator. For instance, consider the TesterPresent case. If we receive the correct service, length,
and sub-function, we respond positively. If the sub-function is anything else, we fall through to the nega-
tive response case “subFunctionNotSupported”. If the length is incorrect, we return “incorrectMessage-
LengthOrInvalidFormat”. Finally, if the service is unknown, the function returns “serviceNotSupported”.
This approach allows us to handle UDS communication effectively and implement the necessary logic
for our ECU automaton.
Full example:
class EcuAutomaton(Automaton):
def __init__(self, *args: Any, power_supply: AutomatonPowerSupply,␣
˓→**kargs: Any) -> None:
self.power_supply = power_supply
super().__init__(*args,
external_fd={"power_supply_fd": self.power_supply.
˓→read_pipe.fileno()},
**kargs)
Test-Setup Tutorials
ISO-TP Kernel Module Installation
A Linux ISO-TP kernel module can be downloaded from this website: https://github.com/
hartkopp/can-isotp.git. The file README.isotp in this repository provides all information and
necessary steps for downloading and building this kernel module. The ISO-TP kernel module should
also be added to the /etc/modules file, to load this module automatically at system boot.
CAN-Interface Setup
As the final step to prepare CAN interfaces for usage, these interfaces have to be set up through some
terminal commands. The bitrate can be chosen to fit the bitrate of a CAN bus under test.
How-To:
To build a small test environment in which you can send SOME/IP messages to and from server instances
or disguise yourself as a server, one Raspberry Pi, your laptop and the vsomeip library are sufficient.
1. Download image
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. Make applications
Write some small applications which function as either a service or a client and use the Scapy
SOME/IP implementation to communicate with the client or the server. Examples for vsomeip
applications are available on the vsomeip github wiki page (https://github.com/GENIVI/
vsomeip/wiki/vsomeip-in-10-minutes).
Cannelloni Framework
The Cannelloni framework is a small application written in C++ to transfer CAN data over UDP. In this
way, a researcher can map the CAN communication of a remote device to its workstation, or even combine
multiple remote CAN devices on his machine. The framework can be downloaded from this website:
https://github.com/mguentner/cannelloni.git. The README.md file explains the installation
and usage in detail. Cannelloni needs virtual CAN interfaces on the operator’s machine. The next listing
shows the setup of virtual CAN interfaces.
How-To:
modprobe vcan
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
9.2 Bluetooth
ò Note
If you’re new to using Scapy, start with the usage documentation, which describes how to use Scapy
with Ethernet and IP.
. Warning
Scapy does not support Bluetooth interfaces on Windows.
Bluetooth on Linux
Linux’s Bluetooth stack is developed by the BlueZ project. The Linux kernel contains drivers to provide
access to Bluetooth interfaces using HCI, which are exposed through sockets with AF_BLUETOOTH.
BlueZ also provides a user-space companion to these kernel interfaces. The key components are:
bluetoothd
A daemon that provides access to Bluetooth devices over D-Bus.
bluetoothctl
An interactive command-line program which interfaces with the bluetoothd over D-Bus.
hcitool
A command-line program which interfaces directly with kernel interfaces.
Support for Classic Bluetooth in bluez is quite mature, however BLE is under active development.
ò Note
You must run these examples as root. These have only been tested on Linux, and require Scapy
v2.4.3 or later.
$ hcitool dev
Devices:
hci0 xx:xx:xx:xx:xx:xx
Unless your computer is doing something else with Bluetooth, you’ll probably get 0 packets at this point.
This is because sniff doesn’t actually enable any promiscuous mode on the device.
However, this is useful for some other commands that will be explained later on.
ò Note
This requires a Bluetooth 4.0 or later interface that supports BLE (Bluetooth Low Energy), either as a
dedicated LE (Low Energy) chipset or a dual-mode LE + BR/EDR chipset (such as an RTL8723BU).
These instructions only been tested on Linux, and require Scapy v2.4.3 or later. There are bugs in
earlier versions which decode packets incorrectly.
These examples presume you have already opened a HCI socket (as bt).
In the background, there are already HCI events waiting on the socket. You can grab these events with
sniff:
>>> # The lfilter will drop anything that's not an advertising report.
>>> adverts = bt.sniff(lfilter=lambda p: HCI_LE_Meta_Advertising_Reports in p)
(press ^C after a few seconds to stop...)
>>> adverts
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:101>
>>> bt.sr(
... HCI_Hdr()/
... HCI_Command_Hdr()/
... HCI_Cmd_LE_Set_Scan_Enable(
... enable=False))
Begin emission:
Finished sending 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
(<Results: TCP:0 UDP:0 ICMP:0 Other:1>, <Unanswered: TCP:0 UDP:0 ICMP:0␣
˓→Other:0>)
# Packet counters
devices_pkts = dict((k, len(v)) for k, v in devices.items())
(continues on next page)
# Get one packet for each device that broadcasted short UUID 0xfe50 (Google).
# Android devices broadcast this pretty much constantly.
google = {}
for mac, reports in devices.items():
for report in reports:
if (EIR_CompleteList16BitServiceUUIDs in report and
0xfe50 in report[EIR_CompleteList16BitServiceUUIDs].svc_uuids):
google[mac] = report
break
Setting up advertising
ò Note
Changing advertisements may not take effect until advertisements have first been stopped.
AltBeacon
AltBeacon is a proximity beacon protocol developed by Radius Networks. This example sets up a virtual
AltBeacon:
ab = AltBeacon(
id1='2f234454-cf6d-4a0f-adf2-f4911ba9ffa6',
id2=1,
id3=2,
tx_power=-59,
)
bt.sr(ab.build_set_advertising_data())
Once advertising has been started, the beacon may then be detected with Beacon Locator (Android).
ò Note
Beacon Locator v1.2.2 incorrectly reports the beacon as being an iBeacon, but the values are other-
wise correct.
Eddystone
Eddystone is a proximity beacon protocol developed by Google. This uses an Eddystone-specific service
data field.
This example sets up a virtual Eddystone URL beacon:
Once advertising has been started, the beacon may then be detected with Eddystone Validator or Beacon
Locator (Android):
iBeacon
iBeacon is a proximity beacon protocol developed by Apple, which uses their manufacturer-specific data
field. Apple/iBeacon framing (below) describes this in more detail.
This example sets up a virtual iBeacon:
# Beacon data consists of a UUID, and two 16-bit integers: "major" and
# "minor".
#
# iBeacon sits on top of Apple's BLE protocol.
p = Apple_BLE_Submessage()/IBeacon_Data(
uuid='fb0b57a2-8228-44cd-913a-94a122ba1206',
major=1, minor=2)
Once advertising has been started, the beacon may then be detected with Beacon Locator (Android):
Starting advertising
bt.sr(HCI_Hdr()/
HCI_Command_Hdr()/
HCI_Cmd_LE_Set_Advertise_Enable(enable=True))
Stopping advertising
bt.sr(HCI_Hdr()/
HCI_Command_Hdr()/
HCI_Cmd_LE_Set_Advertise_Enable(enable=False))
ò Note
This describes the wire format for Apple’s Bluetooth Low Energy advertisements, based on (limited)
publicly available information. It is not specific to using Bluetooth on Apple operating systems.
iBeacon is Apple’s proximity beacon protocol. Scapy includes a contrib module, ibeacon, for working
with Apple’s BLE broadcasts:
>>> load_contrib('ibeacon')
Setting up advertising for iBeacon (above) describes how to broadcast a simple beacon.
While this module is called ibeacon, Apple has other “submessages” which are also advertised within
their manufacturer-specific data field, including:
• AirDrop
• AirPlay
• AirPods
• Handoff
• Nearby
• Overflow area
For compatibility with these other broadcasts, Apple BLE frames in Scapy are layered on top of
Apple_BLE_Submessage and Apple_BLE_Frame:
• HCI_Cmd_LE_Set_Advertising_Data, HCI_LE_Meta_Advertising_Report,
BTLE_ADV_IND, BTLE_ADV_NONCONN_IND or BTLE_ADV_SCAN_IND contain one or more. . .
• EIR_Hdr, which may have a payload of one. . .
• EIR_Manufacturer_Specific_Data, which may have a payload of one. . .
• Apple_BLE_Frame, which contains one or more. . .
• Apple_BLE_Submessage, which contains a payload of one. . .
• Raw (if not supported), or IBeacon_Data.
This module only presently supports IBeacon_Data submessages. Other submessages are decoded as
Raw.
One might sometimes see multiple submessages in a single broadcast, such as Handoff and Nearby. This
is not mandatory – there are also Handoff-only and Nearby-only broadcasts.
Inspecting a raw BTLE advertisement frame from an Apple device:
p = BTLE(hex_bytes(
˓→'d6be898e4024320cfb574d5a02011a1aff4c000c0e009c6b8f40440f1583ec895148b410050318c0b525b8f7d
˓→'))
p.show()
>>> load_contrib("nrf_sniffer")
>>> load_extcap()
>>> conf.ifaces
Source Index Name Address
nrf_sniffer_ble 100 nRF Sniffer for Bluetooth LE /dev/ttyUSB0-None
[...]
>>> sniff(iface="/dev/ttyUSB0-None", prn=lambda x: x.summary())
NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND
NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND
NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_IND
NRFS2_PCAP / NRFS2_Packet / NRF2_Packet_Event / BTLE / BTLE_ADV / BTLE_ADV_
˓→NONCONN_IND
ò Note
DCE/RPC per DCE/RPC 1.1 with the [MS-RPCE] additions
Scapy provides support for dissecting and building Microsoft’s Windows DCE/RPC calls.
Dissecting
You can dissect a DCE/RPC packet like any other packet, by calling ThePacketClass(<bytes>). The
only difference is, as mentioned above, that there are extra ndr64 and ndrendian arguments.
ò Note
DCE/RPC is stateful, and requires the dissector to remember which interface is bound, how (negotia-
tion), etc. Scapy therefore provides a DceRpcSession session that remembers the context to properly
dissect requests and responses.
Here’s an example where a pcap (included in the test/pcaps folder) containing a [MS-NRPC] exchange
is dissected using Scapy:
>>> load_layer("msrpce")
>>> bind_layers(TCP, DceRpc, sport=40564) # the DCE/RPC port
>>> bind_layers(TCP, DceRpc, dport=40564)
>>> pkts = sniff(offline='dcerpc_msnrpc.pcapng.gz', session=DceRpcSession)
>>> pkts[6][DceRpc5].show()
###[ DCE/RPC v5 ]###
rpc_vers = 5 (connection-oriented)
rpc_vers_minor= 0
ptype = request
(continues on next page)
Scapy has opted to not abstract any of the NDR fields (see Design choices), allowing to keep access to all
lengths, offsets, counts, etc. . . This allows to put wrong length values anywhere to test implementations.
The catch is that accessing the value of a field is a bit tedious:
>>> pkts[6][DceRpc5].ComputerName.value[0].value
b'WIN1'
Sometimes, you’ll be glad to have access to the size of a ConformantArray. Most times, you won’t. All
NDRPacket therefore include a valueof() function that goes through any array or pointer containers:
>>> pkts[6][NetrServerReqChallenge_Request].valueof("ComputerName")
b'WIN1'
. Warning
Note that DceRpc5 packets are NOT NDRPacket, so you need to call valueof() on the NDR payload
itself.
Building
If you were to re-build the previous packet exactly as it was dissected, it would look something like this:
If you don’t care about specifying max_count, offset or actual_count manually, you can however
also do the following:
And Scapy will automatically add the NDRConformantArray, NDRVaryingArray. . . in the middle.
This applies to NDRPointers too ! Skipping it will add a default one with a referent id of 0x20000.
Take RPC_UNICODE_STRING for instance:
>>> RPC_UNICODE_STRING(Buffer=b"WIN").show2()
###[ RPC_UNICODE_STRING ]###
Length = 6
MaximumLength= 6
\Buffer \
|###[ NDRPointer ]###
| referent_id= 0x20000
| \value \
| |###[ NDRConformantArray ]###
| | max_count = 3
| | \value \
| | |###[ NDRVaryingArray ]###
(continues on next page)
9.3.2 Client
Scapy also includes a DCE/RPC client: DCERPC_Client.
It provides a bunch of basic DCE/RPC features:
• connect(): connect to a host
• bind(): bind to a DCE/RPC interface
• connect_and_bind(): connect to a host, use the endpoint mapper to find the interface then
reconnect to the host on the matching address
• sr1_req(): send/receive a DCE/RPC request
To be able to use an interface, it must have been imported. This makes it so that the
register_dcerpc_interface() function is called, allowing the DceRpcSession session to properly
understand the bind/alter requests, and match the DCE/RPCs by opcodes.
In the DCE/RPC world, there are several “Transports”. A transport corresponds to the various ways of
transporting DCE/RPC. You can have a look at the documentation over [MS-RPCE] 2.1. In Scapy, this
is implemented in the DCERPC_Transport enum, that currently contains:
• NCACN_IP_TCP: the interface is reached over IP/TCP, on a port that varies. This port can typically
be queried using the endpoint mapper, a DCE/RPC service that is always on port 135.
• NCACN_NP: the interface is reached over a named pipe over SMB. This named pipe is typically
well-known, or can also be queried using the endpoint mapper (over SMB) on certain cases.
Here’s an example sending a ServerAlive over the IObjectExporter interface from [MS-DCOM].
client = DCERPC_Client(
DCERPC_Transport.NCACN_IP_TCP,
ndr64=False,
)
client.connect("192.168.0.100")
client.bind(find_dcerpc_interface("IObjectExporter"))
req = ServerAlive_Request(ndr64=False)
resp = client.sr1_req(req)
resp.show()
Here’s the same example, but this time asking for PKT_PRIVACY (encryption) using NTLMSSP:
req = ServerAlive_Request(ndr64=False)
resp = client.sr1_req(req)
resp.show()
ssp = SPNEGOSSP(
[
KerberosSSP(
UPN="[email protected]",
PASSWORD="Password1",
SPN="host/dc1",
)
]
)
client = DCERPC_Client(
DCERPC_Transport.NCACN_IP_TCP,
auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY,
ssp=ssp,
ndr64=False,
)
client.connect("192.168.0.100")
client.bind(find_dcerpc_interface("IObjectExporter"))
req = ServerAlive_Request(ndr64=False)
resp = client.sr1_req(req)
resp.show()
Here’s a different example, this time connecting over NCACN_NP to [MS-SAMR] to enumerate the do-
mains a server is in:
ssp = NTLMSSP(
UPN="User",
HASHNT=MD4le("Password"),
)
client = DCERPC_Client(
DCERPC_Transport.NCACN_NP,
ssp=ssp,
ndr64=False,
)
client.connect("192.168.0.100")
client.open_smbpipe("lsass") # open the \pipe\lsass pipe
client.bind(find_dcerpc_interface("samr"))
client.close()
ò Note
As you can see, we used the NTLMSSP security provider in the above connection.
client = NetlogonClient(
auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY,
computername="SERVER",
domainname="DOMAIN",
)
client.connect_and_bind("192.168.0.100")
client.negotiate_sessionkey(bytes.fromhex("77777777777777777777777777777777"))
client.close()
9.3.3 Server
It is also possible to create your own DCE/RPC server. This takes the form of creating a DCERPC_Server
class, then serving it over a transport.
This class contains a answer() function that is used to register a handler for a Request, such as for
instance:
class MyRPCServer(DCERPC_Server):
@DCERPC_Server.answer(NetrWkstaGetInfo_Request)
def handle_NetrWkstaGetInfo(self, req):
"""
NetrWkstaGetInfo [MS-SRVS]
"returns information about the configuration of a workstation."
"""
return NetrWkstaGetInfo_Response(
WkstaInfo=NDRUnion(
tag=100,
value=LPWKSTA_INFO_100(
wki100_platform_id=500, # NT
wki100_ver_major=5,
),
),
ndr64=self.ndr64,
)
Let’s spawn this server, listening on the 12345 port using the NCACN_IP_TCP transport.
MyRPCServer.spawn(
DCERPC_Transport.NCACN_IP_TCP,
port=12345,
)
Of course that also works over NCACN_NP, with for instance a NTLMSSP:
MyRPCServer.spawn(
DCERPC_Transport.NCACN_NP,
ssp=ssp,
iface="eth0",
port=445,
ndr64=True,
)
To start an endpoint mapper (this should be a separate process from your RPC server), you can use the
default DCERPC_Server as such:
DCERPC_Server.spawn(
DCERPC_Transport.NCACN_IP_TCP,
iface="eth0",
port=135,
portmap={
find_dcerpc_interface("wkssvc"): 12345,
},
ndr64=True,
)
ò Note
Currently, a DCERPC_Server will let a client bind on all interfaces that Scapy has registered (im-
ported). Supposedly though, you know which RPCs are going to be queried.
# Sniff
pkts = sniff(offline="dcerpc_exchange.pcapng", session=TCPSession)
pkts.show()
. Warning
NTLM, KerberosSSP and SPNEGOSSP are currently supported. NetlogonSSP is still unsupported.
Those lengths are mostly computable, but this raises the question of: what should Scapy report to the
user?.
Some implementations (like impacket’s), have chosen to abstract the lengths, offsets, etc. and hide it to
the user. This has the big advantage that it makes packets much easier to build, but has the inconvenience
that it is in fact hiding part of the information contained in the packet, which really is against Scapy’s
philosophy.
The same happens when encoding pointers, which looks something like this:
where it is tempting to hide the referent_id part, which is on Windows in most parts irrelevant.
In Scapy, you will find all the fields. The pros are that it is exhaustive and doesn’t hide any information,
the cons is that you need to use the utils (valueof() on dissection, implicit any2i on build) in order for
it not to be a massive pain.
This supports:
• The .NET remote protocol: NRTP* classes
• The .NET Binary Formatter: NRBF* classes
For instance you can try to parse a .NET Remoting payload generated using ysoserial with the NRBF()
to analyse what it’s doing.
9.5 GSSAPI
Scapy provides access to various Security Providers following the GSSAPI model, but aiming at inter-
acting with the Windows world.
ò Note
The GSSAPI interfaces are based off the following documentations:
• GSSAPI: RFC4121 / RFC2743
• GSSAPI C bindings: RFC2744
9.5.1 Usage
The following SSPs are currently provided:
• NTLMSSP
• KerberosSSP
• SPNEGOSSP
• NetlogonSSP
Basically those are classes that implement two functions, trying to micmic the RFCs:
• GSS_Init_sec_context(): called by the client, passing it a Context and optionally a token
• GSS_Accept_sec_context(): called by the server, passing it a Context and optionally a token
They both return the updated Context, a token to optionally send to the server/client and a GSSAPI status
code.
ò Note
You can typically use it in SMB_Client, SMB_Server, DCERPC_Client or DCERPC_Server. Have
a look at SMB and DCE/RPC to get examples on how to use it.
Let’s implement our own client that uses one of those SSPs.
Client
First let’s create the SSP. We’ll take NTLMSSP as an example but the others would work just as well.
Let’s get the first token (in this case, the ntlm negotiate):
Send this token to the server, or use it as required, and get back the server’s token. You can then pass that
token as the second parameter of GSS_Init_sec_context(). To give an example, this is what is done
in the LDAP client:
If you want to use SPEGOSSP, you could wrap the SSP as so:
You can override the GSS-API req_flags when calling GSS_Init_sec_context(), using values from
GSS_C_FLAGS:
GSS_C_FLAGS.GSS_C_EXTENDED_ERROR_FLAG |
GSS_C_FLAGS.GSS_C_MUTUAL_FLAG |
GSS_C_FLAGS.GSS_C_CONF_FLAG # Asking for CONFIDENTIALITY
(continues on next page)
Server
Implementing a server is very similar to a client but you’d use GSS_Accept_sec_context() instead.
The client is properly authenticated when status is GSS_S_COMPLETE.
Let’s use NTLMSSP as an example of server-side SSP.
You’ll find other examples of how to instantiate a SSP in the docstrings of each SSP. See the list
9.6 HTTP
Scapy supports the sending / receiving of HTTP packets natively.
ò Note
Support for HTTP 1.X was added in 2.4.3, whereas HTTP 2.X was already in 2.4.0.
Once the first SYN/ACK is done, the connection is established. Scapy will send the HTTPRequest(),
and the host will answer with HTTP fragments. Scapy will ACK each of those, and recompile them using
TCPSession, like Wireshark does when it displays the answer frame.
>>> explore(scapy.layers.http)
Packets contained in scapy.layers.http:
Class |Name
------------|-------------
HTTP |HTTP 1
HTTPRequest |HTTP Request
HTTPResponse|HTTP Response
There are two frames available: HTTPRequest and HTTPResponse. The HTTP is only used during dis-
section, as a util to choose between the two. All common header fields should be supported.
• Default HTTPRequest:
>>> HTTPRequest().show()
###[ HTTP Request ]###
Method= 'GET'
Path= '/'
Http_Version= 'HTTP/1.1'
A_IM= None
(continues on next page)
• Default HTTPResponse:
>>> HTTPResponse().show()
###[ HTTP Response ]###
Http_Version= 'HTTP/1.1'
Status_Code= '200'
Reason_Phrase= 'OK'
Accept_Patch43= None
Accept_Ranges= None
[...]
You can use the following shorthand to do the same very basic feature: http_request(), usable as so:
load_layer("http")
http_request("www.google.com", "/") # first argument is Host, second is Path
Let’s do the same request, but this time to a server that requires NTLM authentication:
• HTTP_Server:
Start an unauthenticated HTTP server automaton:
class Custom_HTTP_Server(HTTP_Server):
def answer(self, pkt):
if pkt.Path == b"/":
return HTTPResponse() / (
"<!doctype html><html><body><h1>OK</h1></body></html>"
)
else:
return HTTPResponse(
Status_Code=b"404",
Reason_Phrase=b"Not Found",
) / (
"<!doctype html><html><body><h1>404 - Not Found</h1></body></
˓→html>"
server = HTTP_Server.spawn(
port=8080,
iface="eth0",
)
We could also have started the same server, but requiring NTLM authorization using:
server = HTTP_Server.spawn(
port=8080,
iface="eth0",
mech=HTTP_AUTH_MECHS.NTLM,
ssp=NTLMSSP(IDENTITIES={"user": MD4le("password")}),
)
Or basic auth:
server = HTTP_Server.spawn(
port=8080,
iface="eth0",
mech=HTTP_AUTH_MECHS.BASIC,
BASIC_IDENTITIES={"user": MD4le("password")},
(continues on next page)
• TCP_client.tcplink:
Send an HTTPRequest to www.secdev.org and write the result in a file:
load_layer("http")
req = HTTP()/HTTPRequest(
Accept_Encoding=b'gzip, deflate',
Cache_Control=b'no-cache',
Connection=b'keep-alive',
Host=b'www.secdev.org',
Pragma=b'no-cache'
)
a = TCP_client.tcplink(HTTP, "www.secdev.org", 80)
answer = a.sr1(req)
a.close()
with open("www.secdev.org.html", "wb") as file:
file.write(answer.load)
TCP_client.tcplink makes it feel like it only received one packet, but in reality it was recombined in
TCPSession. If you performed a plain sniff(), you would have seen those packets.
• sniff():
Dissect a pcap which contains a JPEG image that was sent over HTTP using chunks. This is able to
reconstruct all HTTP streams in parallel.
ò Note
The http_chunk.pcap.gz file is available in scapy/test/pcaps
load_layer("http")
pkts = sniff(offline="http_chunk.pcap.gz", session=TCPSession)
# a[29] is the HTTPResponse
with open("image.jpg", "wb") as file:
file.write(pkts[29].load)
9.7 Kerberos
ò Note
Kerberos per RFC4120 + RFC6113 (FAST)
9.7.1 High-Level
Kerberos client
Scapy includes a (tiny) kerberos client, that has basic functionalities such as:
AS-REQ
ò Note
Full doc at krb_as_req(). krb_as_req actually calls a Scapy automaton that has the following
behavior:
The result is a named tuple with both the full AP-REP and the decrypted session key:
>>> res.asrep.show()
###[ KRB_AS_REP ]###
pvno = 0x5 <ASN1_INTEGER[5]>
msgType = 'AS-REP' 0xb <ASN1_INTEGER[11]>
\padata \
|###[ PADATA ]###
| padataType= 'PA-ETYPE-INFO2' 0x13 <ASN1_INTEGER[19]>
| \padataValue\
| |###[ ETYPE_INFO2 ]###
| | \seq \
| | |###[ ETYPE_INFO_ENTRY2 ]###
| | | etype = 'AES-256' 0x12 <ASN1_INTEGER[18]>
| | | salt = <ASN1_GENERAL_STRING[b'DOMAIN.LOCALuser1']>
| | | s2kparams = None
crealm = <ASN1_GENERAL_STRING[b'DOMAIN.LOCAL']>
[...]
(continues on next page)
TGS-REQ
ò Note
Full doc at krb_tgs_req(). krb_tgs_req actually calls a Scapy automaton.
ò Note
There is also a krb_as_and_tgs() function that does an AS-REQ then a TGS-REQ:
>>> krb_as_and_tgs("[email protected]", "host/DC1", password="Password1")
Renew a ST:
ò Note
For some mysterious reason, this is rarely implemented in other tools.
KerberosSSP
For Kerberos, the Scapy SSP is implemented in KerberosSSP. You can typically use it in SMB_Client,
SMB_Server, DCERPC_Client or DCERPC_Server.
ò Note
Remember that you can wrap it in a SPNEGOSSP
Ticketer++
Scapy also implements a “ticketer++” module, named as a tribute to impacket’s, in order to manipulate
Kerberos tickets. Ticketer++ is easy to use programmatically, and allows you to manipulate the tickets
yourself. Scapy’s ticketer++ implements all fields from RFC4120, [MS-KILE] and [MS-PAC], meaning
you can edit ANY field in a ticket to your likings.
It even provides a GUI (not exactly necessary, but quite handy) that edits & rebuilds the Scapy ticket
packet.
Demo
Here’s a small demo of how this is usable with linux kerberos tools:
ò Note
We first added a realm DOMAIN.LOCAL with a kdc to /etc/krb5.conf
$ kinit [email protected]
Password for [email protected]:
$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: [email protected]
$ scapy
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.open_file("/tmp/krb5cc_1000")
>>> t.show()
Tickets:
1. [email protected] -> krbtgt/[email protected]
(continues on next page)
>>> t.resign_ticket(0)
>>> t.save()
>>> exit()
$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: [email protected]
Features
• Read/Edit/Write CCaches from other apps: Let’s assume you’ve acquired the KRBTGT of a
KDC, plus you’ve used kinit to get a ticket. This ticket was saved to a .ccache file, that we’ll
know try to open.
ò Note
You can get the demo ccache file using the following
cat <<EOF | base64 -d > krb.ccache
BQQADAABAAj/////AAAAAAAAAAEAAAABAAAADERPTUFJTi5MT0NBTAAAAA1BZG1pbmlzdHJhdG9y
AAAAAQAAAAEAAAAMRE9NQUlOLkxPQ0FMAAAADUFkbWluaXN0cmF0b3IAAAACAAAAAgAAAAxET01B
SU4uTE9DQUwAAAAGa3JidGd0AAAADERPTUFJTi5MT0NBTAASAAAAIItCJqGQhmy+NFrl5miCPt1T
WcsAvUeaZCi8j+sbpVdSYzMy+mMzMvpjM7+aYzSEdwBQ4QAAAAAAAAAAAAAAAARIYYIERDCCBECg
AwIBBaEOGwxET01BSU4uTE9DQUyiITAfoAMCAQKhGDAWGwZrcmJ0Z3QbDERPTUFJTi5MT0NBTKOC
BAQwggQAoAMCARKhAwIBAqKCA/IEggPuZiwq78yj+MeN444a8dY7GN4BHYZNm+wS88EeILC73Ebm
9cgxGzMbHMJ7Ixk+kPpHunqmpn+6WCah9HVOpQUO6rLgfQej7BApsqEeBYzjHkj03ivOAX6cKRXu
QP+g9xCVlwiChvopD+bKd3RlFixXV6Z8xTqOMgSEakypz/MMgHPR6ec1tesicX+Xd8Lzj7E9IElS
2xXk8WDiZTX1lvPOZPmo2WARcY0EBWUNf3xyj4fdLQ4iDkYQNH+qikUJm2OjUfWtz8z2adm2ES4x
iBr4aVYSlKIetuKxZLjObGx7AyfsbHHCN4SwbBkDCj+BEZ83fLbwOVtUd7/7xcGiJk7Er3b0s5pO
L3Aw1IyOu8ryEgNuoKWr3V2pH83D+5cA1TefA/vJ/jpHB42uMLBaQY9G7p6iX1IOt+Z7U9lvf0hu
WHiyLqj0IVE3p9z39Lb1BGNxXZ08VE8pRCDtD3QmlV+gpSfvzoYmT3wpvfws7iw+sifrS3ZR64AI
4OsmlEakVIgpawQn+CuVmtBwFGzYqa7Z7yNoFb0hSfP4bXMidYTylNyGz0p35O6r+Y9PNC2/xL60
bYNLDDED2MWWTK1IUu7TZcqOUJN+IZdhItXN4Yxatt1VKMOmgMCiGXEXZt1bajwQOuZa1fVzoxVD
oOvO/eF0kGKVEDD2OQfN4JIBDCLJB2MkjJ9s0DpvCny5p7dEG8feTEDB10k3Ov7ll6Usnb51M9e6
JKOibfKUdLk2Q+7Zf2uP/ROXaGmESEG902TyRU1uPOGuZ37AHFksJbUOEgMDJA3arILfqdY7HELC
ObeKbE67orZFi5JJMcUrIjucnP1s8PCD5iOeMHR/EwLei96U/odWteARj17WHczDhi3byT8QPDFg
rBWFjL4zBCDW4H4snyQsLK+PBNg/PNcfQEwdVoFMniqnh3Y6vClTNCmUh/RU5LTrXw58PPXjdzdK
z4J8n+JV4cfNsTEp7wfHMRZO5O7VA/c1gpqLfMLjcY2yPYWDj796Q4YaHI+JDkwzQ3tldJlGtG9s
/xdnFY9WhLA18uoIb3tWT2pXBQcUtMrVFltyvm96aCCy6fiTZQYUfmSnei+c+cE/5P1ZuDGRiYEB
BooAPm9/kYAGYWIE/0sYqb9JVJe6DfDfy7iaXmQ8YGN2ZzV/zx2XtCQkDqdfzw0muxWQVRB/gNG8
aCyQV/IqPvX7D1CtswupdbJQadOTv36yUi8jCRKsHmS7qTyRqnYKuxIJuxMT443d68rDJdJ775nW
YEXAl5m3ECCkT2S7tZxAVEkwT9lbjWvcbRfkdsuhiPMK0Eu2yR2RsCiwlTmGkpqftCsh9zAoyLof
QWxwYwAAAAAAAAABAAAAAQAAAAxET01BSU4uTE9DQUwAAAANQWRtaW5pc3RyYXRvcgAAAAAAAAAD
AAAADFgtQ0FDSEVDT05GOgAAABVrcmI1X2NjYWNoZV9jb25mX2RhdGEAAAAHcGFfdHlwZQAAACBr
cmJ0Z3QvRE9NQUlOLkxPQ0FMQERPTUFJTi5MT0NBTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAATIAAAAA
EOF
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.open_file("krb.ccache")
>>> t.show()
Tickets:
1. [email protected] -> krbtgt/[email protected]
>>> t.edit_ticket(0)
Enter the NT hash (AES-256) for this ticket (as hex):␣
˓→6df5a9a90cb076f4d232a123d9c24f46ae11590a5430710bc1881dca337989ce
>>> t.resign_ticket(0)
>>> t.save()
1660
>>> # Other stuff you can do
>>> tkt = t.dec_ticket(0)
>>> tkt
<EncTicketPart flags=forwardable, proxiable, renewable, .........>
>>> t.update_ticket(0, tkt)
ò Note
Remember to call resign_ticket to update the Server and KDC checksums in the PAC.
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.request_tgt("[email protected]")
Enter password: ************
>>> t.show()
Tickets:
0. [email protected] -> krbtgt/[email protected]
>>> t.request_st(0, "host/dc1.domain.local")
>>> t.show()
Tickets:
0. [email protected] -> krbtgt/[email protected]
Start time End time Renew until Auth time
31/08/23 11:38:34 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.request_tgt("[email protected]")
Enter password: ************
>>> t.request_st(0, "host/dc1.domain.local")
>>> t.show()
Tickets:
0. [email protected] -> krbtgt/[email protected]
Start time End time Renew until Auth time
31/08/23 11:38:34 31/08/23 21:38:34 31/08/23 21:38:35 31/08/23 01:38:34
• Craft tickets: We can start by showing how to craft a golden ticket, in the same way impacket’s
ticketer does:
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.create_ticket()
User [User]: Administrator
Domain [DOM.LOCAL]: DOMAIN.LOCAL
Domain SID [S-1-5-21-1-2-3]: S-1-5-21-4239584752-1119503303-314831486
Group IDs [513, 512, 520, 518, 519]: 512, 520, 513, 519, 518
User ID [500]: 500
Primary Group ID [513]:
Extra SIDs [] :S-1-18-1
Expires in (h) [10]:
What key should we use (AES128-CTS-HMAC-SHA1-96/AES256-CTS-HMAC-SHA1-96/RC4-
˓→HMAC) ? [AES256-CTS-HMAC-SHA1-96]:
>>> t.show()
Tickets:
0. [email protected] -> krbtgt/[email protected]
>>> t.save(fname="blob.ccache")
Cheat sheet
Command Description
load_module("ticketer") Load ticketer++
t = Ticketer() Create a Ticketer object
t.open_file("/tmp/krb5cc_1000") Open a ccache file
t.save() Save a ccache file
t.show() List the tickets
t.create_ticket() Forge a ticket
dTkt = t.dec_ticket(<index>) Decipher a ticket
t.update_ticket(<index>, dTkt) Re-inject a deciphered ticket
t.edit_ticket(<index>) Edit a ticket (GUI)
t.resign_ticket(<index>) Resign a ticket
t.request_tgt(upn, [...]) Request a TGT
t.request_st(i, spn, [...]) Request a ST using ticket i
t.renew(i, [...]) Renew a TGT/ST
9.7.2 Low-level
Decrypt kerberos packets
Kerberos packets contain encrypted content, let’s take the following packet:
˓→x18 \x14\xb6\xe0\x00\x00\x00\x00\x011j\x82\x01-0\x82\x01)\xa1\x03\x02\x01\
˓→x05\xa2\x03\x02\x01\n\xa3c0a0L\xa1\x03\x02\x01\x02\xa2E\x04C0A\xa0\x03\x02\
˓→x01\x12\xa2:\x048HHM\xec\xb0\x1c\x9bb\xa1\xca\xbf\xbc?-\x1e\xd8Z\xa5\xe0\
˓→x93\xba\x83X\xa8\xce\xa3MC\x93\xaf\x93\xbf!\x1e'O\xa5\x8e\x81Hx\xdb\x9f\rz(\
˓→xd9Ns'f\r\xb4\xf3pK0\x11\xa1\x04\x02\x02\x00\x80\xa2\t\x04\x070\x05\xa0\x03\
˓→x01\x01\xff\xa4\x81\xb70\x81\xb4\xa0\x07\x03\x05\x00@\x81\x00\x10\xa1\x120\
˓→x10\xa0\x03\x02\x01\x01\xa1\t0\x07\x1b\x05win1$\xa2\x0e\x1b\x0cDOMAIN.LOCAL\
˓→xa3!0\x1f\xa0\x03\x02\x01\x02\xa1\x180\x16\x1b\x06krbtgt\x1b\x0cDOMAIN.
˓→LOCAL\xa5\x11\x18\x0f20370913024805Z\xa6\x11\x18\x0f20370913024805Z\xa7\x06\
˓→x02\x04p\x1c\xc5\xd1\xa8\x150\x13\x02\x01\x12\x02\x01\x11\x02\x01\x17\x02\
˓→x01\x18\x02\x02\xffy\x02\x01\x03\xa9\x1d0\x1b0\x19\xa0\x03\x02\x01\x14\xa1\
˓→x12\x04\x10WIN1 ")
>>> pkt[TCP].payload.show()
###[ KerberosTCPHeader ]###
len = 305
###[ Kerberos ]###
\root \
|###[ KRB_AS_REQ ]###
| pvno = 0x5 <ASN1_INTEGER[5]>
| msgType = 'AS-REQ' 0xa <ASN1_INTEGER[10]>
| \padata \
| |###[ PADATA ]###
| | padataType= 'PA-ENC-TIMESTAMP' 0x2 <ASN1_INTEGER[2]>
| | \padataValue\
| | |###[ EncryptedData ]###
(continues on next page)
˓→xa5\x8e\x81Hx\xdb\x9f\rz(\xd9Ns'f\r\xb4\xf3pK"]>
| | \cname \
| | |###[ PrincipalName ]###
| | | nameType = 'NT-PRINCIPAL' 0x1 <ASN1_INTEGER[1]>
| | | nameString= [<ASN1_GENERAL_STRING[b'win1$']>]
| | realm = <ASN1_GENERAL_STRING[b'DOMAIN.LOCAL']>
| | \sname \
| | |###[ PrincipalName ]###
| | | nameType = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
| | | nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_
˓→STRING[b'DOMAIN.LOCAL']>]
| | from = None
| | till = 2037-09-13 02:48:05 UTC <ASN1_GENERALIZED_TIME[
˓→'20370913024805Z']>
| | \addresses \
| | |###[ HostAddress ]###
| | | addrType = 'NetBios' 0x14 <ASN1_INTEGER[20]>
| | | address = <ASN1_STRING[b'WIN1 ']>
| | encAuthorizationData= None
| | additionalTickets= None
The first parameter of the Key constructor is a value from EncryptionType, in this case
EncryptionType.AES256_CTS_HMAC_SHA1_96. This is the same value than enc.etype.val, which
>>> enc.decrypt(k)
<PA_ENC_TS_ENC patimestamp=2022-07-15 17:18:47 UTC <ASN1_GENERALIZED_TIME[
˓→'20220715171847Z']> pausec=0x9a4db <ASN1_INTEGER[632027]> |>
ò Note
Encryption for Kerberos 5 is defined in RFC3961
You may want to compute a Kerberos key from a password + salt. There is an API for that described in
RFC3961 as “string-to-key”. Our implementation is a class method as follow:
Key.string_to_key(etype, string, salt, params=None)
Compute the kerberos key for a certain encryption type.
Parameters
• etype (int) – The EncryptionType to use. May be any value from
EncryptionType
• string (bytes) – The “string” bytes to use. This is the user password in
almost all well-used cases. They must be passed as bytes.
• salt (bytes) – The salt bytes to use. What value to use depends if
you are considering a MACHINE account or a USER account, for the
latter, it’s just the concatenation of the principal's realm and
name components, in order, with no separators. (RFC4120 sect
4)
• params (bytes) – The opaque “parameter” used by string-to-key. The RFC
defines this field in a very general manner but it is basically only used in AES,
in which it is the iteration count as a big-endian int (struct.pack(">L",
4096) by default)
Let’s run a few examples:
>>> print(_.key)
b'm\x07H\xc5F\xf4\xe9\x92\x05\xe7\x8f\x8d\xa7h\x1dN\xc5R\n\xe4\x81UCr\x0c*d|\
˓→x1a\xe8\x14\xc9'
ò Note
The following example is from https://datatracker.ietf.org/doc/html/rfc3962#appendix-B
>>> # Get the AES128 key for [email protected] with "password", with an␣
˓→iteration count of 1200
>>> print(k.key.hex())
'4c01cd46d632d01e6dbe230a01ed642a'
Decrypt FAST
ò Note
Have a look at RFC6113 for Kerberos FAST
˓→'))
>>> pkt[TCP].payload.show()
###[ KerberosTCPHeader ]###
len = 2154
###[ Kerberos ]###
(continues on next page)
| | | | | \armorValue\
| | | | | |###[ KRB_AP_REQ ]###
| | | | | | pvno = 0x5 <ASN1_INTEGER[5]>
| | | | | | msgType = 'AP-REQ' 0xe <ASN1_INTEGER[14]>
| | | | | | apOptions = <ASN1_BIT_STRING[0000000000...
˓→0000000000]=b'\x00\x00\x00\x00' (0 unused bit)>
| | | | | | \ticket \
| | | | | | |###[ KRB_Ticket ]###
| | | | | | | tktVno = 0x5 <ASN1_INTEGER[5]>
| | | | | | | realm = <ASN1_GENERAL_STRING[b'DOM1.LOCAL
˓→']>
| | | | | | | \sname \
| | | | | | | |###[ PrincipalName ]###
| | | | | | | | nameType = 'NT-SRV-INST' 0x2 <ASN1_
˓→INTEGER[2]>
| | | | | | | | nameString= [<ASN1_GENERAL_STRING[b'krbtgt
˓→']>, <ASN1_GENERAL_STRING[b'DOM1.LOCAL']>]
| | | | | | | \encPart \
| | | | | | | |###[ EncryptedData ]###
| | | | | | | | etype = 'AES-256' 0x12 <ASN1_
˓→INTEGER[18]>
˓→x93\xdd\xe4eYR`\xa6\xa5\xa8\xe8^\xa3\x86\x00\xce\x8d\xff}Q\x0f<tN,C\xeb\
˓→x9d1\x87\xd68\xf7\x16\xc2\x9bnz\xa9\xeb@}\xe2\x8d\x01a\xf4\x90\x13\x96n\xda\
˓→n\x16\x1f\xf1t\xda\xd4.z\xa5\x00\xcf\xe2\x98T\x12\x15D\x80\x13\xff\xe4\x88;
˓→k\x11f\xf9\x08\xf5\r\xe1)H\x7f\xe7\x7f\xff\x87O\xd4\x10,\xdc\xce\x8d\xb8\
˓→xdb\xeb\x8d\xa0/\x08\xcc\x88\xb3y\x0c\xda\xd5\xecI\x99Y\xc7\xe7\x9do\xef\
˓→x10}\x1e\x17\xce\x80\xcc=\xf0P\xb7\xe7\xa1\xc3\x1f\'\x8eO\xd4\xea\x95#\xc9P\
˓→x87o\x17K\xe3c#O\x84\x95\xb9U\r\xe1V\x0b\xa1}\xae\xaf\xbf\x13?x\x99\x10S\
˓→xd9)\xad?\xd6h2}B(\x8ee\x81g\x1d\xaa\xef\x90\x86\x82\xee(.\x17\xc3\x1d\x8f\
˓→x8b\xb5]\'\xfc\xe1U\xee.\x84\xa2\xff\x8b\xc9`\x08\x91\xbe\x15\xe6\xed\xe3\
˓→xe1\xbb\xd2t*z\xf8\xb0\xa3,H\x97<\x9e7v\xa6\x96G\xba\xb1\x15\x92ulZ\x15\xb9\
˓→xb4P\xd1\xb7\xd0\x9c\x06pI;\xcc\xc6\r\xfc\xaa\\\xfeF\xfdP\xad\xf8\xe3\x88␣
˓→JF\x91\xdc_\x0c=\xba\xe0\xb4\xdaj\xc2\xddx\x1f\x14\x9aDH@\xaa\xa3\xa3\xc3\
˓→xbe\xfbZ\\\x04\xee\x04\x05\xba\xedf\xaf\xcf\x9b\x98\x8d\x10\xea\x14\xa9U\
˓→xf4=\xf7\x94e\xe6\xfc\x02\xa1+\xce8p\x98\x89P\xf1\xabH\xe1\xa4\xf8v\xf3Qg\
˓→x1cPa\xe69\x9ac\xcb\x04y\xf7\xbd\x01}\xfd\x9b\xc5\xbe\x19/\xafmO\x11\xe6\
˓→xee`\x03\x93>\xea\xf62\xf0\x05lL\x1c\xcd\x18=yw\xcf\xca\x85A\x9f\xe5\xb09gD\
˓→x19\xd8\x02\x06\x8ey,\x95v\xae*\x88\xbf\xbe\xb1\xf5\x92s"g\x82\xc6\xef\xb2\
˓→x88q}\x8fzK\xc3\xbfLi\x7f\xca\xc1\xad\xc1\x82\x9f\n\x91O%Y\xb2x\xcc\xad\xd1\
˓→x08\xeb\x87\xa1\x1d\xac\xc8\x8eC\x02\xe9\xafbtt\xe5qq\x19+\x94\xc6\xb3X\xf8\
˓→xf9\x8e0\x85\x96!]/\xb9\xd9\xc2\xb4\x9cL\xbe\xdc\xb4?\xc21\xb8o\x04\x93\
˓→xd5k\x82\x96,\xf38:\x84\xf8\x92,+\x99\xf8\xfa\x8f\xdd\x85y{\t\xa6\xe6\x0fr\
˓→x00|\x03y\x98\x8b\xe2\xff\x1c\xfc\x16\xf2\x13\x00\xc1\xb4\xb7\x84\x17@\x05\
˓→xa9\x18_v\x0eh\xef\x94\xb98N\xb2M\xec\xee1\xb6=\x1b\x92\'\x8c\xd7[\x85\xd4\
˓→xd8\x0cN\x830e3\xa9\xd9Z\xa6 |\xbf\xbe\xb0\x97\nA\xc4J\xbaY\x83\x9f\x00y#\
˓→xec\xd8\xff\r\xe81I\x90\xa45\xdb\xeaM\xed\xbe\xe1o\xafZ\xb2\xbe\x9f\x96\xd6\
˓→x91\xcf\xa9\x83\xa6\xc8C\xbd\x18?\x84\xc1\xb4\x99\x8a>\xaa\x90|\xaek\x82\
˓→xb0\xae\x83c\xf3\xed\xd8\xcb\x03\xd3\xc9\xc6\x0f\xf5Z\x84\xd8\xa2\x92\xea U_
˓→\xbdl\xe5\xadJ\xd7\xa6\xb4\xbc[\xff.\x02\xc4w\xa7\xa8\xa9\x8dZ8}8\x9c\xaa\
˓→x17,@\x0b\x15\x1d\x95\x87\x1b*\xa1j\x04\r\xc7\x1a\x9b\xe5\xf0wK\x06\xa5\xca\
˓→x87gL\xcbA\t\xa2\xc4\x1d\xb9\xe3\x16\x07\x04!\x8a\xd4\x95\xd0u\x11\x94\xfb\
˓→xefK\xec\xaeM{\xe2K\x9d\x96\x8d\xa5\x92%j+"\xcfrN\x98\x9eq\xa6\r\x06\x03\
˓→xb5\x9b\xeb\xd4u(_y7\x94\xb7\xa1\x8a\xf4\x9a+hg\x0e:bG\xc4S\'N5\xc8c\xa1kP#\
˓→xc6\xc9FY\xe2Z\xbb\'\xc7`\xf9\x89\xac\x0b\xbf\x9a[\x12]\x0e\xa3O\xb02%\xcc\
˓→x93\xd5\xb8\xb6\x82\x9e\x90h\x83\xeev\xcf\x8e\xe6\x1d\xfa\xccH\x8e\x8d\xc5\
˓→xcb\xc8\xba\x97\x05\xa9\xe9\x15\xa6\x8f\x83\x8229O\x97\xfb\x1a\xacJ*\x90\
˓→xfe\x17\xd4o\x9cQ\x94j+\xf9Y\x8d\xf7\xf5\xb5\xe7\xeei*x\x86\x0e\xea<\xeft\
˓→x8a[\xe3e)"\x8e@\xb4\xae\xc8>\xbc\x8b\xb1Av\xa4\xc5e\xb0e\x00\xe9Qr)\xb84\
˓→x0cU\x81!\x01\xdb\xbck\xeei<5\x870\x82\xa5\xa1\xa5;5\xcf5\t\x19=M\xc5\x17\\\
˓→x93`\xa0\r\xa7\x16\x92\xba [2d\xae\xcc\x9e\xcc\x8b\xca1\xfe\xc4>\xfc\x87\
˓→x01B;\xb4\x84\xf6\xf2\x16\x99C\x9d\xd3\x0fq"\x8f\x16\xea\xab\x96\xb7\xde5Gr\
˓→x1d\x165\xbb\xfePg\x89\x00\xac7\x8aIX\xb6\xc3Id\xf3\xe0\xdc\x848\x80\xdb\
˓→xdeW\xfbJv\xab\x85\xeb\xa2\xb1\x90\xbf\xda\xef\xc7\xba\x17\xe1\t\xf89I;\x0f-
˓→o\xc7\xea\x17@;\xeb\xe0o(\t1L\xa5\x14`oTf\x80\x826N\xd6u \x19\xf2~\x1d\xf7O\
˓→x93\xfc\xf1\xc2V0\xa2\x97\x13\xa8\x9dJ\x99\x8cDK\xc9\x12y\xc6\xfcf\xe0\xaa]\
˓→xecr\xbe1n\x11`\xcf\x9f\x90\xd5\x91\\FKk\xfe\xc5!n\x90\x1b\xe4rm\xb5\x96\
˓→xa1WEQ\x1ccsji\xac\x9e\xcb\x9e\x86`\x1cc\x1bI\x92e<2\x0ei\x83V/\xa6\x13\
˓→x13E`\xcb`f!\xe9f\x1a\xc5\x96\x13\x13\xeep\x86\x8a\xb4\x8d`\x10\x17=\x8a\
˓→x96\xff\xfd\xb2\xba\xf4\xaf\xe1\x8c\x84m?\xedo0\xb9\xa8\t\xd7.dw5\xfcSn\xde\
˓→xc5C\xab\xc22H\r(f\x03\x95']>
| | | | | | \authenticator\
| | | | | | |###[ EncryptedData ]###
| | | | | | | etype = 'AES-256' 0x12 <ASN1_INTEGER[18]>
| | | | | | | kvno = None
| | | | | | | cipher = <ASN1_STRING[b'\x12s\xd5\xafa\
˓→xadBmQ\xd0u~\x89y\x17\xca\xebo\xc1\xb6\x95\x05T\xe8\xd7P\xf9]\'\xf4D\xe3\
˓→xaa\xf7\xae\x0b\xf4Y[^\x90m\x96\x82\xdb\xde\xed\xcfn\xb4*\x84\xab\x80\x92\
˓→x99{x?Wq\x01\'"\x81e\xde\xeb,\xe5\xe0\x9e-\xdcqU]\xc3\x19p\xa81-\x88\x8b\
˓→xd5\x8d\xa5\xb0']>
| | | | \encFastReq\
| | | | |###[ EncryptedData ]###
| | | | | etype = 'AES-256' 0x12 <ASN1_INTEGER[18]>
| | | | | kvno = None
| | | | | cipher = <ASN1_STRING[b'<\xaf4\xec\xef\xd8Lxg\x03\
˓→xc2\x009\xdea\xbc\x01\xeb\xed\x9b\xe7\xe5\x1c\x90\xa5\x82\xfe\xc8Rik\xf9/\
˓→xd1e\xcd[^\xf0\xf9\xb8\xed\xb6f\xc9\xcc\xa5i\r6N\\j\xd6\x9e}[\xc7\xe0Uuz\
˓→xaab\x06B\x8a0%$\x14M]\x97\xcc\x0bd\xdb\x133PE\x03\x91q\xed\x1f\r\x11\x1c\
˓→xa1\xbdFQ\xeb\xca=t\xdb\x02\x9e\\m<\x7f\x86\x00\xc4NU\xb1L\xd3\xc7\xf6\xa1\\
˓→\x913@\x0eBU\xd7\x1f#{\xf2\x88\xc1\x86\x13|\xd0J_,\xab\xba1f\xde[\xf1\x11\
˓→x90\xa2\xe5\x96.M\xbb\xfb\x98\x01\xe3\xbes\xed\xe5\xa56\xeb\'\xa0\x86\xb6D\
˓→xf1"E\x19\x84Y\xc0c\xb8\xec\xba"\x8e\x1f\x92\t\xe0Z[\xcb\xb3\x9a\x12e\x1e\
˓→x1048\xeey\x98\xe6f\xd8b\x88\x12\xfa4\xbc\x07\xf4\xc4\xd0\xa4\xd8o\xe2\x07\
˓→x12\x8d\xe3~\x1f\xfd\x16\x9aL\xb8y\xcb[\x9d\xb8\xf9\xc3\xe8aC\xbf\xd44\t\
˓→xcaG\xe9\x0f;\xc8H\xa1\x83\x8f\xcer\t\xf5r\x96\xe4Ic\xa2\xd1\xe3\xd4']>
| \reqBody \
| |###[ KRB_KDC_REQ_BODY ]###
| | kdcOptions= forwardable, renewable, canonicalize, renewable-ok
˓→<ASN1_BIT_STRING[0100000010...0000010000]=b'@\x81\x00\x10' (0 unused bit)>
| | \cname \
| | |###[ PrincipalName ]###
| | | nameType = 'NT-PRINCIPAL' 0x1 <ASN1_INTEGER[1]>
| | | nameString= [<ASN1_GENERAL_STRING[b'adm-0-fastenb']>]
| | realm = <ASN1_GENERAL_STRING[b'DOM1']>
| | \sname \
| | |###[ PrincipalName ]###
| | | nameType = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
| | | nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_
˓→STRING[b'DOM1']>]
| | from = None
| | till = 2037-09-13 02:48:05 UTC <ASN1_GENERALIZED_TIME[
˓→'20370913024805Z']>
| | \addresses \
| | |###[ HostAddress ]###
| | | addrType = 'NetBios' 0x14 <ASN1_INTEGER[20]>
| | | address = <ASN1_STRING[b'SRV ']>
| | encAuthorizationData= None
| | additionalTickets= None
\key \
|###[ EncryptionKey ]###
| keytype = 'AES-256' 0x12 <ASN1_INTEGER[18]>
| keyvalue = <ASN1_STRING[b'\xe3\xa2\x0f\x8e\xb2\xe1*\xe0\x7f\x86\xcc\
˓→x88\xe6,\x08>B\xd8)m/G\x82B;\x9f+\x86\xcd\xcd\xf4\x05']>
crealm = <ASN1_GENERAL_STRING[b'DOM1.LOCAL']>
\cname \
|###[ PrincipalName ]###
| nameType = 'NT-PRINCIPAL' 0x1 <ASN1_INTEGER[1]>
| nameString= [<ASN1_GENERAL_STRING[b'SRV$']>]
\transited \
|###[ TransitedEncoding ]###
| trType = 0x0 <ASN1_INTEGER[0]>
| contents = <ASN1_STRING[b'']>
authtime = 2022-07-12 23:02:25 UTC <ASN1_GENERALIZED_TIME[
˓→'20220712230225Z']>
addresses = None
[...]
We can see the ticket session key in there, let’s retrieve it and build a Key object:
ò Note
We use the .toKey() function in the EncryptedKey type which is a shorthand for Key(<keytype>,
key=<keyvalue>)
\subkey \
|###[ EncryptionKey ]###
| keytype = 'AES-256' 0x12 <ASN1_INTEGER[18]>
| keyvalue = <ASN1_STRING[b'%\xa4n\xe1\xd0\xf5\x8d\xc4\x8d\xecv\xe8\x9c\
˓→xd3\xc9\xee\x1bu\xc9\xa5\xa6\xf8\x83f\x98\xa1\xd9\xe7*I\x9b\xf8']>
Again, we see inside this the subkey that is used to compute the armor key. We get it:
Following RFC6113 sect 5.4.1.1, we can now compute the armor key using:
\padata \
|###[ PADATA ]###
| padataType= 'PA-PAC-REQUEST' 0x80 <ASN1_INTEGER[128]>
| \padataValue\
| |###[ PA_PAC_REQUEST ]###
| | includePac= True <ASN1_BOOLEAN[-1]>
|###[ PADATA ]###
| padataType= 'PA-PAC-OPTIONS' 0xa7 <ASN1_INTEGER[167]>
| \padataValue\
| |###[ PA_PAC_OPTIONS ]###
| | options = Claims <ASN1_BIT_STRING[1000000000...0000000000]=b'\
˓→x80\x00\x00\x00' (0 unused bit)>
\reqBody \
|###[ KRB_KDC_REQ_BODY ]###
| kdcOptions= forwardable, renewable, canonicalize, renewable-ok <ASN1_
˓→BIT_STRING[0100000010...0000010000]=b'@\x81\x00\x10' (0 unused bit)>
| \cname \
| |###[ PrincipalName ]###
| | nameType = 'NT-PRINCIPAL' 0x1 <ASN1_INTEGER[1]>
| | nameString= [<ASN1_GENERAL_STRING[b'adm-0-fastenb']>]
| realm = <ASN1_GENERAL_STRING[b'DOM1']>
| \sname \
| |###[ PrincipalName ]###
| | nameType = 'NT-SRV-INST' 0x2 <ASN1_INTEGER[2]>
| | nameString= [<ASN1_GENERAL_STRING[b'krbtgt']>, <ASN1_GENERAL_
˓→STRING[b'DOM1']>]
| from = None
| till = 2037-09-13 02:48:05 UTC <ASN1_GENERALIZED_TIME[
˓→'20370913024805Z']>
˓→0x3 <ASN1_INTEGER[3]>]
| \addresses \
| |###[ HostAddress ]###
| | addrType = 'NetBios' 0x14 <ASN1_INTEGER[20]>
(continues on next page)
Encryption
A encrypt() function exists in the Key object in order to do the opposite of decrypt().
For instance, during pre-authentication, encode PA-ENC-TIMESTAMP:
>>> # Encrypt
>>> pkt.padataValue.encrypt(key, PA_ENC_TS_ENC(patimestamp=ASN1_GENERALIZED_
˓→TIME(now_time)))
>>> pkt.show()
###[ PADATA ]###
padataType= 2
\padataValue\
|###[ EncryptedData ]###
| etype = 18
| kvno = 0x0 <ASN1_INTEGER[0]>
| cipher = b"\xc1\x9a\xaf\x89V\x16\x82\xb6\x9a\xcb\x15[\xaf\xed\xd9\
˓→xfc\x04\xbf\x18\xd4&\x91\xb3\xcf~tEk,\x98m\xee\xa4O\x05=\x11b\xe05\xca\
˓→x92+80\x99\xb1'~\x8d\xdbtz\xa8"
9.8 LDAP
Scapy fully implements the LDAPv2 / LDAPv3 messages, in addition to a very basic LDAP_Client
class.
Connecting
Let’s first instantiate the LDAP_Client, and connect to a server over the default port (389):
client = LDAP_Client()
client.connect("192.168.0.100")
client = LDAP_Client()
client.connect("192.168.0.100", use_ssl=True)
In that case, the default port is 636. This can be changed using the port attribute.
ò Note
By default, the server certificate is NOT checked when using this mode, because the server certificate
will likely be self-signed. To actually use TLS securely, you should pass a sslcontext as shown
below:
import ssl
client = LDAP_Client()
sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
sslcontext.load_verify_locations('path/to/ca.crt')
client.connect("192.168.0.100", use_ssl=True, sspcontext=sslcontext)
ò Note
If the client is too verbose, you can pass verb=False when instantiating LDAP_Client.
Binding
When binding, you must specify a mechanism type. This type comes from the LDAP_BIND_MECHS enu-
meration, which contains:
• NONE: an unauthenticated bind.
• SIMPLE: the simple bind mechanism. Credentials are sent in plaintext.
• SICILY: a Windows specific authentication mechanism specified in [MS-ADTS] that only supports
NTLM.
• SASL_GSSAPI: the SASL authentication mechanism, as specified by RFC 4422.
• SASL_GSS_SPNEGO: the SPNEGO authentication mechanism, another Windows specific authen-
tication mechanism specified in [MS-SPNG].
Depending on the server that you are talking to, some of those mechanisms might not be available. This
is most notably the case of SICILY and SASL_GSS_SPNEGO which are mostly Windows-specific.
We’ll now go over “how to bind” using each one of those mechanisms:
NONE (Unauthenticated):
client.bind(LDAP_BIND_MECHS.NONE)
SIMPLE:
client.bind(
LDAP_BIND_MECHS.SIMPLE,
simple_username="Administrator",
simple_password="Password1!",
)
SICILY - NTLM:
SASL_GSSAPI - Kerberos:
ssp = SPNEGOSSP([
NTLMSSP(UPN="Administrator", PASSWORD="Password1!"),
KerberosSSP(UPN="[email protected]", PASSWORD="Password1!",
SPN="ldap/dc1.domain.local"),
])
client.bind(
LDAP_BIND_MECHS.SASL_GSS_SPNEGO,
ssp=ssp,
)
Signing / Encryption
Additionally, it is possible to enable signing or encryption of the LDAP data, when LDAPS is NOT in
use. This is done by setting sign and encrypt parameters of the bind() function.
There are however a few caveats to note:
• It’s not possible to use those flags in NONE (duh) or SIMPLE mode.
• When using the NTLMSSP (in SICILY or SASL_GSS_SPNEGO mode), it isn’t possible to use sign
without encrypt, because Windows doesn’t implement it.
Querying
Once the LDAP connection is bound, it becomes possible to perform requests. For instance, to query all
the values of the root DSE:
client.sr1(LDAP_SearchRequest()).show()
We can also use the search() passing a base DN, a filter (as specified by RFC2254) and a scope.\
The scope can be one of the following:
• 0=baseObject: only the base DN’s attributes are queried
• 1=singleLevel: the base DN’s children are queried
• 2=wholeSubtree: the entire subtree under the base DN is included
For instance, this corresponds to querying the DN CN=Users,DC=domain,DC=local with the fil-
ter (objectCategory=person) and asking for the attributes objectClass,name,description,
canonicalName:
resp = client.search(
"CN=Users,DC=domain,DC=local",
"(objectCategory=person)",
["objectClass", "name", "description", "canonicalName"],
scope=1, # children
)
resp.show()
To understand exactly what’s going on, note that the previous call is exactly identical to the following:
resp = client.sr1(
LDAP_SearchRequest(
filter=LDAP_Filter(
filter=LDAP_FilterEqual(
attributeType=ASN1_STRING(b'objectCategory'),
attributeValue=ASN1_STRING(b'person')
)
),
attributes=[
LDAP_SearchRequestAttribute(type=ASN1_STRING(b'objectClass')),
LDAP_SearchRequestAttribute(type=ASN1_STRING(b'name')),
LDAP_SearchRequestAttribute(type=ASN1_STRING(b'description')),
LDAP_SearchRequestAttribute(type=ASN1_STRING(b'canonicalName'))
],
baseObject=ASN1_STRING(b'CN=Users,DC=domain,DC=local'),
scope=ASN1_ENUMERATED(1),
derefAliases=ASN1_ENUMERATED(0),
sizeLimit=ASN1_INTEGER(1000),
timeLimit=ASN1_INTEGER(60),
attrsOnly=ASN1_BOOLEAN(0)
)
)
. Warning
Our RFC2254 parser currently does not support ‘Extensible Match’.
Modifying attributes
It’s also possible to change some attributes on an object. The following issues a Modify Request that
replaces the displayName attribute and adds a servicePrincipalName:
client.modify(
"CN=User1,CN=Users,DC=domain,DC=local",
changes=[
LDAP_ModifyRequestChange(
operation="replace",
modification=LDAP_PartialAttribute(
type="displayName",
(continues on next page)
9.9 Netflow
Netflow packets mainly comes in 3 versions:
- ``Netflow V5``
- ``Netflow V7``
- ``Netflow V9 / V10 (IPfix)``
While the two first versions are pretty straightforward, building or dissecting Netflow v9/v10 isn’t easy.
9.9.1 Netflow V1
netflow = NetflowHeader()/NetflowHeaderV1()/NetflowRecordV1()
pkt = Ether()/IP()/UDP()/netflow
9.9.2 Netflow V5
netflow = NetflowHeader()/NetflowHeaderV5(count=1)/NetflowRecordV5(dst="192.
˓→168.0.1")
pkt = Ether()/IP()/UDP()/netflow
ò Note
The following examples apply to Netflow V9. When using IPfix, use the exact same format but replace
the class names with their V10 counterpart (if they exist ! Scapy shares some classes between the
two). Have a look at netflow
• Build
header = Ether()/IP()/UDP()
netflow_header = NetflowHeader()/NetflowHeaderV9()
• Dissection
Scapy provides two methods to parse NetflowV9/IPFix:
• NetflowSession: to use with sniff(session=NetflowV9Session, [...])
• netflowv9_defragment(): to use on a packet or list of packets.
With the previous example:
>>> load_contrib('pnio')
>>> raw(ProfinetIO()/b'AAA')
b'\x00\x00AAA'
>>> raw(PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 4)(data = b'AAA',␣
˓→control=0x20, crc=0x424242))
b'AAA\x00 BBB'
>>> hexdump(PNIORealTime_IOxS())
0000 80 .
>>> load_contrib('pnio')
>>> p=PNIORealTimeCyclicPDU(cycleCounter=1024, data=[
... PNIORealTime_IOxS(),
... PNIORealTimeCyclicPDU.build_fixed_len_raw_type(4)(data = b'AAA') /␣
˓→PNIORealTime_IOxS(),
... ])
>>> p.show()
###[ PROFINET Real-Time ]###
\data \
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
|###[ FixedLenRawPacketLen4 ]###
| data = 'AAA'
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
|###[ PROFISafe Control Message with F_CRC_Seed=0 ]###
| dat( = 'AAA'
| control = Toggle_h
| crc = 0x424242
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
padding = ''
cycleCounter= 1024
dataStatus= primary+validData+run+no_problem
transferStatus= 0
For Scapy to be able to dissect it correctly, one must also configure the layer for it to know the location
of each data in the buffer. This configuration is saved in the dictionary conf.contribs["PNIO_RTC"]
which can be updated with the conf.contribs["PNIO_RTC"].update method. Each item in the dic-
tionary uses the tuple (Ether.src, Ether.dst, ProfinetIO.frameID) as key, to be able to sepa-
rate the configuration of each communication. Each value is then a list of classes which describes a data
packet. If we continue the previous example, here is the configuration to set:
>>> e.show2()
###[ Ethernet ]###
dst = 06:07:08:09:0a:0b
src = 00:01:02:03:04:05
(continues on next page)
... PNIORealTime_IOxS,
... PNIORealTimeCyclicPDU.build_fixed_len_raw_type(4),
... PNIORealTime_IOxS,
... PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 4),
... PNIORealTime_IOxS,
... ]})
>>> e.show2()
###[ Ethernet ]###
dst = 06:07:08:09:0a:0b
src = 00:01:02:03:04:05
type = 0x8892
###[ ProfinetIO ]###
frameID = RT_CLASS_1 (8000)
###[ PROFINET Real-Time ]###
\data \
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
|###[ FixedLenRawPacketLen4 ]###
| data = 'AAA'
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
|###[ PROFISafe Control Message with F_CRC_Seed=0 ]###
| data = 'AAA'
| control = Toggle_h
| crc = 0x424242
|###[ PNIO RTC IOxS ]###
| dataState = good
| instance = subslot
| reserved = 0x0
| extension = 0
(continues on next page)
9.11 SCTP
SCTP is a relatively young transport-layer protocol combining both TCP and UDP characteristics. The
RFC 3286 introduces it and its description lays in the RFC 4960.
It is not broadly used, its mainly present in core networks operated by telecommunication companies, to
support VoIP for instance.
You may also want to use the dynamic address reconfiguration without necessarily enabling the chunk
authentication:
9.12 SMB
Scapy provides pretty good support for SMB 2/3 and very partial support of SMB1.
You can use the SMB2_Header to dissect or build SMB2/3, or SMB_Header for SMB1.
High-Level smbclient
From the CLI
ò Note
You can use help or ? in the CLI to get the list of available commands.
As you can see, the previous example used Kerberos to authenticate. By default, the smbclient class
will use a SPNEGOSSP and provide ask for both NTLM and Kerberos. but it is possible to have a greater
control over this by providing your own ssp attribute.
smbclient using a NTLMSSP
You might be wondering if you can pass the HashNT of the password of the user ‘Administrator’ directly.
The answer is yes, you can ‘pass the hash’ directly:
>>> load_module("ticketer")
>>> t = Ticketer()
>>> t.request_tgt("[email protected]")
Enter password: **********
>>> t.request_st(0, "host/server1.domain.local")
(continues on next page)
If you pay very close attention, you’ll notice that in this case we aren’t using the SPNEGOSSP wrapper.
You could have used ssp=SPNEGOSSP([t.ssp(1)]).
smbclient forcing encryption:
ò Note
It is also possible to start the smbclient directly from the OS, using the following:
$ python3 -m scapy.layers.smbclient server1.domain.local␣
˓→[email protected]
Programmatically
A cool feature of the smbclient is that all commands that you can call from the CLI, you can also call
programmatically.
Let’s re-do the initial example programmatically, by turning off the CLI mode. Obviously prompting for
passwords will not work so make sure the client has everything it needs for Session Setup.
Mid-Level SMB_SOCKET
If you know what you’re doing, then the High-Level smbclient might not be enough for you. You can go
a level lower using the SMB_SOCKET. You can instantiate the object directly or via the from_tcpsock()
helper.
Let’s write a script that connects to a share and list the files in the root folder.
import socket
from scapy.layers.smbclient import SMB_SOCKET
from scapy.layers.spnego import SPNEGOSSP
from scapy.layers.ntlm import NTLMSSP, MD4le
from scapy.layers.kerberos import KerberosSSP
# Build SSP first. In SMB_SOCKET you have to do this yourself
password = "password"
ssp = SPNEGOSSP([
NTLMSSP(UPN="Administrator", PASSWORD=password),
KerberosSSP(
UPN="[email protected]",
PASSWORD=password,
SPN="cifs/server1",
)
])
# Connect to the server
sock = socket.socket()
sock.connect(("server1.domain.local", 445))
smbsock = SMB_SOCKET.from_tcpsock(sock, ssp=ssp)
# Tree connect
tid = smbsock.tree_connect("C$")
smbsock.set_TID(tid)
# Open root folder and query files at root
fileid = smbsock.create_request('', type='folder')
files = smbsock.query_directory(fileid)
names = [x[0] for x in files]
# Close the handle
smbsock.close_request(fileid)
# Close the socket
smbsock.close()
This has a lot more overhead so make sure you need it.
Something hybrid that might be easier to use, is to access the underlying SMB_SOCKET in a higher-level
smbclient:
>>> cli.use('c$')
>>> smbsock = cli.smbsock
>>> # Open root folder and query files at root
>>> fileid = smbsock.create_request('', type='folder')
>>> files = smbsock.query_directory(fileid)
>>> names = [x[0] for x in files]
Low-Level SMB_Client
Finally, it’s also possible to call the underlying smblink socket directly. Again, you can instantiate the
object directly or via the from_tcpsock() helper.
It’s also accessible as the ins attribute of a SMB_SOCKET, or the sock attribute of a smbclient.
Once again, Scapy provides high level smbserver class that allows to spawn a SMB server.
High-Level smbserver
The smbserver class allows to spawn a SMB server serving a selection of shares. A share is identified
by a name and a path (+ an optional description called remark).
Start a SMB server with NTLM auth for 2 users:
smbserver(
shares=[SMBShare(name="Scapy", path="/tmp")],
iface="eth0",
ssp=NTLMSSP(
IDENTITIES={
"User1": MD4le("Password1"),
"Administrator": MD4le("Password2"),
},
(continues on next page)
smbserver(
shares=[SMBShare(name="Scapy", path="/tmp")],
iface="eth0",
ssp=KerberosSSP(
KEY=Key(
EncryptionType.AES256_CTS_HMAC_SHA1_96,
key=bytes.fromhex(
˓→"0000000000000000000000000000000000000000000000000000000000000000"),
),
SPN="cifs/server.domain.local",
),
)
You can of course combine a NTLM and Kerberos server and provide them both over a SPNEGOSSP:
smbserver(
shares=[SMBShare(name="Scapy", path="/tmp")],
iface="eth0",
ssp=SPNEGOSSP(
[
KerberosSSP(
KEY=Key(
EncryptionType.AES256_CTS_HMAC_SHA1_96,
key=bytes.fromhex(
˓→"0000000000000000000000000000000000000000000000000000000000000000"),
),
SPN="cifs/server.domain.local",
),
NTLMSSP(
IDENTITIES={
"User1": MD4le("Password1"),
"Administrator": MD4le("Password2"),
},
),
]
),
)
ò Note
By default, Scapy’s SMB server is read-only. You can set readonly to False to disable it, as follows.
smbserver(
shares=[SMBShare(name="Scapy", path="/tmp")],
iface="eth0",
ssp=NTLMSSP(
IDENTITIES={
"User1": MD4le("Password1"),
"Administrator": MD4le("Password2"),
},
),
# Enable Read-Write
readonly=False,
)
ò Note
It is possible to start the smbserver (albeit only in unauthenticated mode) directly from the OS, using
the following:
$ python3 -m scapy.layers.smbserver --port 12345
Low-Level SMB_Server
To change the functionality of the SMB_Server, you shall extend the server class (which is an automaton)
and provide additional custom conditions (or overwrite existing ones).
9.13 TCP
Scapy is based on a stimulus/response model. This model does not work well for a TCP stack. On the
other hand, quite often, the TCP stream is used as a tube to exchange messages that are stimulus/response-
based.
Also, Scapy provides a way to describe network automata that can be used to create a TCP stack automa-
ton.
There are many ways to use TCP with Scapy
>>> s=socket.socket()
>>> s.connect(("www.test.com",80))
>>> ss=StreamSocket(s,Raw)
>>> ss.sr1(Raw("GET /\r\n"))
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
<Raw load='<html>\r\n<head> ... >
Using kernel’s TCP stack means you’ll depend on your local firewall’s rules and the kernel’s routing
table.
ò Note
TCP_client.tcplink is a SuperSocket subclass, therefore all its functions (.sniff(), . . . ) are
available.
ò Note
specifically for HTTP, you could pass HTTP instead of Raw. More information over HTTP in Scapy.
ò Note
This module only works on BSD, Linux and macOS.
TUN/TAP lets you create virtual network interfaces from userspace. There are two types of devices:
TUN devices
Operates at Layer 3 (IP), and is generally limited to one protocol.
TAP devices
Operates at Layer 2 (Ether), and allows you to use any Layer 3 protocol (IP, IPv6, IPX, etc.)
9.14.1 Requirements
FreeBSD
Requires the if_tap and if_tun kernel modules.
See tap(4) and tun(4) manual pages for more information.
Linux
Load the tun kernel module:
# modprobe tun
ò Note
On macOS 10.13 and later, you need to explicitly approve loading each third-party kext for the
first time.
Tip
Using TUN/TAP generally requires running Scapy (and these utilities) as root.
>>> t = TunTapInterface('tun0')
You’ll then need to bring the interface up, and assign an IP address in another terminal.
Because TUN is a layer 3 connection, it acts as a point-to-point link. We’ll assign these parameters:
• local address (for your machine): 192.0.2.1
• remote address (for Scapy): 192.0.2.2
On Linux, you would use:
Now, nothing will happen when you ping those addresses – you’ll need to make Scapy respond to that
traffic.
TunTapInterface works the same as a SuperSocket, so lets setup an AnsweringMachine to respond
to ICMP echo-request:
>>> am = t.am(ICMPEcho_am)
>>> am()
>>> am()
Replying 192.0.2.1 to 192.0.2.2
Replying 192.0.2.1 to 192.0.2.2
Replying 192.0.2.1 to 192.0.2.2
You might have noticed that didn’t configure Scapy with any IP address. . . and there’s a trick to this:
ICMPEcho_am swaps the source and destination fields of any Ether and IP headers on the ICMP
packet that it receives. As a result, it actually responds to any IP address.
You can stop the ICMPEcho_am AnsweringMachine with ^C.
When you close Scapy, the tun0 interface will automatically disappear.
Parameters
• iface (Text) – The name of the interface to use, eg: tun0.
On BSD and macOS, this must start with either tun or tap, and have a
corresponding /dev/ node (eg: /dev/tun0).
On Linux, this will be truncated to 16 bytes.
• mode_tun (bool) – If True, create as TUN interface (layer 3). If False,
creates a TAP interface (layer 2).
If not supplied, attempts to detect from the iface parameter.
• strip_packet_info (bool) – If True (default), any TunPacketInfo will
be stripped from the packet (so you get Ether or IP).
Only Linux TUN interfaces have TunPacketInfo available.
This has no effect for interfaces that do not have TunPacketInfo available.
• default_read_size (int) – Sets the default size that is read by
SuperSocket.raw_recv() and SuperSocket.recv(). This defaults to
scapy.data.MTU.
TunTapInterface always adds overhead for TunPacketInfo headers, if
required.
class TunPacketInfo(Packet)
Abstract class used to stack layer 3 protocols on a platform-specific header.
See LinuxTunPacketInfo for an example.
guess_payload_class(payload)
The default implementation expects the field proto to be declared, with a value from scapy.
data.ETHER_TYPES.
Linux-specific structures
class LinuxTunPacketInfo(TunPacketInfo)
Packet header used for Linux TUN packets.
This is struct tun_pi, declared in linux/if_tun.h.
flags
Flags to set on the packet. Only TUN_VNET_HDR is supported.
proto
Layer 3 protocol number, per scapy.data.ETHER_TYPES.
Used by TunTapPacketInfo.guess_payload_class().
class LinuxTunIfReq(Packet)
Internal “packet” used for TUNSETIFF requests on Linux.
This is struct ifreq, declared in linux/if.h.
TEN
TROUBLESHOOTING
10.1 FAQ
10.1.1 I can’t sniff/inject packets in monitor mode.
The use monitor mode varies greatly depending on the platform, reasons are explained on the Wireshark
wiki:
Unfortunately, changing the 802.11 capture modes is very platform/network
adapter/driver/libpcap dependent, and might not be possible at all (Windows is very
limited here).
Here is some guidance on how to properly use monitor mode with Scapy:
• Using Libpcap (or Npcap):
libpcap must be called differently by Scapy in order for it to create the sockets in moni-
tor mode. You will need to pass the monitor=True to any calls that open a socket (send,
sniff. . . ) or to a Scapy socket that you create yourself (conf.L2Socket. . . )
On Windows, you additionally need to turn on monitor mode on the WiFi card, use:
>>> conf.iface.setmonitor(True)
. Warning
233
Scapy Documentation, Release 2.6.1
If you are using Npcap: please note that Npcap npcap-0.9983 broke the 802.11 support until
npcap-1.3.0. Avoid using those versions.
We make our best to make this work, if your adapter works with Wireshark for instance, but not with
Scapy, feel free to report an issue.
10.1.3 I can’t ping 127.0.0.1 (or ::1). Scapy does not work with 127.0.0.1 (or ::1)
on the loopback interface.
The loopback interface is a very special interface. Packets going through it are not really assembled and
disassembled. The kernel routes the packet to its destination while it is still stored an internal structure.
What you see with `tcpdump -i lo` is only a fake to make you think everything is normal. The kernel
is not aware of what Scapy is doing behind his back, so what you see on the loopback interface is also a
fake. Except this one did not come from a local structure. Thus the kernel will never receive it.
ò Note
Starting from Scapy > 2.5.0, Scapy will automatically use L3RawSocket when necessary when using
L3-functions (sr-like) on the loopback interface, when libpcap is not in use.
On Linux, in order to speak to local IPv4 applications, you need to build your packets one layer upper,
using a PF_INET/SOCK_RAW socket instead of a PF_PACKET/SOCK_RAW (or its equivalent on other
systems than Linux):
>>> conf.L3socket
<class __main__.L3PacketSocket at 0xb7bdf5fc>
>>> conf.L3socket = L3RawSocket
>>> sr1(IP() / ICMP())
<IP version=4L ihl=5L tos=0x0 len=28 id=40953 flags= frag=0L ttl=64␣
˓→proto=ICMP chksum=0xdce5 src=127.0.0.1 dst=127.0.0.1 options='' |<ICMP ␣
# Layer 3
>>> sr1(IPv6() / ICMPv6EchoRequest())
<IPv6 version=6 tc=0 fl=866674 plen=8 nh=ICMPv6 hlim=64 src=::1 dst=::1 |
˓→<ICMPv6EchoReply type=Echo Reply code=0 cksum=0x7ebb id=0x0 seq=0x0 |>>
# Layer 2
>>> srp1(Ether() / IPv6() / ICMPv6EchoRequest(), iface=conf.loopback_name)
<Ether dst=00:00:00:00:00:00 src=00:00:00:00:00:00 type=IPv6 |<IPv6 ␣
˓→version=6 tc=0 fl=866674 plen=8 nh=ICMPv6 hlim=64 src=::1 dst=::1 |
. Warning
You can disable libpcap using conf.use_pcap = False or bypass it on layer 3 using conf.
L3socket = L3RawSocket.
On Windows, BSD, and macOS, you must deactivate/configure the local firewall prior to using the
following commands:
# Layer 3
>>> sr1(IP() / ICMP())
<IP version=4L ihl=5L tos=0x0 len=28 id=40953 flags= frag=0L ttl=64␣
˓→proto=ICMP chksum=0xdce5 src=127.0.0.1 dst=127.0.0.1 options='' |<ICMP ␣
# Layer 2
>>> srp1(Loopback() / IP() / ICMP(), iface=conf.loopback_name)
<Loopback type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=28 id=56066 flags=␣
˓→frag=0 ttl=64 proto=icmp chksum=0x0 src=127.0.0.1 dst=127.0.0.1 |<ICMP ␣
conf.sniff_promisc = False
ò Note
This does NOT apply for Windows XP, which isn’t supported by Npcap. On XP, uninstall Npcap and
keep Winpcap.
1. If you get the message 'Winpcap is installed over Npcap.' it means that you have in-
stalled both Winpcap and Npcap versions, which isn’t recommended.
You may first uninstall winpcap from your Program Files, then you will need to remove some files
that are not deleted by the Winpcap uninstaller:
C:/Windows/System32/wpcap.dll
C:/Windows/System32/Packet.dll
C:/Windows/SysWOW64/wpcap.dll
C:/Windows/SysWOW64/Packet.dll
>>> x.graph(format="png")
>>> x.graph(options="-Gdpi=70")
ELEVEN
SCAPY DEVELOPMENT
class FlagsField(BitField):
""" Handle Flag type field
239
Scapy Documentation, Release 2.6.1
>>> FlagsTest(flags=9).show2()
###[ FlagsTest ]###
flags = f0+f3
>>> FlagsTest(flags=0).show2().strip()
###[ FlagsTest ]###
flags =
"""
It will contain a short one-line description of the class followed by some indications about its usage. You
can add a usage example if it makes sense using the doctest format. Finally, the classic python signature
can be added following the sphinx documentation.
This task works in pair with writing non regression unit tests.
11.3.2 Documentation
A way to improve the documentation content is by keeping it up to date with the latest version of Scapy.
You can also help by adding usage examples of your own or directly gathered from existing online Scapy
presentations.
+ Test Set 1
* comments for test set 1
= Unit Test 1
~ keywords
* Comments for unit test 1
# Python statements follow
a = 1
print a
a == 1
Python statements are identified by the lack of a defined UTScapy syntax specifier. The Python state-
ments are fed directly to the Python interpreter as if one is operating within the interactive Scapy shell
(interact). Looping, iteration and conditionals are permissible but must be terminated by a blank line.
A test set may be comprised of multiple unit tests and multiple test sets may be defined for each cam-
paign. It is even possible to have multiple test campaigns in a particular test definition file. The use of
keywords allows testing of subsets of the entire campaign. For example, during the development of a
test campaign, the user may wish to mark new tests under development with the keyword “debug”. Once
the tests run successfully to their desired conclusion, the keyword “debug” could be removed. Keywords
such as “regression” or “limited” could be used as well.
It is important to note that UTScapy uses the truth value from the last Python statement as the indicator
as to whether a test passed or failed. Multiple logical tests may appear on the last line. If the result is 0
or False, the test fails. Otherwise, the test passes. Use of an assert() statement can force evaluation of
intermediate values if needed.
The syntax for UTScapy is shown in Table 3 - UTScapy command line syntax:
* This comment is associated with the test campaign and will appear
* in the produced output.
+ Test Set 1
= Unit Test 1
~ test_set_1 simple
(continues on next page)
= Unit test 2
~ test_set_1 simple
* this test will fail
b = 2
a == b
= Unit test 3
~ test_set_1 harder
a = 1
b = 2
c = "hello"
assert (a != b)
c == "hello"
+ Test Set 2
= Unit Test 4
~ test_set_2 harder
b = 2
d = b
d is b
= Unit Test 5
~ test_set_2 harder hardest
a = 2
b = 3
d = 4
e = (a * b)**d
# The following statement evaluates to False but is not last; continue
e == 6
# assert evaluates to False; stop test and fail
assert (e == 7)
e == 1296
= Unit Test 6
~ test_set_2 hardest
print e
e == 1296
ò Note
This will trigger the unit tests on all available Python versions unless you specify a -e option. See
below
For your convenience, and for package maintainers, we provide a util that run tox on only a single (default
Python) environment, again with no external dependencies:
./test/run_tests
cp -i -v ftdetect/filetype.vim $HOME/.vim/ftdetect/filetype.vim
cp -i -v ftdetect/uts.vim $HOME/.vim/ftdetect/uts.vim
cp -i -v syntax/uts.vim $HOME/.vim/syntax/uts.vim
Release Candidates (RC) could also be done. For example, the first RC will be tagged v2.4.3rc1 and the
message 2.4.3 Release Candidate #1.
ò Note
To add a signing key, configure to use a SSH one, then register it via::
$ git config –global gpg.format ssh $ git config –global user.signingkey ~/.ssh/examplekey.pub
Prior to uploading the release to PyPi, the mail address of the maintainer performing the release must be
added next to his name in pyproject.toml. See this for details.
The following commands can then be used:
. Warning
Make sure that you don’t have left-overs in your dist/ folder ! There should only be the source and
the wheel for the package. Also check that the wheel ends in *-py3-none-any.whl !
If you want to test Scapy while packaging it, you are encouraged to use the ./run_tests script with no
arguments. It will run a subset of the tests that don’t use any external dependency, and will be easier to
test. The only dependency is tox
$ ./test/run_tests
TWELVE
CREDITS
• Philippe Biondi is Scapy’s author. He has also written most of the documentation.
• Pierre Lalet, Gabriel Potter, Guillaume Valadon, Nils Weiss are the current most active maintainers
and contributors.
• Fred Raynal wrote the chapter on building and dissecting packets.
• Peter Kacherginsky contributed several tutorial sections, one-liners and recipes.
• Dirk Loss integrated and restructured the existing docs to make this book.
• Nils Weiss contributed automotive specific layers and utilities.
247
Scapy Documentation, Release 2.6.1
s scapy.contrib.automotive.gm.gmlan_logging,
scapy, ?? ??
scapy.ansmachine, ?? scapy.contrib.automotive.gm.gmlan_scanner,
scapy.as_resolvers, ?? ??
scapy.asn1, ?? scapy.contrib.automotive.gm.gmlanutils,
scapy.asn1.asn1, ?? ??
scapy.asn1.ber, ?? scapy.contrib.automotive.kwp, ??
scapy.asn1.mib, ?? scapy.contrib.automotive.obd, ??
scapy.asn1fields, ?? scapy.contrib.automotive.obd.iid, ??
scapy.asn1packet, ?? scapy.contrib.automotive.obd.iid.iids,
scapy.automaton, ?? ??
scapy.autorun, ?? scapy.contrib.automotive.obd.mid, ??
scapy.base_classes, ?? scapy.contrib.automotive.obd.mid.mids,
scapy.config, ?? ??
scapy.consts, ?? scapy.contrib.automotive.obd.obd, ??
scapy.contrib, ?? scapy.contrib.automotive.obd.packet, ??
scapy.contrib.altbeacon, ?? scapy.contrib.automotive.obd.pid, ??
scapy.contrib.aoe, ?? scapy.contrib.automotive.obd.pid.pids,
scapy.contrib.automotive, ?? ??
scapy.contrib.automotive.autosar, ?? scapy.contrib.automotive.obd.pid.pids_00_1F,
scapy.contrib.automotive.autosar.pdu, ??
?? scapy.contrib.automotive.obd.pid.pids_20_3F,
scapy.contrib.automotive.autosar.secoc, ??
?? scapy.contrib.automotive.obd.pid.pids_40_5F,
scapy.contrib.automotive.autosar.secoc_canfd, ??
?? scapy.contrib.automotive.obd.pid.pids_60_7F,
scapy.contrib.automotive.autosar.secoc_pdu, ??
?? scapy.contrib.automotive.obd.pid.pids_80_9F,
scapy.contrib.automotive.bmw, ?? ??
scapy.contrib.automotive.bmw.definitions,scapy.contrib.automotive.obd.pid.pids_A0_C0,
?? ??
scapy.contrib.automotive.bmw.enumerator, scapy.contrib.automotive.obd.scanner,
?? ??
scapy.contrib.automotive.bmw.hsfz, ?? scapy.contrib.automotive.obd.services,
scapy.contrib.automotive.ccp, ?? ??
scapy.contrib.automotive.doip, ?? scapy.contrib.automotive.obd.tid, ??
scapy.contrib.automotive.ecu, ?? scapy.contrib.automotive.obd.tid.tids,
scapy.contrib.automotive.gm, ?? ??
scapy.contrib.automotive.gm.gmlan, ?? scapy.contrib.automotive.scanner, ??
scapy.contrib.automotive.scanner.configuration,
scapy.contrib.automotive.gm.gmlan_ecu_states,
?? ??
249
Scapy Documentation, Release 2.6.1
scapy.contrib.exposure_notification, ??
scapy.contrib.automotive.scanner.enumerator,
?? scapy.contrib.geneve, ??
scapy.contrib.gtp, ??
scapy.contrib.automotive.scanner.executor,
?? scapy.contrib.gtp_v2, ??
scapy.contrib.automotive.scanner.graph, scapy.contrib.gxrp, ??
?? scapy.contrib.hicp, ??
scapy.contrib.homeplugav, ??
scapy.contrib.automotive.scanner.staged_test_case,
?? scapy.contrib.homepluggp, ??
scapy.contrib.homeplugsg, ??
scapy.contrib.automotive.scanner.test_case,
?? scapy.contrib.http2, ??
scapy.contrib.automotive.someip, ?? scapy.contrib.ibeacon, ??
scapy.contrib.automotive.uds, ?? scapy.contrib.icmp_extensions, ??
scapy.contrib.automotive.uds_ecu_states, scapy.contrib.ife, ??
?? scapy.contrib.igmp, ??
scapy.contrib.automotive.uds_logging, scapy.contrib.igmpv3, ??
?? scapy.contrib.ikev2, ??
scapy.contrib.automotive.uds_scan, ?? scapy.contrib.isis, ??
scapy.contrib.automotive.volkswagen, ?? scapy.contrib.isotp, ??
scapy.contrib.isotp.isotp_native_socket,
scapy.contrib.automotive.volkswagen.definitions,
?? ??
scapy.contrib.automotive.xcp, ?? scapy.contrib.isotp.isotp_packet, ??
scapy.contrib.isotp.isotp_scanner, ??
scapy.contrib.automotive.xcp.cto_commands_master,
?? scapy.contrib.isotp.isotp_soft_socket,
scapy.contrib.automotive.xcp.cto_commands_slave,??
?? scapy.contrib.isotp.isotp_utils, ??
scapy.contrib.automotive.xcp.scanner, scapy.contrib.knx, ??
?? scapy.contrib.lacp, ??
scapy.contrib.automotive.xcp.utils, ?? scapy.contrib.ldp, ??
scapy.contrib.automotive.xcp.xcp, ?? scapy.contrib.lldp, ??
scapy.contrib.avs, ?? scapy.contrib.loraphy2wan, ??
scapy.contrib.bfd, ?? scapy.contrib.ltp, ??
scapy.contrib.bgp, ?? scapy.contrib.mac_control, ??
scapy.contrib.bier, ?? scapy.contrib.macsec, ??
scapy.contrib.bp, ?? scapy.contrib.metawatch, ??
scapy.contrib.cansocket, ?? scapy.contrib.modbus, ??
scapy.contrib.cansocket_native, ?? scapy.contrib.mount, ??
scapy.contrib.cansocket_python_can, ?? scapy.contrib.mpls, ??
scapy.contrib.carp, ?? scapy.contrib.mqtt, ??
scapy.contrib.cdp, ?? scapy.contrib.mqttsn, ??
scapy.contrib.chdlc, ?? scapy.contrib.nfs, ??
scapy.contrib.coap, ?? scapy.contrib.nlm, ??
scapy.contrib.concox, ?? scapy.contrib.nrf_sniffer, ??
scapy.contrib.diameter, ?? scapy.contrib.nsh, ??
scapy.contrib.dtp, ?? scapy.contrib.oam, ??
scapy.contrib.eddystone, ?? scapy.contrib.oncrpc, ??
scapy.contrib.eigrp, ?? scapy.contrib.opc_da, ??
scapy.contrib.enipTCP, ?? scapy.contrib.openflow, ??
scapy.contrib.erspan, ?? scapy.contrib.openflow3, ??
scapy.contrib.esmc, ?? scapy.contrib.ospf, ??
scapy.contrib.ethercat, ?? scapy.contrib.pfcp, ??
scapy.contrib.etherip, ?? scapy.contrib.pim, ??
scapy.contrib.pnio, ?? scapy.layers.gprs, ??
scapy.contrib.pnio_dcp, ?? scapy.layers.gssapi, ??
scapy.contrib.pnio_rpc, ?? scapy.layers.hsrp, ??
scapy.contrib.portmap, ?? scapy.layers.http, ??
scapy.contrib.postgres, ?? scapy.layers.inet, ??
scapy.contrib.ppi_cace, ?? scapy.layers.inet6, ??
scapy.contrib.ppi_geotag, ?? scapy.layers.ipsec, ??
scapy.contrib.ripng, ?? scapy.layers.ir, ??
scapy.contrib.roce, ?? scapy.layers.isakmp, ??
scapy.contrib.rpl, ?? scapy.layers.kerberos, ??
scapy.contrib.rpl_metrics, ?? scapy.layers.l2, ??
scapy.contrib.rsvp, ?? scapy.layers.l2tp, ??
scapy.contrib.rtcp, ?? scapy.layers.ldap, ??
scapy.contrib.rtps, ?? scapy.layers.llmnr, ??
scapy.contrib.rtps.common_types, ?? scapy.layers.lltd, ??
scapy.contrib.rtps.pid_types, ?? scapy.layers.mgcp, ??
scapy.contrib.rtps.rtps, ?? scapy.layers.mobileip, ??
scapy.contrib.rtr, ?? scapy.layers.ms_nrtp, ??
scapy.contrib.rtsp, ?? scapy.layers.msrpce, ??
scapy.contrib.sdnv, ?? scapy.layers.msrpce.ept, ??
scapy.contrib.sebek, ?? scapy.layers.msrpce.msdcom, ??
scapy.contrib.send, ?? scapy.layers.msrpce.msdrsr, ??
scapy.contrib.skinny, ?? scapy.layers.msrpce.msnrpc, ??
scapy.contrib.slowprot, ?? scapy.layers.msrpce.mspac, ??
scapy.contrib.socks, ?? scapy.layers.msrpce.rpcclient, ??
scapy.contrib.stamp, ?? scapy.layers.msrpce.rpcserver, ??
scapy.contrib.stun, ?? scapy.layers.netbios, ??
scapy.contrib.tacacs, ?? scapy.layers.netflow, ??
scapy.contrib.tcpao, ?? scapy.layers.ntlm, ??
scapy.contrib.tcpros, ?? scapy.layers.ntp, ??
scapy.contrib.tzsp, ?? scapy.layers.pflog, ??
scapy.contrib.vqp, ?? scapy.layers.ppi, ??
scapy.contrib.vtp, ?? scapy.layers.ppp, ??
scapy.contrib.wireguard, ?? scapy.layers.pptp, ??
scapy.dadict, ?? scapy.layers.radius, ??
scapy.data, ?? scapy.layers.rip, ??
scapy.error, ?? scapy.layers.rtp, ??
scapy.fields, ?? scapy.layers.sctp, ??
scapy.interfaces, ?? scapy.layers.sixlowpan, ??
scapy.layers, ?? scapy.layers.skinny, ??
scapy.layers.bluetooth, ?? scapy.layers.smb, ??
scapy.layers.bluetooth4LE, ?? scapy.layers.smb2, ??
scapy.layers.can, ?? scapy.layers.smbclient, ??
scapy.layers.clns, ?? scapy.layers.smbserver, ??
scapy.layers.dcerpc, ?? scapy.layers.snmp, ??
scapy.layers.dhcp, ?? scapy.layers.spnego, ??
scapy.layers.dhcp6, ?? scapy.layers.ssh, ??
scapy.layers.dns, ?? scapy.layers.tftp, ??
scapy.layers.dot11, ?? scapy.layers.tls, ??
scapy.layers.dot15d4, ?? scapy.layers.tls.all, ??
scapy.layers.eap, ?? scapy.layers.tls.automaton, ??
scapy.layers.tls.automaton_cli, ?? scapy.utils, ??
scapy.layers.tls.automaton_srv, ?? scapy.utils6, ??
scapy.layers.tls.basefields, ?? scapy.volatile, ??
scapy.layers.tls.cert, ??
scapy.layers.tls.crypto, ??
scapy.layers.tls.crypto.all, ??
scapy.layers.tls.crypto.cipher_aead, ??
scapy.layers.tls.crypto.cipher_block,
??
scapy.layers.tls.crypto.cipher_stream,
??
scapy.layers.tls.crypto.ciphers, ??
scapy.layers.tls.crypto.common, ??
scapy.layers.tls.crypto.compression, ??
scapy.layers.tls.crypto.groups, ??
scapy.layers.tls.crypto.h_mac, ??
scapy.layers.tls.crypto.hash, ??
scapy.layers.tls.crypto.hkdf, ??
scapy.layers.tls.crypto.kx_algs, ??
scapy.layers.tls.crypto.md4, ??
scapy.layers.tls.crypto.pkcs1, ??
scapy.layers.tls.crypto.prf, ??
scapy.layers.tls.crypto.suites, ??
scapy.layers.tls.extensions, ??
scapy.layers.tls.handshake, ??
scapy.layers.tls.handshake_sslv2, ??
scapy.layers.tls.keyexchange, ??
scapy.layers.tls.keyexchange_tls13, ??
scapy.layers.tls.record, ??
scapy.layers.tls.record_sslv2, ??
scapy.layers.tls.record_tls13, ??
scapy.layers.tls.session, ??
scapy.layers.tls.tools, ??
scapy.layers.tuntap, ??
scapy.layers.usb, ??
scapy.layers.vrrp, ??
scapy.layers.vxlan, ??
scapy.layers.x509, ??
scapy.layers.zigbee, ??
scapy.main, ??
scapy.packet, ??
scapy.pipetool, ??
scapy.plist, ??
scapy.pton_ntop, ??
scapy.route, ??
scapy.route6, ??
scapy.scapypipes, ??
scapy.sendrecv, ??
scapy.sessions, ??
scapy.supersocket, ??
scapy.themes, ??
Symbols M
__init__() (TunTapInterface method), 230 m2i(), 99
Matplotlib, plot(), 40
A Multicast, 22
AsyncSniffer(), 33
P
B pdfdump(), psdump(), 19
built-in function plot(), 10
Key.string_to_key(), 201 proto (LinuxTunPacketInfo attribute), 230
wireshark(), 56
R
D RawVal, 23
DHCP, 54 rdpcap(), 19
dissecting, 102 Routing, conf.route, 40
DNS, Etherleak, 24
S
F Sending packets, send, 22
FakeAP, Dot11, wireless, WLAN, 46 sniff(), 30
fields, 111 sr(), 23
filter, sprintf(), 35 srloop(), 36
flags (LinuxTunPacketInfo attribute), 230 super socket, 29
fuzz(), fuzzing, 23 SYN Scan, 26
G T
Git, repository, 10 tables, make_table(), 39
guess_payload_class() (TunPacketInfo Traceroute, 28
method), 230 traceroute(), Traceroute, 41
TunPacketInfo (built-in class), 230
I TunTapInterface (built-in class), 230
i2h(), 99
i2m(), 99 W
WEP, unwep(), 11
K wireshark(), 55
Key.string_to_key() built-in function, 56
built-in function, 201
L
Layer, 99
LinuxTunIfReq (built-in class), 230
LinuxTunPacketInfo (built-in class), 230
253