Anonymizing sniffer traces using Scapy
Manipulating MAC addresses, hiding key material, guarding privacy and general obfuscation.
Manipulation of sniffer traces is something which isn’t often done, but there are valid cases for doing so. One such reason is to anonymize a sniffer trace when sharing with a wider audience for educational or debug purposes. Anonymizing a trace involves modification of various fields or values within the trace, to hide data or to keep personal identifiers (MAC addresses) private.
There are a few tools which can help in changing the contents of sniffer traces, we’ll concentrate on one in particular: Scapy1.
In this article we’ll use Scapy to anonymize the MAC address of a client inside a trace file, and hide the contents of EAPOL-KEY packets to remove the possibility of offline dictionary attacks. We’ll use the 2.5.0 version of Scapy, the latest stable release.
Scapy
We learned a little about Scapy in a previous article (Over the air penetration testing and protocol fuzzing), in which I used Scapy to implement a broadcast deauth attack. Scapy is great for such rapid turnaround scripting, supporting live monitor mode receive and arbitrary packet transmit. Scapy also has another very powerful use as a batch processor which can analyse, manipulate and even forge sniffer traces.
Scapy has the ability to read and write pcap and pcapng files, meaning that any sniffer trace captured in this format can be read in and the packets and their inner layers (RadioTap2, L2→L7) can be analysed and modified. We’ll look into modification of sniffer traces in this article.
All of the scripts developed while writing this article are available on my github WiFi Diving project3 here. Feel free to re-use the scripts, they are unburdened by any license and provided for educational purposes.
Reading pcap files and parsing packets
The simplest method to read in a pcap file using Scapy is using the PcapReader class (or the PcapNgReader class for pcapng files). There is also a helper function rdpcap which wraps the PcapReader and PcapNgReader classes, which may be more convenient for smaller trace files.
The following script is an example of reading in and printing each of the packets in a sniffer file (script on GitHub, the trace file being used is available on my shared Google drive here).
# This script reads in and prints all the packets in a trace to the output.
from scapy.all import *
filename = "initial-connection-plus-ping.pcap"
pcap_reader = PcapReader(filename)
for pkt in pcap_reader:
print(pkt)
The output looks similar to the following screen snip:
Super simple!
If we drop to the interactive Python interpreter, we can use the same code as above to instantiate a PcapReader object, then read packets and look deeper inside them.
Manipulating the MAC address of packets
The MAC address used by a device is a globally unique 48-bit number consisting of an organisation identifier (the Organizationally Unique Identifier - OUI), which takes up 24-bits (22-bits in reality due to the I/G and U/L bits), with the second 24-bits being filled in by the device vendor generally in increasing order.
The list of currently assigned OUIs (MA-L) is freely available on the IEEE SA website, here. The combination of the OUI + MAC address uniquely identifies a device worldwide (with the U/L bit cleared), hence is a thread to user privacy through tracking of this globally unique MAC address.
The MAC address is such a threat to user privacy that device vendors now support MAC address randomization. MAC address randomization attempts to restrict the ability of public networks to track devices via their globally unique MAC address. A good overview article of MAC address randomization can be found here.
However, in some situations a device may keep it’s globally assigned MAC address, such as with older or legacy devices that don’t support random MAC addresses. Anonymizing these addresses is important to retain privacy when sharing sniffer traces.
Using Scapy, we can easily manipulate the various address fields of each of the packets. A script like the following works quite well (GitHub link):
# This script will replace MAC addresses in the addr1 -> addr4 fields
# based on the mac_map_list dictionary
from scapy.all import *
pcap_reader = PcapReader("initial-connection-plus-ping.pcap")
def substitute_mac(pkt, _mac_map_list):
# The fields 'addr1' -> 'addr4' are the internal Scapy addresses
for addr_id in ["addr1", "addr2", "addr3", "addr4"]:
# See if this pkt has the given MAC address identifier
if hasattr(pkt, addr_id):
# The actual MAC address for the field addr_in
addr = getattr(pkt, addr_id)
if addr in _mac_map_list:
# Hit - this 'addr_id' field is one of the map addrs
# Replace it with the mapped address
setattr(pkt, addr_id, _mac_map_list[addr])
for pkt in pcap_reader:
# Modify this as appropriate
mac_map_list = {"00:26:86:f0:7d:67": "02:2F:3A:4B:5C:6D",
"2c:cf:67:01:0b:a3": "02:01:02:03:04:5F"}
substitute_mac(pkt, mac_map_list)
This script will modify the address 1, 2, 3 and 4 values in each packet based on keys in the ‘mac_map_list’ variable. See the IEEE Std 802.11™️‐2020 document4 section 9.2.3 for details of the four addresses possible in regular frames.
Obfuscating the EAPOL-KEY packet
The second trace modification we will examine in this article is how to obscure EAPOL-KEY packets. These packets are used in the key derivation phases of WPA/WPA2/WPA3 connections. The EAPOL-KEY packets can be used to perform offline dictionary attacks for WPA and WPA2, meaning anything using these protocols can be compromised through analysis of a sniffer trace containing the EAPOL-KEY packets.
Within Scapy, we can quickly locate EAPOL-KEY packets using a script looking something like below (GitHub link):
# This script will read in and print any EAPOL-KEY packets to the output along
# with their packet number within the trace.
from scapy.all import *
count = 1
pcap_reader = PcapReader("initial-connection-plus-ping.pcap")
eapol_pkts = []
for pkt in pcap_reader:
# haslayer can find all layers of the packet and be used to filter out
# what the application needs.
if pkt.haslayer(EAPOL_KEY):
print(f"Packet {count} is an EAPOL-KEY packet:")
print(pkt)
eapol_pkts.append(pkt)
count += 1
If we examine the contents of the EAPOL-KEY packet through the interactive Python interpreter, we see the different fields, and get hints as to what we should manipulate.
The fields in the EAPOL-KEY packet which we want to mangle are (Scapy field names):
key_nonce
key_iv
key_rsc
key_id
key_mic
Once again this is easily achieved with Scapy, a simple script such as the following will work:
# This script will read in a file and modify EAPOL packets by inserting
# random data into the key_nonce, key_iv, key_rsc, key_id and key_mic fields.
# Additional code is needed to write this back to a pcap file.
from scapy.all import *
filename = "initial-connection-plus-ping.pcap"
pcap_reader = PcapReader(filename)
def obfuscate_eapol_key(eapol_key):
# The attributes we want to replace
attributes = ["key_nonce", "key_iv", "key_rsc", "key_id", "key_mic"]
for attribute in attributes:
# Replace each attribute with some random values - use os.urandom
print(f"Attribute {attribute}: {getattr(eapol_key, attribute)}")
setattr(eapol_key, attribute, os.urandom(len(getattr(eapol_key, attribute))))
print(f"New attribute {attribute}: {getattr(eapol_key, attribute)}")
# Main loop
for pkt in pcap_reader:
if pkt.haslayer(EAPOL_KEY):
obfuscate_eapol_key(pkt)
One thing to note is that the operations performed when deriving the key material for WPA/WPA2 are dependent on the MAC address of the supplicant (STA) and authenticator (AP) involved in the key exchanges. Section 12.7.1.3 of the IEEE Std 802.11™️‐2020 document outlines the Pseudorandom Function (PRF), which includes concatenation of the supplicant and authenticator MAC addresses along with other data to derive the Pairwise Master Key (PMK). That is, if we obscure the MAC addresses of the EAPOL-KEY frames then that should be enough to prevent dictionary attack. Regardless of this, it can’t harm in obfuscating the EAPOL-KEY fields further, especially when it’s trivial to do so.
Saving the modified PCAP file
Scapy provides a very simple interface to write back the pcap file:
scapy.utils.wrpcap(filename: IO[bytes] | str, pkt: _PacketIterable, *args: Any, **kargs: Any)→ None
A complete script which will anonymize both the MAC addresses as well as the EAPOL-KEY frames is provided below (GitHub link):
# This script is a combination of the different pieces and can do a simple
# obfuscation of a sniffer trace file by replacing the MAC addresses of certain
# devices, and by randomising the data within the EAPOL-KEY packets.
# This provides some level of anonymity, but further changes would be necessary
# to ensure all user identifiable data is purged/altered.
from scapy.all import *
filename = "initial-connection-plus-ping.pcap"
pcap_reader = PcapReader(filename)
# Take a packet and a dictionary of MAC addresses to replace, and do
# the necessary for all of the addresses found in the packet.
# If any of the addresses are changed, invalidate the FCS so that
# Scapy will regenerate the FCS when writing back to a pcap file.
def substitute_mac(pkt, _mac_map_list):
# The fields 'addr1' -> 'addr4' are the internal Scapy addresses
for addr_id in ["addr1", "addr2", "addr3", "addr4"]:
# See if this pkt has the given MAC address identifier
if hasattr(pkt, addr_id):
# The actual MAC address for the field addr_in
addr = getattr(pkt, addr_id)
if addr in _mac_map_list:
# Hit - this 'addr_id' field is one of the map addrs
print(f"Replacing address {addr} with {_mac_map_list[addr]}")
# Replace it with the mapped address
setattr(pkt, addr_id, _mac_map_list[addr])
# See below! pkt.fcs = None
# Take an EAPOL-KEY packet and obfuscate the fields which could be used
# for offline dictionary attacks (nonce, iv, rsc, id and mic fields).
def obfuscate_eapol_key(eapol_key):
# The attributes we want to replace
attributes = ["key_nonce", "key_iv", "key_rsc", "key_id", "key_mic"]
for attribute in attributes:
# Replace each attribute with some random values
print(f"Attribute {attribute}: {getattr(eapol_key, attribute)}")
setattr(eapol_key, attribute, os.urandom(len(getattr(eapol_key, attribute))))
print(f"New attribute {attribute}: {getattr(eapol_key, attribute)}")
# See below! pkt.fcs = None
# Save all the packets to write them out. Warning: this won't work well for large traces!
all_pkts = []
# Main loop, do the necessary changes to all packets as necessary.
for pkt in pcap_reader:
# Substitute MAC address dictionary
mac_map_list = {"00:26:86:f0:7d:67": "02:2F:3A:4B:5C:6D",
"2c:cf:67:01:0b:a3": "02:01:02:03:04:5F"}
# Update the MAC address if necessary
substitute_mac(pkt, mac_map_list)
# Update and obfuscate the EAPOL-KEY frame fields
if pkt.haslayer(EAPOL_KEY):
obfuscate_eapol_key(pkt)
# Save the packet regardless of update
all_pkts.append(pkt)
# Write the packets back out to a pcap file
wrpcap(f"obfuscated_{filename}", all_pkts)
However, if we open the PCAP file in Wireshark, we notice something is wrong:
Here we notice that the FCS on all of the modified frames are incorrect. This is not surprising as we have modified the fields within the packet, but unless we explicitly tell Scapy to recalculate the FCS, the value within any modified packet will keep the old (incorrect) value. Fortunately, Scapy once again gives us a simple method to recalculate the FCS (this line is commented out in the full script above):
pkt.fcs = None
If this is added into the appropriate places within the script (anywhere the packet contents are modified), when the pcap file is written out, Scapy will recalculate the FCS on the given frame and populate the updated value automatically. The corrected pcap file when opened inside Wireshark shows valid FCS calculations for the modified packets.
Note: Enabling FCS check within Wireshark is via the Preferences → Advanced → wlan.check_checksum and wlan.check_fcs boolean variables. To have Wireshark confirm the FCS on the packets, change both of these booleans to ‘TRUE’ by double clicking on them.
At this point, we’ve successfully modified the pcap trace and removed some of the user identifiable data, and no-one is any the wiser!
The script we’ve developed can be easily extended to obfuscate more fields within the trace file. Some of the fields/IEs which may need anonymizing are:
SSID - this may be used to identify the physical location of a network if it is part of a wardriving database.
IEs within beacons/probe requests/responses which may identify the type of device in the network or the AP device, raising the possibility of external attacks.
Any non-encrypted data which may be on the network.
The script we have developed can be used as a basis for your anonymizer tool.
Other tools
The Wireshark wiki mentions a whole bunch of additional tools which can be used for packet manipulation, the link is here. There are many tools available but in my opinion, none of them come close to the power of Scapy. A few of these alternative tools are mentioned below.
editcap
The editcap utility ships as part of the wireshark application, and has some basic tools which allow:
Splitting of pcap files
Truncating packet lengths (helps with sniffer trace size reduction when sharing)
Adjusting timestamps of packets in the trace
Duplicate packet removal (useful when multiple sniffers are used to capture all data on a given wireless network)
There is little within the editcap tool to make it generically useful, but it is useful as part of an overall trace pipeline, especially when dealing with very large files.
tcprewrite
The tcprewrite tool offers some reasonably powerful options for manipulating fields within packets inside of a pcap file and can read in 802.11 (RadioTap) traces, but unfortunately does not offer the ability to output the resulting re-written file in RadioTap format.
wireedit
A very interesting alternative to Scapy, one which supports both a UI and command line APIs, is wireedit.
wireedit is provided as a commercial offering from a company called Omnipacket. The tool capabilities can be found on their website: https://omnipacket.com/wireedit
The tool does look very powerful, but the cost will be prohibitive for all but the best funded hackers out there :)
Summary
In this article we’ve used Scapy to anonymize a wireless sniffer pcap file. The scripts developed allow changing of arbitrary MAC addresses within the trace file, as well as obfuscating the important fields within the EAPOL-KEY frames.
The power of Scapy is shown in that the scripts written for this article took less than an hour to fully develop.
Do you use Scapy in your day-to-day work? How have you previously used it? Join me in the WiFi Diving chat and we can talk about this powerful tool!
GitHub Scapy project:
RadioTap definition website:
GitHub for WiFi Diving, sniffer trace anonymizing Scapy scripts:
https://github.com/rkinder2023/wifidiving/tree/main/source/anonymizer
IEEE Standard for Information Technology — Telecommunications and Information Exchange between Systems Local and Metropolitan Area Networks — Specific Requirements.
Part 11: Wireless LAN Medium Access Control (MAC) and Physical Layer (PHY) Specifications (IEEE Std 802.11™️‐2020) - https://ieeexplore.ieee.org/document/9363693