NewFPV / documentation

PWMENU WIKI

The complete guide to installing, configuring, and safely operating A_pwmenu on Pwnagotchi.

Version 1.3.2 Python 3.11 GPL-3.0
Authorized auditing only

Use this plugin only with networks you own or have explicit permission to audit. PCAPs, hashes, passwords, and GPS coordinates can all contain sensitive data.

01 / overview

What PWMenu does

A_pwmenu turns the standard handshakes directory into a complete field console. It indexes captures, combines results from multiple sources, grades PCAP quality, displays locations, and runs external processing without blocking the web interface.

CAPTURE

PCAP management

ESSID/BSSID grouping, individual downloads, ZIP exports, mode 22000 conversion, quality analysis, and precise deletion.

PASSWORDS

One credential view

WPA-sec, OHC, manual entries, and QuickDic results appear in one interface with their source clearly identified.

GEO

GPS map

PwnDroid, browser GPS, and GPSD support, sidecar files, clusters, capture history, and a dedicated no-location list.

AUTOMATION

Persistent OHC queue

Mode 22000 extraction, deduplication, batching, durable queue state, and automatic recovery after API backoff.

SAFETY

Controlled cleanup

Exact candidate previews, report tokens, and a final file and quality check immediately before deletion.

FIELD UI

Phone and desktop

Four focused workspaces, compact mobile navigation, customizable accent color, and direct capture actions.

Version 1.3.2 is faster and more precise

Large HTML responses use gzip, Yandex Maps is not requested until the Map tab opens, common Map actions use compact background requests, and uncracked exports select one best capture per exact access point.

02 / prerequisites

Requirements

ComponentPurposeRequired
Pwnagotchi 2.xHost, plugin loader, and Flask Web UIYes
Python 3.11The `/home/pi/.pwn` environmentYes
requestsHTTP integrations and time synchronizationYes
websocketsPwnDroid GPS transportFor PwnDroid
hcxpcapngtoolQuality analysis and mode 22000 conversionRecommended
Internet accessWPA-sec, OHC, Yandex Maps, and time syncFor integrations

Check the environment

/home/pi/.pwn/bin/python3 -c \
  "import requests, websockets; print('Python dependencies OK')"

/usr/bin/hcxpcapngtool --version

Install missing optional components:

sudo apt update
sudo apt install -y hcxtools
sudo /home/pi/.pwn/bin/pip install websockets

03 / install

Quick start

  1. Back up the current fileKeep a known-good plugin copy before replacing it.
  2. Install the tagged releasePin `v1.3.2` so the installed code is reproducible.
  3. Compile before restartNever restart the service with an unchecked Python file.
  4. Enable the pluginAdd one line to `config.toml`, then restart Pwnagotchi.

1. Back up and install

sudo cp /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /root/A_pwmenu.py.backup 2>/dev/null || true

sudo wget -O /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  https://raw.githubusercontent.com/newfpv/pwmenu/v1.3.2/A_pwmenu.py

sudo chown root:root /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo chmod 644 /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

2. Compile and enable

/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

sudoedit /etc/pwnagotchi/config.toml

Minimum configuration:

main.plugins.A_pwmenu.enabled = true
sudo systemctl restart pwnagotchi
sudo systemctl status pwnagotchi --no-pager -l

Open http://<pwnagotchi-ip>:8080/plugins/A_pwmenu/. The Bluetooth interface address will usually differ from the USB or LAN address.

04 / lifecycle

Update, rollback, and removal

Update to 1.3.2

A normal update replaces only `A_pwmenu.py`. State, PCAPs, GPS sidecars, and potfiles remain in place.

sudo cp /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /root/A_pwmenu.py.before-1.3.2

sudo wget -O /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  https://raw.githubusercontent.com/newfpv/pwmenu/v1.3.2/A_pwmenu.py

/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

sudo systemctl restart pwnagotchi

Rollback

sudo cp /root/A_pwmenu.py.before-1.3.2 \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo systemctl restart pwnagotchi

Remove the plugin

sudo systemctl stop pwnagotchi
sudo rm /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo systemctl start pwnagotchi

Remove `main.plugins.A_pwmenu.*` settings from the configuration separately. Persistent data in `/root/handshakes` is deliberately left untouched.

05 / config

Complete configuration

The following is a safe starting template. Replace placeholder keys locally and never publish your real `config.toml`.

# Plugin
main.plugins.A_pwmenu.enabled = true

# WPA-sec: an empty key hides WPA-sec controls
main.plugins.A_pwmenu.wpa_sec_key = ""

# OnlineHashCrack API v2
main.plugins.A_pwmenu.ohc_enabled = false
main.plugins.A_pwmenu.ohc_api_key = ""
main.plugins.A_pwmenu.ohc_auto_upload = true
main.plugins.A_pwmenu.ohc_sync_interval = 3600
main.plugins.A_pwmenu.ohc_retry_poll_interval = 60
main.plugins.A_pwmenu.ohc_reconcile_on_start = false

# PwnDroid over Bluetooth PAN
main.plugins.A_pwmenu.pwndroid_ws_enabled = true
main.plugins.A_pwmenu.pwndroid_mac = "AA:BB:CC:DD:EE:FF"
main.plugins.A_pwmenu.pwndroid_gateway = ""
main.plugins.A_pwmenu.pwndroid_extra_gateways = []
main.plugins.A_pwmenu.pwndroid_port = 8080

# Browser GPS and capture assignment
main.plugins.A_pwmenu.phone_gps_enabled = true
main.plugins.A_pwmenu.phone_gps_max_age = 600
main.plugins.A_pwmenu.gps_assign_window = 180
main.plugins.A_pwmenu.gps_stale_seconds = 180

# Optional GPSD fallback
main.plugins.A_pwmenu.gpsd_enabled = false
main.plugins.A_pwmenu.gpsd_host = "127.0.0.1"
main.plugins.A_pwmenu.gpsd_port = 2947
main.plugins.A_pwmenu.gpsd_poll_interval = 10

# Capture analysis
main.plugins.A_pwmenu.quality_auto_scan = true
main.plugins.A_pwmenu.quality_scan_delay_ms = 250
main.plugins.A_pwmenu.auto_replace_unusable = true

# Time and operational limits
main.plugins.A_pwmenu.timezone = 0
main.plugins.A_pwmenu.time_sync_interval = 1800
main.plugins.A_pwmenu.hcxpcapngtool_timeout = 90
main.plugins.A_pwmenu.import_max_bytes = 2097152
main.plugins.A_pwmenu.archive_memory_limit = 2097152
OptionDefaultWhat it controls
ohc_auto_uploadtrueAdds new PCAPs to the durable OHC queue.
ohc_reconcile_on_startfalsePerforms a full local/OHC reconciliation at every start. Enable intentionally.
pwndroid_gatewayemptyAn empty value enables Android gateway discovery from MAC, ARP, and the default route.
gps_assign_window180 secondsMaximum time difference between the capture and a GPS fix.
auto_replace_unusabletrueArchives an older weak capture after a newer usable capture appears for the same BSSID.
archive_memory_limit2 MiBIn-memory threshold for `SpooledTemporaryFile` ZIP archives.

06 / ui

Interface

01

Cracked

Known ESSIDs and passwords, result source, reveal and copy controls, plus update and delete actions for local entries.

02

Handshakes

All capture groups, per-file quality, and direct PCAP, 22000, OHC, WPA-sec, whitelist, and delete actions.

03

Map

Yandex Maps when online or the built-in fallback, cracked filter, search, GPS clusters, history, the No GPS list, and in-place actions that preserve the open map.

04

Other

OHC queue, potfile health, whitelist, achievements, exports, imports, time sync, and confirmation-bound cleanup.

Accent color

The interface provides six presets and a custom color picker. The value is stored in browser `localStorage` and a cookie, not in Pwnagotchi state. Each browser keeps its own preference.

Workspace search

The main search filters the current list by ESSID, BSSID, password, and capture filename. On the Map tab it is synchronized with the map search.

Download Best Uncracked

The uncracked ZIP is built from exact access-point identities, not ESSID alone. A locally known password excludes captures only when both the ESSID and BSSID match. Two unrelated routers named `Home` therefore remain independent.

  1. 1
    Read exact cracked identities

    PWMenu reads valid `ESSID + BSSID` records from WPA-sec, OHC, Manual, and QuickDic results.

  2. 2
    Group exact access points

    Captures are grouped by `ESSID + BSSID`. A capture without a valid BSSID is kept separate rather than merged speculatively.

  3. 3
    Choose the best capture

    For duplicate captures of one access point, quality rank wins first, followed by usable hash count, authorized exchange evidence, best-pair count, recency, and file size.

  4. 4
    Create the ZIP

    Only the selected capture for each exact access point without a known password is written to `uncracked-handshakes.zip`.

Same ESSID does not mean same access point

Mesh nodes often share a password, but unrelated routers can also share a default ESSID. Version 1.3.2 favors avoiding false exclusions: a different BSSID remains exportable until that exact access point has a known local credential.

Fast actions over Bluetooth

Whitelist changes and OHC or WPA-sec submissions made from the Map workspace use compact background requests. The browser keeps the map, selected marker, filters, search text, and details card in place instead of downloading and rendering the entire page again. A short notification reports progress and the final result. Duplicate clicks for the same upload are ignored while its request is active.

Actions that materially replace the current data set, such as deleting a capture or importing a file, still use a full reload so the interface is rebuilt from authoritative server state.

07 / capture intelligence

PCAP quality

PWMenu runs the local `hcxpcapngtool`, parses its report, and checks whether a valid mode 22000 hash was produced. The result is cached against the file signature and recalculated whenever the PCAP changes.

Excellent

A usable hash plus evidence of an authorized EAPOL exchange or a written PMKID.

Usable

At least one valid WPA/PMKID hash for Hashcat mode 22000.

Partial

EAPOL or PMKID material exists, but a usable hash cannot be generated yet.

Unusable

No useful WPA/PMKID material, or the file is only an empty PCAP header.

Automatic weak-capture replacement

When a separate, newer PCAP for the same BSSID becomes `Usable` or `Excellent`, an older weak file larger than 24 bytes may be renamed with a .replaced-<timestamp> suffix. This is a reversible archive: the original data remains recoverable but is excluded from the active index.

Empty 24-byte PCAPs are never archived automatically

They require explicit confirmation in Capture Cleanup, preventing an automatic classifier from silently deleting a file.

08 / safe cleanup

Safe capture cleanup

Version 1.3.0 replaces separate destructive actions with one controlled flow. Only valid empty PCAP headers and files already classified as `Unusable` become candidates.

  1. 1
    Preview

    The plugin shows filenames and reasons, then creates a SHA-256 token from path, file signature, and reason.

  2. 2
    Browser confirmation

    The exact candidate count is shown before the user confirms.

  3. 3
    Revalidation

    The report token, signature, size, and current quality grade are checked again before every deletion.

  4. 4
    Cleanup

    The PCAP and related `.gps.json`, `.geo.json`, `.hc22000`, and `.22000` files are removed, then recorded in history.

If a file changes after preview, the old report token is rejected. The broad “nuke all” action has been removed from the Web UI.

09 / whitelist

Network whitelist

The Other workspace manages `main.whitelist` without manual TOML editing. The Map workspace also provides a live Allow/Remove control on every network card. Names are validated, the list is sorted, and configuration is written atomically.

  • Exact network name, up to 128 characters.
  • NUL, carriage return, and newline characters are rejected.
  • `/etc/pwnagotchi/config.toml.pwmenu-whitelist.bak` is created before writing.
  • The original `config.toml` mode, UID, and GID are preserved.
  • The parent directory is synchronized after `os.replace()`.
  • The active agent configuration is updated immediately.

Excellent-only map groups

A multi-network map marker has one group whitelist button. It selects only ESSIDs with at least one capture currently graded `Excellent`; `Usable`, `Partial`, `Unusable`, and unclassified entries are skipped. Networks already present in the whitelist are not submitted again. When every Excellent network in the group is already allowed, the group button changes to `Allowed`.

This rule is enforced again on the Pwnagotchi, using a fresh capture scan. Modifying the browser request cannot add a network whose current server-side quality is below Excellent.

On an individual map card, `Allow` changes to `Remove` immediately after a successful update, and changes back after removal. These map operations do not reload the page.

A service restart after a large series of whitelist changes is still reasonable if you want to confirm the fully reloaded configuration.

10 / credentials

Password sources and imports

SourceFormatEditable in UI
WPA-sec / OHC potfile`AP_MAC:CLIENT_MAC:ESSID:PASSWORD`Deletion depends on source
Manual`manual.potfile`Yes
QuickDic`*.pcap.cracked`Read-only
OHC JSON/CSVService exportImported into the local potfile

Before import, the potfile is normalized as UTF-8: NUL bytes are removed and credentials are deduplicated by BSSID, ESSID, and password. Writes use a temporary file, `fsync`, and atomic replacement.

Import reports distinguish `added`, `already present`, `duplicate rows`, `ignored`, `invalid`, and the number of saved OHC task identities. `import_max_bytes` limits the upload size.

Review exports before sharing

JSON and CSV files may include SSIDs, BSSIDs, and recovered passwords. PWMenu’s deduplication snapshot stores task identities without passwords.

11 / wpa-sec

WPA-sec

Set an API key to reveal WPA actions in Handshakes and Map. Single-file and cluster submissions are independent from OHC.

main.plugins.A_pwmenu.wpa_sec_key = "REPLACE_ME"

Cluster upload runs in a background thread and does not hold the HTTP request open. The plugin checks network connectivity before submission. Avoid running another automatic WPA-sec uploader against the same capture set unless you specifically need it.

Obtain your key from wpa-sec.stanev.org ↗.

12 / onlinehashcrack

OnlineHashCrack API v2

main.plugins.A_pwmenu.ohc_enabled = true
main.plugins.A_pwmenu.ohc_api_key = "sk_REPLACE_ME"
main.plugins.A_pwmenu.ohc_auto_upload = true
main.plugins.A_pwmenu.ohc_sync_interval = 3600
main.plugins.A_pwmenu.ohc_retry_poll_interval = 60

Submission flow

  1. 1
    Extract

    `hcxpcapngtool` writes mode 22000 records to a unique temporary file.

  2. 2
    Deduplicate

    Exact hashes, file signatures, `list_tasks`, and identities from the latest valid import are compared.

  3. 3
    Batch

    New records are submitted in batches of up to 50 hashes.

  4. 4
    Persist

    Accepted and skipped metadata is saved after every successful batch.

HTTP 429 and durable backoff

`rate_limit_exceeded` does not mean the plugin is broken. PWMenu stores `retry_at`, the reason, and the complete queue, adds a small safety margin to `Retry-After`, suppresses requests until the deadline, and resumes automatically.

grep -E 'OHC|A_pwmenu' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -100

Send all missing to OHC performs a full local reconciliation. It extracts hashes from every uncracked PCAP and adds only tasks not already known.

Do not delete state to bypass a 429

The server-side limit will remain while your local queue and deduplication context are lost. Wait for the automatic retry.

13 / geo

GPS, PwnDroid, and Bluetooth

Live location priority is PwnDroid WebSocket → Browser Geolocation → GPSD. A recent fix is saved beside a new PCAP as a sidecar file.

Recommended topology

Android phoneBluetooth tetheringMobile internetPwnDroid :8080
Pwnagotchi bnep0Dynamic IPv4Default routeA_pwmenu client

Configure PwnDroid

  1. Pair the phone and Pwnagotchi.
  2. Enable Bluetooth tethering on Android.
  3. Allow Location and WebSocket sharing in PwnDroid.
  4. Find the phone MAC with bluetoothctl devices.
  5. Leave `pwndroid_gateway` empty for dynamic discovery.
main.plugins.A_pwmenu.pwndroid_ws_enabled = true
main.plugins.A_pwmenu.pwndroid_mac = "AA:BB:CC:DD:EE:FF"
main.plugins.A_pwmenu.pwndroid_gateway = ""
main.plugins.A_pwmenu.pwndroid_port = 8080

GPS sidecar example

{
  "Latitude": 53.900000,
  "Longitude": 27.566700,
  "Accuracy": 12.5,
  "Timestamp": 1784550000,
  "CaptureTimestamp": 1784550001,
  "GPSAge": 1,
  "GPSStale": false,
  "Source": "pwndroid"
}

The physical display shows `G C` for a recent coordinate fix and `G -` when no recent fix exists. An open WebSocket without coordinates still shows `G -`.

Why browser GPS may fail

Mobile browsers normally expose Geolocation only in a secure HTTPS context. PwnDroid is generally more reliable when the page is opened through a private `http://10.x.x.x:8080` address.

14 / storage

Files and persistent data

PathPurpose
/usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.pyPlugin source
/root/handshakes/.a_pwmenu_data.jsonXP, GPS index, OHC metadata, signatures, and durable queue
.a_pwmenu_data.json.bakCrash-recovery snapshot
.a_pwmenu_ohc_export.jsonPassword-free identities from the latest valid OHC export
onlinehashcrack.cracked.potfileImported OHC results
manual.potfileManually managed passwords
*.gps.json / *.geo.jsonCapture coordinates
*.pcap.crackedQuickDic result
config.toml.pwmenu-whitelist.bakConfiguration backup before a whitelist update

State is written atomically with a recovery copy and parent-directory synchronization. Do not edit the state file while Pwnagotchi is running.

15 / recovery

Backup and restore

Complete backup

sudo systemctl stop pwnagotchi

sudo tar -czf /root/a-pwmenu-backup.tar.gz \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /etc/pwnagotchi/config.toml \
  /root/handshakes

sudo systemctl start pwnagotchi

State-only backup

sudo cp /root/handshakes/.a_pwmenu_data.json \
  /root/handshakes/.a_pwmenu_data.json.backup

Restore state

sudo systemctl stop pwnagotchi
sudo cp /root/handshakes/.a_pwmenu_data.json.backup \
  /root/handshakes/.a_pwmenu_data.json
sudo chown root:root /root/handshakes/.a_pwmenu_data.json
sudo systemctl start pwnagotchi

Remove API keys, passwords, PCAPs, and GPS data before sharing a backup archive.

16 / security

Security

Enable Web UI authentication

ui.web.auth = true
ui.web.username = "change-me"
ui.web.password = "use-a-strong-password"
  • Never expose port 8080 directly to the public internet.
  • Never commit real `wpa_sec_key`, `ohc_api_key`, device MACs, or coordinates.
  • POST actions use the current Flask session and a CSRF token when Flask-WTF is available.
  • File routes accept only a local `.pcap` basename; traversal, slashes, backslashes, and NUL are rejected.
  • `hcxpcapngtool` is executed with an argument list and never through a shell.
  • Hide SSIDs, BSSIDs, passwords, and locations before publishing screenshots.
# Recommended Git exclusions
config.toml
*.pcap
*.pcapng
*.22000
*.hc22000
*.potfile
*.cracked
*.gps.json
*.geo.json
.a_pwmenu_data.json*
__pycache__/

17 / troubleshooting

Troubleshooting

Basic service health check
sudo systemctl status pwnagotchi --no-pager -l
sudo journalctl -u pwnagotchi -n 100 --no-pager
tail -100 /etc/pwnagotchi/log/pwnagotchi.log

grep -E 'A_pwmenu|OHC|WPA-sec|PwnDroid|GPSD' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -150
The plugin does not appear in the Web UI
/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

grep -n 'main.plugins.A_pwmenu' /etc/pwnagotchi/config.toml
sudo systemctl restart pwnagotchi

The file must be named exactly `A_pwmenu.py` and be readable by the Pwnagotchi service.

The page is slow over Bluetooth
ip -4 addr show bnep0
ip route
ss -lntp | grep ':8080'

Version 1.3.0 introduced gzip and lazy map loading; newer builds also keep Map whitelist, OHC, and WPA-sec actions in place. The main response should include `Content-Encoding: gzip` in browser DevTools. Perform a hard refresh if an older page is cached. A first visit to the Map workspace can still wait for the external Yandex Maps script; later in-page actions do not reload it.

The Web UI does not open over Bluetooth
ip -4 addr show bnep0
ip route
nmcli connection show --active
ss -lntp | grep ':8080'

Use the address assigned to `bnep0`; it is not necessarily `10.0.0.2`. Use `http://` unless TLS was configured separately.

The display shows G - while the phone is connected
ip route show default
ip neigh show dev bnep0
grep 'PwnDroid GPS' /etc/pwnagotchi/log/pwnagotchi.log | tail

Check Android Location permission, WebSocket sharing, battery optimization, the phone MAC, the port, and whether Android currently has a real GPS fix.

OHC is paused or rate limited

This is durable backoff. Inspect the remaining delay:

/home/pi/.pwn/bin/python3 - <<'PY'
import json, time
path = '/root/handshakes/.a_pwmenu_data.json'
with open(path) as state_file:
    data = json.load(state_file)
retry_at = float(data.get('ohc_retry_at', 0) or 0)
print('remaining:', max(0, int(retry_at - time.time())), 'seconds')
print('reason:', data.get('ohc_retry_reason', ''))
PY
Mode 22000 output is empty
/usr/bin/hcxpcapngtool \
  -o /tmp/test.22000 \
  /root/handshakes/EXAMPLE.pcap

wc -l /tmp/test.22000
rm -f /tmp/test.22000

A valid PCAP container does not guarantee a complete EAPOL exchange or usable PMKID.

The map is empty
  • Check for `.gps.json` or `.geo.json` files.
  • Review the GPS status panel and No GPS list.
  • Check internet access for Yandex Maps; the offline fallback remains available.
  • An old PCAP does not receive the current fix when `gps_assign_window` is exceeded.
The state file is damaged

The plugin first tries the newest valid primary or backup copy. If both are damaged:

sudo systemctl stop pwnagotchi
sudo mv /root/handshakes/.a_pwmenu_data.json \
  /root/handshakes/.a_pwmenu_data.json.bad
sudo systemctl start pwnagotchi

PCAP and potfile data remain, but XP and cached locations will be reset or rebuilt.

Time synchronization fails
curl -I --max-time 10 \
  http://connectivitycheck.gstatic.com/generate_204
date -u

The plugin reads the HTTP `Date` header and applies it with the system `date` command.

18 / api surface

HTTP routes

Base path: /plugins/A_pwmenu/. Do not call POST routes outside the Web UI without a valid session and CSRF token.

MethodRoutePurpose
GET/Main page
GETdownload/<pcap>Original PCAP
GETdownload-22000/<pcap>Convert and download mode 22000
GETdownload-cluster/<csv>ZIP of selected files
GETdownload-zipZIP of all PCAPs
GETdownload-uncrackedBest-quality PCAP per exact uncracked ESSID + BSSID
GETexport-passwordsCombined password list
POSTwpa-sec-upload(-cluster)Submit to WPA-sec
POSTohc-upload-clusterSubmit selected files to OHC
POSTohc-upload-all-missingFull reconciliation
POSTphone-gpsReceive browser coordinates
POSTadd/update/delete-passwordManage local credentials
POSTdelete-fileDelete one PCAP and derivatives
POSTwhitelist-add/removeManage the Pwnagotchi whitelist
POSTwhitelist-add-excellentAdd only server-verified Excellent networks from a map group
POSTclean-capturesRun confirmation-bound cleanup
POSTimportImport OHC JSON or CSV
POSTsync-timeSynchronize system time

ready / field use

Ready for the field

Start with the minimum configuration, then enable integrations one at a time. Check the logs after each step so network failures, API rate limits, and individual PCAP quality problems remain easy to distinguish.