Fingerprinting Network Packets

Robin Cowley
THG Tech Blog
Published in
14 min readFeb 21, 2022

Following on from looking at DDoS attacks in general, our next task as part of the Soteria research project, was to categorise packets based on a “fingerprint” concept. The goal of this research was to understand if it was plausible to use a fingerprint to determine which packets were malicious. When it comes to packet fingerprinting, there are two types: active and passive fingerprinting.

Active fingerprinting

Active fingerprinting is the process of gaining knowledge about the target system by actively sending packets to the machine in an attempt to illicit a response. This response (or lack of) can be used to determine certain aspects of the target machine. For example, a port scan could be used to discover the services running which could lead to a number of vulnerable areas. The main usage of active fingerprinting is to determine the OS (operating system) of a particular machine, which once known, can be used for a number of nefarious activities. However, this has drawbacks, as actively communicating with the target machine could alert the host to the potentially malicious intent.

Passive fingerprinting

Conversely, passive fingerprinting relies on sniffing packets sent from the target machine without their knowledge. This is normally done by inspecting the IP header and either the TCP header, the UDP header or the ICMP header. Specific traits of these headers can be utilised to determine which OS the target machine is using, just by looking at the fields within the headers. An example may be looking at the TTL (time to live) and window size of the incoming packet to understand which OS the packets came from.

OS typical header values
(from https://www.netresec.com/index.ashx?page=Blog&month=2011-11&post=Passive-OS-Fingerprinting ) Typical header values for different OS.

This is especially useful in defence of a server or machine, as the fingerprinting can be run on every incoming packet to inspect if they could potentially be malicious. This leads to our hypothesis: Is it possible to classify packets into benign or malicious categories by using passive fingerprinting to inspect every packet — even while under a DDoS attack?

Prior Art

Fingerprinting network packets has been a topic of investigation for many years. There have been a variety of tools that have sprung up in this space, the most notable of which are:

Ettercap

Ettercap is more of a security analysts tool than a straight-forward network packet fingerprinting library/tool. The goal of the ‘fingerprinting’ functionality in this case is to identify the OS — this then allows for determining which ‘attacks’ will be most effective. As it isn’t strictly designed for packet fingerprinting, ettercap has no easy way to show the fingerprint for each packet and doesn’t provide information about unique fingerprints.

Nmap

Nmap is another networking tool that uses ‘active stack’ fingerprinting. It describes itself as a “utility for network discovery and security auditing”. Once again slotting into the security analysts tool belt rather than a software engineer’s collection of useful libraries.

Wireshark

An essential tool for all network engineers, Wireshark is not useful in fingerprinting packets but very useful for inspecting generated packets and checking values in the packets and debugging fingerprinting issues.

p0f

Passive fingerprinting tool. Handles TCP well but not UDP. Uses quirks of the packet to determine OS etc. Effective and has become a de facto standard in packet fingerprinting over the years.

Prads

Inspired by p0f, “passively listens to network traffic”. Fingerprint output is similar to p0f, however, the order of output is slightly different. Different list of quirk checks from p0f. Useful to combine Prads and p0f when understanding differences in quirks to identify packets.

PacketFence

A “network access control” project (or a firewall). This project contains code for fingerprinting DHCP packets, unlike p0f and prads, this is a golang project so the code is harder to integrate into the team’s existing C code.

Satori

This is a Python rewrite of a previous windows based tool, which supports fingerprinting for TCP, DHCP, HTTP and SMB packets. However, because it is written in Python, it is significantly slower at parsing packets than the C based fingerprinting programs.

Choosing a starting point

From the start of the project, we quickly realised that we needed the performance of C over the ease development of Python. With Python, there are a few helpful libraries, the standout and most popular of these is scapy. Scapy allows for a relatively simple parsing of packets compared to C, as the library has APIs to do most of the work for you. Conversely, using C requires a deeper understanding of the make up of packets and the individual bits that are used in fingerprinting which takes longer to get up and running.

This is where p0f and prads come in. As C programs, they contain useful information on the parsing of packets at a low level which was vital in our research. Both of these programs contain extra network or flow related functionality, such as connection tracking or table lookups to cross reference these packet traits (signatures) against OS traits that have been observed. This functionality is helpful, in their case, to fingerprint the first packet of a TCP flow or HTTP at the application layer. Nevertheless, for our DDoS use case, this functionality is irrelevant, as we are looking to parse packets as quickly and efficiently as possible. From this, we just focused on the packet parsing and traits/signatures returned from packets.

P0f signatures

P0f has a concept called a “raw_sig”, which contains traits that the creator believed best represent a particular OS and that does not contain any information about the flow (five tuple). This raw_sig layout for p0f consists of:

What the p0f signature represents
Taken from section 5 of https://lcamtuf.coredump.cx/p0f3/README

What each part of the signature means:

Ver:

IP version of the packet, which will either be 4 or 6. This is found within the IP header.

Ittl (Initial time to live):

This denotes the maximum hop count of the packet to stop routing loops. Each OS uses a different value here. For each hop the packet takes, the TTL is decremented by 1. This is found within the IP header.

Olen (option length):

This is the length of the options in the IP header. This is normally zero for IPv4 and is always zero for IPv6.

Mss (maximum segment size):

This is the largest amount of data in bytes that TCP is willing to receive in one segment. This is normally announced when the TCP connection is established.

Wsize (Window size):

This states the number of bytes the sender is willing to receive. Up to a maximum of 65535 due to the 16 bit nature of the field.

Scale (Window scale size):

This is the option to increase the receive window size above the maximum. This would be found in the TCP options if it is present.

Olayout (Option layout):

This is found within the TCP header and describes the ordering of the options.

Option layout and what the strings represent

Quirks:

These quirks come from interesting aspects of the IP header and TCP header. They are found throughout the layer 3 and 4 headers, they are defined as:

Pclass (Payload class):

This describes the size of the payload, whether it is zero or non-zero.

Adaptation

As p0f only works on TCP packets, by applying a BPF (Berkeley Packet Filter) rule which ignores any network traffic other than TCP, we had to adapt the existing code base to parse more layer 4 protocols. Namely, these are: UDP, ICMP and GRE. BPF allows a userspace process to supply a rule that specifies which packets to receive. In this case, we were using libbpf to wrapper the BPF API calls. The libbpf function used to set the BPF rule was:

int	pcap_setfilter(pcap_t *, struct bpf_program *);

While TCP headers are richer in useful information to identify a particular packet, UDP, ICMP and GRE still contain the IP headers and small layer 4 headers. These can still be incredibly valuable, even if they provide less information. A DDoS attack can still utilise these protocols so they have to be included. This meant, altering p0f to allow UDP, ICMP and GRE through the BPF rule and adding parsing for these 3 protocols. When this had been achieved, it was possible to pass malicious or benign PCAPs through the program to test our hypothesis. This hypothesis was that most malicious packets under a DDoS scenario will contain the same or similar signatures as they are being sent from the same program. This altered p0f code would act as our PoC (proof of concept) code initially, act as a way to test the hypothesis and act as a teaching material to learn more about packets and networking.

PCAPs

Having a PoC codebase is a start; however, finding relevant and useful PCAPs is another challenge. Luckily, our team had already spent time researching and generating PCAPs from a variety of DDoS tools that are available online. In addition to this, we were able to capture a couple of PCAPs from our internal infrastructure that contain purely benign traffic. The tools and dataset we used to generate the malicious PCAPs, were:

  • Bonesi — A botnet simulator, to simulate traffic on the wire.
  • Hping3 — An open source packet generator.
  • TRex — An open source low cost traffic generator.
  • CIC dataset — Canadian institute of cybersecurity generated PCAPs.
  • Internal PCAPs — Legitimate benign and malicious traffic.

Analysis

Now, using this altered p0f PoC code, we could start looking at the distribution of signatures in benign and malicious traffic.

To get a benchmark, the purely benign PCAPs from our infrastructure would show the usual distribution of packets and signatures. We will be looking at two separate PCAPs, to compare results.

Size of PCAPs and number of signatures seen

From these results, we can deduce that there are roughly 7.5 packets per signature seen.

Additionally, it is possible to count how often each signature is seen and then chart this to visualise the distribution of the data.

Graph 1
The number of times each signature appears in PCAP 1. The signature number represents the index of the signature when the signatures are arranged in ascending order of how many times they appear.
Graph 2
The number of times each signature appears in PCAP 2. The signature number represents the index of the signature when the signatures are arranged in ascending order.

From graph 1 and graph 2, it is clear to see that the signatures that appear the most, skew the data a lot.

In fact, for PCAP 1, the top 300 signatures (5 percent), account for 33430 packets. This is 67.8 percent of all packets in the PCAP being represented by the top 5 percent of the signatures.

This is surprising at first, as it was expected that the dataset would be more uniform, as the packets are coming from a vast amount of IPs. However, as all the traffic in PCAP 1 is destined for the same server, it can be deduced that a lot of the traffic will contain similar requests. Furthermore, most of the traffic will be from the same modern operating systems, which contain similar TTL’s, window sizes and TCP options so it is expected that a few signatures will have a larger count.

In comparison to this, we ran a PCAP that contained a 100,000 packet SYN flood from the CIC dataset.

Graph 3
Log graph to show the number of times each signature appears in the CIC dataset SYN flood.

As evidenced by graph 3 above, only 10 unique signatures appeared within this dataset. With one signature in particular representing over 99.9 percent of the total packets. This result adds confirmation to the hypothesis, that signatures are able to represent multiple packets coming from the same piece of DDoS software. However, the result of this might also suggest that the generated attack does not contain enough packets with differing fields in them. As the CIC dataset does not contain information on how it generates malicious traffic, it can assumed from this that not a very sophisticated attack tool was used for this particular PCAP.

This led the research onto looking at existing DDoS tools (referenced above), that output a larger variety of packets, to further confirm our hypothesis that packet signatures are a viable way of determining packet maliciousness.

Hping3

To begin with, Hping3 seemed like the best place to start for malicious packets, as it is one of the more well known DDoS attacking tools online. Additionally, the distribution of IPs and ports can be randomised by the command line. At first glance, this would seem like a distributed attack from multiple clients, if only using the five tuple to classify packets. Lets run this through the p0f PoC code to get the unique signatures and the amount of times they appear:

50270 4:tcp:64:0:0:512:0:::0
49730 4:tcp:64:0:0:512:0::ack+:0

An interesting result! Once you look beyond the five-tuple, the rest of the packets follow the same pattern, making it trivial to classify. It seems that Hping3 does not randomise any of the fields within the packets, apart from those contained in the five tuple. Therefore, it can be seen that if a particular program only randomises the five tuple and keeps the rest of the fields the same, it would be simple to see the attack packets.

Bonesi

Bonesi is described as a DDoS botnet simulator, used to study the effect of DDoS attacks. This suggests that at the very least, the ports and IP’s would be randomised. To find out if Bonesi is doing anything else with the packets, one could either look at the source code or check the distribution of signatures.

Graph 4
The number of times each signature appears in a 100,000 PCAP of a Bonesi SYN flood.

As seen by graph 4, we see 1774 signatures in 100,000 packets which is roughly one signature per 50 packets. Compared to the internal PCAPs captured, Bonesi still has significantly less variation in the signatures outputted. However, compared to Hping3, Bonesi actually manipulates fields within the packet that are not contained in the five tuple. In addition to this, the distribution range of signatures is a lot smaller in the Bonesi graph. The internal PCAP in graph 1, has a signature seen 978 times, where as the signature seen most in the Bonesi PCAP is only seen 102 times. This actually makes sense, as if Bonesi is randomising a couple of fields within the packet, it is expected that there would be fewer outliers. So, what are the fields that Bonesi is changing to produce this range of signatures.

We can have a look within the signatures to see:

Sig num  Count  IP count  Signature
1774 102 102 4:41+23:0:1516:4096,0:mss,nop,sok,eol+0::0
1773 97 97 4:148+?:0:1460:4096,0:mss,nop,sok,eol+0::0 1772 97 97 4:178+?:0:1460:4096,0:mss,nop,sok,eol+0::0 1771 97 97 4:19+13:0:1460:4096,0:mss,nop,sok,eol+0::0 1770 97 97 4:70+?:0:1516:4096,0:mss,nop,sok,eol+0::0

From the 5 most populous signatures seen above, it is clear to see that Bonesi changes the TTL and the MSS of the packet. This makes sense as to why there are more signatures for Bonesi, because it tries to manipulate the packet underneath the five tuple. This makes the detection of particular malicious packets harder, but not impossible.

For example, a Bonesi DDoS attack is happening, on top of our internal PCAP 1, in which we received 50,000 packets in around 210 seconds. Therefore, if we scale up the Bonesi attack, to 100,000 packets per second then we would receive 21 million of these packets in 210 seconds. As seen from graph 4, most of the Bonesi signatures appear 20 times or more, so if we multiply the count seen in 100,000 packets by 210, for the amount of seconds the figurative attack would take. The least amount that most of the Bonesi signatures would appear would be (20 * 210) = 4200.
This would result in a relatively simple analysis again. Compared to the signature in the internal PCAP 1 that was seen the most, at 978 times. The Bonesi signatures would greatly out number any of PCAP 1’s signatures. Consequently, it would be possible to block any packet that has the same signature as any of the packets that appear 4000+ times.

This is just a hypothetical scenario; however, it shows the possibility of being able to identify malicious packets and signatures by the amount of times they appear in a certain window, compared to the norm.

Internal UDP flood

During the process of research and analysis, our team were lucky (or unlucky) enough to come across a low level UDP flood hitting a server. This resulted in being able to capture a small PCAP with the attack included. Furthermore, as a comparison, a PCAP was captured on the same server at a similar time with purely benign traffic. Instead of solely analysing the distribution of signatures within the PCAPs, another approach to detecting an attack would be to inspect packets per second (PPS) and the number of signatures per second. This was done for the internal UDP flood and benign the PCAP, which resulted in a couple of time series graphs.

Graph 5
A graph to show the count of PPS and signatures per second from the internal UDP flood.

From graph 5, it is clear to see when the UDP flood is occurring, which is whenever a spike happens. This is expected, as UDP floods rely on saturating the bandwidth. Additionally, if the all of the traffic was legitimate, it would be expected that the shape of the unique signature count would mirror the packets per second. However, we can see this is not the case, as referenced 21 seconds into the PCAP, the packet per second rate increases a large amount, but unique signature count stays constant. This suggests that a reasonable next step would be to check the ratio of packets per second compared to the number of signatures present.

Graph 6
A graph to show the ratio of PPS to signatures from the internal UDP flood.

Graph 6 shows, how similar to graph 5, the spikes in the ratio of PPS per signature denote when the UDP flood is taking place. In addition to being able to tell when the attack is happening, we now have the specific signatures that are causing this spike in packets. This could result in blocking future packets that have the same signature as the most commonly occurring signatures within the spikes of traffic. This analysis furthers the evidence that passive packet fingerprinting to obtain signatures could result in a DDoS mitigation software.

Benign PCAP for comparison

Graph 7
The PPS and count of signatures seen per second from the internal benign PCAP.
Graph 8
A graph to show the ratio of PPS to signatures from the internal benign PCAP

Graphs 7 and 8 show how the benign traffic follows the expected trends. It can be seen from graph 7, that as the PPS count increases during the first couple of seconds, the unique signature count has a visible increase too. Comparing this to the ratios of graph 8, in the first couple of seconds we can see the ratio is around 6.5, which is high in comparison to the rest of the graph. However, when comparing the ratio of 6.5 PPS to signatures in graph 8, to the peaks of 20–180 PPS to signatures in graph 7, it reveals how the ratio differs from malicious to benign PCAPs.

This research enabled the team to learn about the intricacies of packet headers, DDoS attack vectors and networking in general. From creating our own malicious PCAPs using open source software, to researching ways to defend against them, allowed us to understand what attackers are aiming to do during an attack. We can now take this gained knowledge and apply what we have learnt onto viable solutions.

Thanks to Kev Jackson and Abbey Woodyear.

--

--