Files
Mark-VIII/README.md
T
c4ch3c4d3 d23ea56364 fix(portals,dns,radio1,capture): live-validation fixes, verified on Pager 24.10.1
- portals: replace zipfile with struct+zlib ZIP reader (python3-light has
  no urllib; import endpoint was dead on device)
- dns hijack: uci add_list/del_list for dhcp.@dnsmasq[0].address (list
  option; plain set was silently dropped from generated dnsmasq config)
- radio1: bridge attack APs into br-lan via network.brlan.ports so
  victims get DHCP/portal reach; wlan1ent runtime-bridged after hostapd
  verify (retry loop may recreate the iface)
- capture: auto-start pinned wlan1mon pcap on 5GHz WPA deploy, teardown
  on stop; loot flows via hc22000 export (crack-verified end-to-end)
- enterprise: pineapd restart after ctrl link + re-assert PineAPE toggles

Documented residual: pineapd refuses forwarding from foreign hostapd
instances (broken pipe), and daemon set_ap rejects radio1 names - so
hostap_handshake rows for radio1 twins and enterprise cred tables cannot
populate without a Hak5 firmware change.

New tests/test_validation_fixes.py covers each fix (TDD); full suite
(29 modules) green.
2026-08-23 21:44:21 -06:00

219 lines
10 KiB
Markdown

# Mark VIII
A Mark VII-style web management UI that runs **on the WiFi Pineapple Pager** at
`http://172.16.52.1:8080/`. Packaged as a native Pager payload.
Features: Dashboard (live), PineAP (settings, SSID pool, filters, clients/kick),
Recon (scans from `recon.db`), Handshakes/Loot, Payloads (embedded stock Pager
Portal), Logs, Settings (hostname/NTP/password/prefs), and a bottom-docked xterm
terminal. Recon AP focus offers bulk deauth; an Evil Portal tab imports Hak5
EvilPortalNano-format portals (`kleo/evilportals` compatible), serves them to
victims via DNS hijack on port 80, and captures form credentials.
- Rogue AP on the second radio (5GHz / 6GHz Wi-Fi 6E): Open AP and Evil WPA
(WPA2-PSK/WPA3-SAE/WPA3-OWE) on `radio1`, band-aware channel pickers,
6GHz requires WPA3. While a radio1 AP is enabled the stock monitor-hopping
(`wlan1mon`) is paused and resumed on disable; 2.4GHz PineAP is untouched.
## Requirements
- WiFi Pineapple Pager, firmware `Pineapple Pager 24.10.1`
- `python3` on the device (factory 24.10.1 may not ship it; `scripts/deploy.sh` installs OpenWrt `python3-light` from offline ipks)
- Python 3.11 on the development machine
## Install (sideload)
macOS/Linux:
```bash
# Recommended: key authentication
./scripts/deploy.sh --ssh-key "$HOME/.ssh/pager_key"
# Password authentication requires sshpass
brew install hudochenkov/sshpass/sshpass
./scripts/deploy.sh --password '<device-password>'
```
Windows:
```powershell
# deploy.ps1 needs either an SSH key or sshpass for password auth:
& .\scripts\deploy.ps1 -SshKey "$HOME\.ssh\pager_key"
# or set up a key and add it: ssh-copy-id root@172.16.52.1
```
The deployment scripts build `build/pager-webui/payload-<b64>.zip`, upload it,
extract it to `/root/payloads/user/remote_access/pager-webui/`, and refresh the
portal index.
Then on the Pager menu, run **Mark VIII**:
- **Yes** to "Run as background service?" -> procd service (respawns on crash,
boot-persistent via rc.d symlinks).
- **No** -> foreground mode; press **B** to stop.
- Re-run the payload while running to **Stop** the service.
- `PAYLOAD_GET_CONFIG pager_webui auto_mode/run_mode` skip the prompt.
Every payload run (and every service startup) first runs an **environment
check** that prints on the payload screen / `/tmp/pagerwebui.log`: daemon
reachable, pineapd alive, monitor interfaces up, and recon DB readable. On a
healthy pager it **does not rewrite** live PineAP or wireless UCI (SSID pool,
hopping, dummy_radio0 STA, Open AP). Crash-prone settings are reported as
warnings. Startup aborts only if a core dependency fails. The health monitor
will restart a dead pineapd and re-raise dropped monitors; it will not clear
the SSID list or disable pool broadcast.
Browse `http://172.16.52.1:8080/` and log in with the device password.
## Uninstall / recovery
Re-run the payload and confirm "Stop service?" (stops, disables, removes the
init script), then delete the payload directory via the portal or:
`rm -rf /root/payloads/user/remote_access/pager-webui`. No stock files are modified.
After a **firmware upgrade** (which wipes the overlay), reinstall and run the
payload to re-enable it—the same caveat as Nautilus.
## Local dev loop
```powershell
.\scripts\deploy.ps1 -SshKey "$HOME\.ssh\pager_key" # deploy backend once
.\scripts\dev.ps1 -Tunnel # local SPA + API proxy
# open http://127.0.0.1:8000
```
`dev.ps1` serves `www/` locally, proxies `/api/*` to the Pager, and points the
terminal at the Pager's daemon WS (`-Tunnel` opens the `:1471` SSH tunnel).
The live WebSocket falls back to 5s polling through the dev proxy.
## API tests
Python unit tests (stdlib `unittest`, runnable on Windows with mocks). Run each
module in its own process—the tests monkeypatch module-level helpers and do not
restore them, so a single `discover` process leaks state between files:
```powershell
$py = "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe"
Get-ChildItem tests\test_*.py | ForEach-Object {
$mod = "tests." + [IO.Path]::GetFileNameWithoutExtension($_.Name)
& $py -m unittest $mod -v
}
```
On-device smoke tests cover every page, background vs foreground,
terminal I/O, and reboot persistence.
## Architecture
- `server.py` — Mark VIII's pure-socket HTTP + JSON API + minimal RFC6455 WS on
`0.0.0.0:8080`, written to run on the device's `python3-light` (no
`urllib`/`http.server`/`sqlite3` stdlib modules there); talks to the Hak5
daemon (`127.0.0.1:1471`) over a raw-socket HTTP client, `hak5cmd`,
`uci`, `iwinfo`, and `recon.db` read-only (via the `sqlite3` CLI).
- `www/` — vanilla JS SPA (no build step) + bundled xterm.js.
- `payload.sh` + `pagerwebui.init` — Nautilus-style installer / procd service.
## Stability notes (Pager 24.10.1)
### Reliability Core (v1.4.0)
The factory `/etc/config/pineapd` ships every crash source below enabled, so
guards that only live inside Mark VIII revert on every reset/upgrade. v1.4.0
makes the fixes structural (payload-only — no firmware changes):
- **Boot guard** (`mk8-guard`, START=49): enforces the known-good UCI set
before the S50 pineapple stack starts; idempotent, diff-only commits,
logged to `/tmp/mk8-guard.log` and the event journal.
- **Profiles + rollback watchdogs**: every risky operation (AP deploy,
client-mode change, profile restore) first snapshots UCI under
`/mmc/mk8/profiles/`, then spawns a detached watchdog probing *local*
liveness (`127.0.0.1:8080` + monitor presence). Sustained failure →
automatic snapshot restore + `wifi reload`; recovery → promotes
`lastknown-good`. Watchdogs self-exit after 120 quiet ticks.
- **RF plan**: phy0 is always 2.4GHz ops; phy1 role-switches
uplink/attack/idle (`POST /api/rfplan/role`). The uplink STA lives on
radio1, so client-mode no longer blinds 2.4GHz recon. Hop governance
belongs to the role manager, not the boot guard.
- **Supervisor**: passive sampler (pidof/iw//proc only — never pineapd's
socket), bounded JSONL journal at `/mmc/mk8/events.log`, boot-marker
unexpected-reboot detection, memory watermark alerts. Dashboard shows
events feed + reliability counters + guard sync chip.
- **Atomic deploys**: `scripts/deploy.sh` stamps `VERSION` into build
copies only, sha256-verifies the upload, swaps `/mmc/mk8/releases/<ts>`
with a `current` symlink, polls local health, and auto-rolls back to the
previous release on failure.
- **Smoke suite**: `scripts/smoke.sh` on-device (read-only checks always;
`--write` adds reconcile/watchdog drills; `SMOKE_UPLINK_SSID=...` enables
the RF-role drill). All checks verified against live hardware including
reboot persistence.
Persistent state lives in `/mmc/mk8/` and survives reboots *and* firmware
upgrades (overlay wipes). A pre-reliability config backup is captured at
first deploy.
### pineapd crash sources found and fixed on this firmware
pineapd crash sources found and fixed on this firmware (verified on-device,
zero crashes over sustained watches):
1. **SSID-pool broadcast** — segfaults pineapd (~15s cadence). Kept disabled.
2. **wlan2mon** — a 6GHz monitor this hardware never creates; hopping the
missing iface segfaults pineapd. Disabled.
3. **Large refilled pool** — the pool list itself crashes pineapd even with
broadcast disabled. The health monitor clears it (collect refills).
4. **wlan1mon fast-hopping 6GHz** — stalls pineapd's command socket; the
stock daemon's watchdog then SIGTERMs pineapd every ~30s. Bands pinned
to 5GHz (2.4GHz only on wlan0mon).
5. **Socket collisions** — actively pinging pineapd from a health monitor
collides with the stock daemon's own socket writes. The monitor now
checks `pidof` only.
`GET /api/health` reports pineapd/monitor state; the top bar shows a
PINEAP OK / POOL OFF / PINEAPD DOWN chip.
### Live validation findings (v1.4.x, Pager 24.10.1)
Fixed after an on-hardware attack validation pass:
1. **Evil Portal import** no longer uses `zipfile` (pulls
`pathlib → urllib`, absent from python3-light). A minimal
`struct`+`zlib` ZIP reader handles stored/deflate entries.
2. **Portal DNS hijack** now uses `uci add_list/del_list`
(`dhcp.@dnsmasq[0].address` is a list option; a plain `uci set` was
silently dropped from the generated dnsmasq config).
3. **5 GHz attack APs are bridged** into `br-lan`
(`network.brlan.ports`) so victims get DHCP/portal reach; the
standalone enterprise AP (`wlan1ent`) is runtime-bridged after its
hostapd instance verifies ENABLED (the retry loop may recreate it).
4. **5 GHz WPA deploys auto-start a pinned `wlan1mon` capture** and the
matching stop tears it down: loot flows via pcap → `.hc22000` export
instead of the dead daemon path below.
Residual firmware limitation (not fixable in-process): pineapd refuses
handshake/PineAPE forwarding from hostapd instances it did not provision
itself (`PINEAP: could not send ... Broken pipe`), and the stock daemon's
`set_ap` rejects radio1 interface names ("Invalid access point
interface"). Consequences: `hostap_handshake` rows never populate for
radio1 evil twins (use the auto-capture + `.hc22000` export, which is
crack-verified end-to-end), and enterprise credentials never reach
`hostap_basic`/`hostap_chalresp` even though the AP terminates
PEAP/MSCHAPv2 successfully. Fixing these requires a Hak5 pineapd change.
## Security notes
- Auth via device password validated against the daemon; HttpOnly session
cookie `AUTH_<serverid>`; all state-changing endpoints gated.
- Commands run with argument lists (no shell interpolation).
- Binds `0.0.0.0:8080` — same exposure class as the stock `:1471`/`:7681`.
- Same-origin validation protects browser writes and WebSocket upgrades; the
daemon token is stored in a root-only temporary session file.
## Out of scope (v1)
`:1471` takeover, Mark VII-only features (Campaigns/Modules/Cloud C2/EAP),
physical-display mirroring, and a PR to `hak5/wifipineapplepager-payloads`
(packaging is drop-in ready for that PR).
## License
Mark VIII source code is available under the MIT License. See `LICENSE` and
`THIRD_PARTY_NOTICES.md` for bundled-component and trademark notices.