Skip to main content

3 posts tagged with "Python"

View All Tags

· 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


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
RUN curl -O${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/

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

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/"]

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'

driver: journald

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

container_name: supernexmon
context: .
labels: *default-labels
logging: *default-logging
restart: always
network_mode: host
privileged: true
- /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)

err = exec.Command("/nexmon/nexutil", "-k"+channel).Run()
if err != nil {
http.Error(w, err.Error(), 400)
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.


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 /


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


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

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)


root@wifilab0:~/barely-ap/src# python3                                                                                                                  
command failed: Device or resource busy (-16)
Created TUN interface scapyap at 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

· 3 min read

Reducing Attack Surfaces (Part 1)

SPR lets users create adaptive, micro-segmented networks for connecting and managing devices. In addition to fine-grained network visibility we also build hardened software and work to avoid common security flaws. As SPR has matured we've started taking on further efforts to eliminate attack surfaces.

When it comes to native code: we introduce none. As in, we have not written new native code for SPR anywhere. We have one BPF filter, and its otherwise golang all the way down. We also do not run standard native services where we can avoid them. We have replaced traditional C code for services such as DNS and DHCP with golang implementations, namely CoreDNS and CoreDHCP.

The remaining native code targets that we have in SPR are as follows:

  • The Linux kernel. For example: ethernet, the tcp/ip stack, nftables, the mac80211 framework and vendor drivers
  • 802.11 Firmware, Ethernet Firmware
  • Hostapd
  • PPP Daemon (off by default)
  • OS Services (Ubuntu)

Targeting the Whole WiFi Stack

We believe the wifi firmware to be today's most insecure target (along with the vendor drivers). Many firmwares are blackbox, poorly documented, and opaque to public security research. We want SPR to be immune to attacks like Broadpwn and Qualcomm Exploitation.

We've previously published barely-ap to teach people about WiFi authentication. It can and does work with real wifi chips running in monitor mode to connect clients over the air. We've tested with Android, iOS, and Linux devices.

The plan is to build a series of experiments to host high-speed wifi.

In the near term:

  1. Develop a Proof-of-Concept AP with scapy in monitor mode (DONE)
  2. Develop a shim from monitor frames to hostapd running under mac80211_hwsim. This is a work in progress. We would like to see a rust kernel driver/userland daemon for this


  1. A full AP written in rust, operating on raw 802.11 frames (not relying on the Linux kernel 802.11 subsystem)
  2. Rust protocol firmware for a wifi chip.

Developing a Shim Explained

By running the card in monitor mode, protocol parsing in the card firmware is substantially reduced if not altogether eliminated.

And with relaying frames over to macsim, hostapd is good to go. What needs to happen however is making this incredibly fast, and researching rate negotiation and what calls might need to be made to firmware to enable higher coding rates.

By using hostapd and the kernel mac80211 stack, we still maintain some native attack surface, however we get a known working, security-tested AP that will be compatible with a wide variety of devices, without the firmware protocol parsing and the vendor driver parsing.

For next steps, a proof-of-concept with scapy is actually much too slow. We want to start with a rust userland daemon leveraging iouring. If that doesn't fly then we'll go to a shim in the kernel.

Interested in working with us? Please reach out

We are actively seeking an intern to help develop rust+wifi for SPR.

You can contact us at spr-wifi [ a-t ] or hop on the discord

· One min read

Introducing Barely AP

We've published barely an implementation of a WiFi 802.11 Access Point, using Scapy to teach people about WiFi authentication.


On Linux, this code lets you spin up a python access point over monitor mode. It implements features like handling probe requests, authentication, association, and reassociation, and encryption and decryption of data using CCMP (Counter Mode Cipher Block Chaining Message Authentication Code Protocol).


This code just barely gets the job done -- it should NOT be used as a reference for writing production code. It has NO protocol security, as it is not security robust despite performing authenticated CCMP encryption.


Building & running


Inspect IP traffic

docker exec -it barely-ap tcpdump -i scapyap
docker exec -it barely-sta tcpdump -i wlan1