Skip to content
Michael Stahn edited this page May 13, 2018 · 22 revisions

This is a general user-introduction to pypacker and will cover the topics of creating, analyzing, sending and receiving packets. For a quick dive-into check out the examples in the /examples directory.

Needed imports

In order to do anything with pypacker all relevant layers have to be imported. This will look like: "from pypacker.layerX import PacketClass". The following statement will import the ethernet layer:

from pypacker.layer12 import ethernet

Now the Ethernet class contained in the ethernet packet can be used:

ether = ethernet.Ethernet()

The following examples assume all relevant layers and classes have been imported.

Creating custom packets

There are two ways to create custom packets: Using keyword constructor or giving raw bytes:

# keyword consturctor
ether = Ethernet(src_s="aa:bb:cc:dd:ee:ff")
# init using raw bytes
ether = Ethernet(b"\xAA\xBB\xCC\xDD\xEE\xFF\xAA\xBB\xCC\xDD\xEE\xFF\x00\x00\xFF\xFF")

Note that only the src address was given using the keyword constructor. Generally all fields left out will be set to default values defined in the correspondig class. Another handy feature is the auto-conversion seen in the keyword example: using the format "varname_s" generally all MAC-, IP-address or DNS-names can be set using normal string representation. Setting values using raw bytes is supported after all e.g. via 'src=b"\xAA\xBB\xCC\xDD\xEE\xFF'.

After instantiation all header fields can be further changed:

# change destination like this..a
ether.dst_s="aa:bb:cc:dd:ee:ff"
# ..this is the same as that
ether.dst= b"\xAA\xBB\xCC\xDD\xEE\xFF"
ether.bin()

Packets can contain simple (static and dynamic) and list style fields (aka TriggerList). Simple ones are like python integers or byte strings. Byte fields have a fixed length except some rare cases (see dns.DNS.Query -> name) which can be identified via header format "None". All simple fields can be deactivated by setting the value to "None" and re-activated by setting an appropriate value.

In contrast to this, list style headers can contain Packets, tuples as key/value pairs and bytes. The kind of content for this header depends on the class itself. List style headers can be identified via format "None" and a TriggerList-like value. As simple fields already have been introduced the following examples will clear up handling TriggerList ones:

# init packet having TriggerList fields
# single value
ip = IP(src_s="127.0.0.1", opts=[IPOptMulti(type=IP_OPT_TS, len=3, body_bytes=b"\x00\x01")])
# multiple values
ip = IP(src_s="127.0.0.1", opts=[IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"), IPOptSingle(type=IP_OPT_EOOL)])
# change TriggerList field after instantiation
ip.opts=IPOptSingle(type=IP_OPT_EOOL)

TriggerList fields generally expect raw bytes, tuples or Packets itself depending on the implementation. As TriggerList fields are basically python lists itself, all list operations can be applied, too:

del ip.opts[0]
ip.opts.insert(1, IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"))
ip.opts.append(IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"))

Layers can be concatenated using "+":

# create a new packet composed of concatenated ethernet, ip and tcp layer
packet = Ethernet() + IP() + TCP()
# dump raw bytes of all concatenated layers
packet.bin()

Note: Changes to a packet will also change the original packet instances used to build "packet" and vise versa:

ip1 = IP()
tcp1 = TCP()
packet = ip1 + tcp1
# this will also change "p" in ip1
packet.p = 123
# this will also change packet.tcp.sport
tcp1.sport = 123

Note: Auto-update fields like checksums, lengths etc. (eg "sum" in IP) will not be updated until calling bin(). This can be disabled by calling bin(update_auto_fields=False) to set custom values eg for fuzzing purposes.

Analyzing packets

All header values can be retrieved like in the previous examples. Present layers can be accessed using the index or dot notation::

# Example packet, whatever it might be.
pkt_ethernet = Ethernet() + IP() + TCP() + Telnet()
# Search for TCP in constallation "Ethernet + IP + TCP" (layer above TCP don't matter)
if pkt_ethernet[Ethernet, IP, TCP]:
    print("TCP layer found: %s" % pkt[Ethernet, IP, TCP])
# Search first occurence of TCP (warning: parses until TCP is found or highest layer reached)
tcp = pkt_ethernet[TCP]
# TCP layer found
if tcp is not None:
    print("TCP layer found: %s" % tcp)
# A more generic way to check for a layer (assuming 2 additional layers present above Ethernet)
if pkt_ethernet.upper_layer.upper_layer.__class__ == TCP.__class__:
    print("TCP layer found: %s" % pkt_ethernet.upper_layer.upper_layer)
# This is equivalent to the next example (assuming IP and TCP is really present)
tcp = pkt_ethernet.ip.tcp
# ..which is equivalent to the next example
tcp = pkt_ethernet.upper_layer.upper_layer

To check if a packet matches an other e.g. to find an answer packet, using the direction() method can become in handy. This will return the constants DIR_SAME, DIR_REV or DIR_UNKNOWN or an "OR"-concatination of some::

# DIR_SAME, DIR_REV, DIR_UNKNOWN or combination of any of them
direction = ether1.direction
# check direction explicitly
if ether1.is_direction(ether2, DIR_REV):
	print("found answer packet!")

Read/write packets from/to file

Pypacker supports reading and writing pcap files. The ppcap module is responsible for this task:

from pypacker import ppcap
# open file to be read
pcap = ppcap.Reader(filename="my_packets.pcap")
# cycle through all packets
for ts, buf in pcap:
	eth = ethernet.Ethernet(buf)
	print("time (nanoseconds): %d, packet: %s:" % (ts, eth))
pcap.close()

Send and receive packets

Using the SocketHndl class one can send and receive packets at OSI layer 2 and 3. This supports simple receiving/sending, advanced receiving using filters and send/receiving auto-matching answers. The following examples will clear this up:

from pypacker import psocket

# open sockets using the socket handler
sock_l2 = psocket.SocketHndl(iface_name="eth0", mode=psocket.SocketHndl.MODE_LAYER_2)
sock_l3 = psocket.SocketHndl(iface_name="eth0", mode=psocket.SocketHndl.MODE_LAYER_3)
# send raw bytes
sock_l2.send(ether.bin())
sock_l3.send(ether.bin(), "127.0.0.1")
# receive arbitrary bytes
bts = sock_l2.recv()
# receive packets: raw bytes will be internally used to create packets
pkts = socket_l2.recvp(filter=lambda p: p[IP].src=="127.0.0.1", lowest_layer=Ethernet)
# send packets and auto-match answers: those will be returned
pkts = socket_l2.sr(Ethernet() + IP() + TCP(), lowest_layer=ethernet.Ethernet)
print("answer was: %s" % pkts[0])
# close sockets
sock_l2.close()
sock_l3.close()