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.
PCAP management
ESSID/BSSID grouping, individual downloads, ZIP exports, mode 22000 conversion, quality analysis, and precise deletion.
One credential view
WPA-sec, OHC, manual entries, and QuickDic results appear in one interface with their source clearly identified.
GPS map
PwnDroid, browser GPS, and GPSD support, sidecar files, clusters, capture history, and a dedicated no-location list.
Persistent OHC queue
Mode 22000 extraction, deduplication, batching, durable queue state, and automatic recovery after API backoff.
Controlled cleanup
Exact candidate previews, report tokens, and a final file and quality check immediately before deletion.
Phone and desktop
Four focused workspaces, compact mobile navigation, customizable accent color, and direct capture actions.
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
| Component | Purpose | Required |
|---|---|---|
| Pwnagotchi 2.x | Host, plugin loader, and Flask Web UI | Yes |
| Python 3.11 | The `/home/pi/.pwn` environment | Yes |
| requests | HTTP integrations and time synchronization | Yes |
| websockets | PwnDroid GPS transport | For PwnDroid |
| hcxpcapngtool | Quality analysis and mode 22000 conversion | Recommended |
| Internet access | WPA-sec, OHC, Yandex Maps, and time sync | For 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
- Back up the current fileKeep a known-good plugin copy before replacing it.
- Install the tagged releasePin `v1.3.2` so the installed code is reproducible.
- Compile before restartNever restart the service with an unchecked Python file.
- 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
| Option | Default | What it controls |
|---|---|---|
| ohc_auto_upload | true | Adds new PCAPs to the durable OHC queue. |
| ohc_reconcile_on_start | false | Performs a full local/OHC reconciliation at every start. Enable intentionally. |
| pwndroid_gateway | empty | An empty value enables Android gateway discovery from MAC, ARP, and the default route. |
| gps_assign_window | 180 seconds | Maximum time difference between the capture and a GPS fix. |
| auto_replace_unusable | true | Archives an older weak capture after a newer usable capture appears for the same BSSID. |
| archive_memory_limit | 2 MiB | In-memory threshold for `SpooledTemporaryFile` ZIP archives. |
06 / ui
Interface
Cracked
Known ESSIDs and passwords, result source, reveal and copy controls, plus update and delete actions for local entries.
Handshakes
All capture groups, per-file quality, and direct PCAP, 22000, OHC, WPA-sec, whitelist, and delete actions.
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.
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.
- 1Read exact cracked identities
PWMenu reads valid `ESSID + BSSID` records from WPA-sec, OHC, Manual, and QuickDic results.
- 2Group exact access points
Captures are grouped by `ESSID + BSSID`. A capture without a valid BSSID is kept separate rather than merged speculatively.
- 3Choose 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.
- 4Create the ZIP
Only the selected capture for each exact access point without a known password is written to `uncracked-handshakes.zip`.
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.
A usable hash plus evidence of an authorized EAPOL exchange or a written PMKID.
At least one valid WPA/PMKID hash for Hashcat mode 22000.
EAPOL or PMKID material exists, but a usable hash cannot be generated yet.
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.
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.
- 1Preview
The plugin shows filenames and reasons, then creates a SHA-256 token from path, file signature, and reason.
- 2Browser confirmation
The exact candidate count is shown before the user confirms.
- 3Revalidation
The report token, signature, size, and current quality grade are checked again before every deletion.
- 4Cleanup
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
| Source | Format | Editable 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/CSV | Service export | Imported 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.
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
- 1Extract
`hcxpcapngtool` writes mode 22000 records to a unique temporary file.
- 2Deduplicate
Exact hashes, file signatures, `list_tasks`, and identities from the latest valid import are compared.
- 3Batch
New records are submitted in batches of up to 50 hashes.
- 4Persist
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.
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
Configure PwnDroid
- Pair the phone and Pwnagotchi.
- Enable Bluetooth tethering on Android.
- Allow Location and WebSocket sharing in PwnDroid.
- Find the phone MAC with
bluetoothctl devices. - 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
| Path | Purpose |
|---|---|
| /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py | Plugin source |
| /root/handshakes/.a_pwmenu_data.json | XP, GPS index, OHC metadata, signatures, and durable queue |
| .a_pwmenu_data.json.bak | Crash-recovery snapshot |
| .a_pwmenu_ohc_export.json | Password-free identities from the latest valid OHC export |
| onlinehashcrack.cracked.potfile | Imported OHC results |
| manual.potfile | Manually managed passwords |
| *.gps.json / *.geo.json | Capture coordinates |
| *.pcap.cracked | QuickDic result |
| config.toml.pwmenu-whitelist.bak | Configuration 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 -150The 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 pwnagotchiThe 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 | tailCheck 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', ''))
PYMode 22000 output is empty
/usr/bin/hcxpcapngtool \
-o /tmp/test.22000 \
/root/handshakes/EXAMPLE.pcap
wc -l /tmp/test.22000
rm -f /tmp/test.22000A 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 pwnagotchiPCAP 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 -uThe 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.
| Method | Route | Purpose |
|---|---|---|
| GET | / | Main page |
| GET | download/<pcap> | Original PCAP |
| GET | download-22000/<pcap> | Convert and download mode 22000 |
| GET | download-cluster/<csv> | ZIP of selected files |
| GET | download-zip | ZIP of all PCAPs |
| GET | download-uncracked | Best-quality PCAP per exact uncracked ESSID + BSSID |
| GET | export-passwords | Combined password list |
| POST | wpa-sec-upload(-cluster) | Submit to WPA-sec |
| POST | ohc-upload-cluster | Submit selected files to OHC |
| POST | ohc-upload-all-missing | Full reconciliation |
| POST | phone-gps | Receive browser coordinates |
| POST | add/update/delete-password | Manage local credentials |
| POST | delete-file | Delete one PCAP and derivatives |
| POST | whitelist-add/remove | Manage the Pwnagotchi whitelist |
| POST | whitelist-add-excellent | Add only server-verified Excellent networks from a map group |
| POST | clean-captures | Run confirmation-bound cleanup |
| POST | import | Import OHC JSON or CSV |
| POST | sync-time | Synchronize 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.