feat: local MCP harness (tools/resources/prompts) + Harness UI page

Streamable-HTTP MCP server on POST /mcp: device.state, attack.deploy/stop/
status/deauth/capture/export_hc22000, loot.handshakes/enterprise_creds,
recon.aps/isearch/devices, pineap.kick_client/set_filter tools; recon DB +
bundled opencode skills resources; attack playbook prompts. Cookie or Bearer
auth. Harness page shows endpoint, token, curl snippet, capability explorer
and a copy-paste pi.dev prompt. scripts/harness_stdio.py for stdio-only
agents.
This commit is contained in:
2026-08-18 20:00:13 -05:00
parent f31b38d1fa
commit e871e49466
8 changed files with 917 additions and 2 deletions
@@ -0,0 +1,101 @@
---
name: pineapple-control
description: Use when operating a WiFi Pineapple (Pager / FENRIS / PineAP firmware) over SSH — accessing the device, understanding its radios/processes, controlling it via PINEAPPLE_* / _pineap / hostapd_cli, fixing pineapd crashes (SSID-pool SIGSEGV), or persistently configuring APs and evil twins via /etc/config/wireless. Pair with the wifi-deauth skill for deauth/handshake attack work.
---
# Pineapple Control (Pager / FENRIS)
Field-verified operating guide for the WiFi Pineapple Pager (FENRIS firmware, kernel 6.6, OpenWrt, BusyBox). Read this before touching the device; the wifi-deauth skill covers the attack methodology.
## Hardware / radios
| Radio | Hardware | Interfaces | Notes |
|---|---|---|---|
| phy0 | internal `mt76_wmac` (2.4 GHz) | `wlan0wpa` (AP), `wlan0open` (AP), `wlan0mon` (monitor), `wlan0` (managed uplink) | `wlan0mon` DOES see the Pineapple's own TX |
| phy1 | USB `mt7921u` (5 GHz) | `wlan1wpa` (AP), `wlan1mon` (monitor) | `wlan1mon` does NOT see own TX (beacon offload) — see Captures |
Naming: `wlan0*` = 2.4 GHz, `wlan1*` = 5 GHz. A phy's channel is held by its AP interface (`iw dev`); the monitor on that phy is pinned to it. The UI "Evil WPA AP" feature is hardwired to `wlan0wpa` (2.4 GHz); a 5 GHz evil twin must be made via `/etc/config/wireless`.
## Access
```sh
sshpass -p '<pw>' ssh -o StrictHostKeyChecking=no root@<ip> # lab unit: 172.16.52.1
```
- Transient `Permission denied` after bursts of sessions = SSH rate limiting — pause ~10 s and retry.
- Keep sessions short; run each logical step in its own command. One combined session for multi-step attacks (see wifi-deauth).
- BusyBox: `pkill`, `nohup`, `sshpass` are MISSING. Use `killall`/`kill $(pidof ...)`, `setsid`, and local sshpass. `od`/`hexdump`/`cat -n` absent — use `strings`/`grep`/`head -c`.
## What runs on the box
| Process | Managed by | Purpose | Socket |
|---|---|---|---|
| `/pineapple/pineapple` (ELF UI backend) | procd (`/etc/init.d/pineapplepager`) | Web UI; supervises/reconverges hostapd | — |
| `/usr/sbin/pineapd` | procd (auto-restarts on crash) | recon, deauth, SSID pool, handshake logging | `/tmp/pineap_sock` |
| `/usr/sbin/hostapd` (single global instance) | standalone (PPID 1) | all AP interfaces | `/var/run/hostapd/global`, per-iface under `/var/run/hostapd/` |
| `wpa_supplicant` | procd | device's own client uplink (`wlan0`) | — |
## Command surface
- `PINEAPPLE_*` (e.g. `PINEAPPLE_DEAUTH_CLIENT`) = symlinks to `hak5cmd`, which talks to pineapd over `/tmp/pineap_sock`. Do NOT `curl 127.0.0.1/api/...` — the HTTP API is not on :80.
- `_pineap` = pineapd control CLI (`PING`, `RECON APS|DEVICES|ISEARCH format=json`, `INTERFACE LIST/SET`, `SSIDPOOL ...`, `DEAUTH`, `EXAMINE`, `PCAP START/STOP`). Direct use can desync the UI — prefer `PINEAPPLE_*` where one exists.
- `hostapd_cli -i <iface> status|get_config|disable|enable` (per-iface) and `-p /var/run/hostapd -i global` (global). This is a Karma-patched build.
- `iw`, `sqlite3`, `tcpdump` (full build: `-G`/`-W` rotate supported), `logread`, `dmesg`.
## Config & persistence (the hard-won rules)
- `/etc/config/wireless` is the SOURCE OF TRUTH for APs (`config wifi-iface` sections). `wifi reload` (or `wifi up radioN`) applies it.
- Editing `/var/run/hostapd-phy*.conf` is TRANSIENT. `hostapd_cli ... reload_config`/`reload` do NOT re-read the file. `hostapd_cli raw ADD/REMOVE` misfires (treats the config path as the ctrl dir). Killing hostapd triggers the UI backend to restart it (`-g /var/run/hostapd/global`, no configs) and the ubus path reconverges from `/etc/config/wireless` — reverting your change.
- **To change an AP persistently:** back up first, edit `/etc/config/wireless`, then `wifi reload`. Example — convert a 5 GHz AP to a WPA2-PSK evil twin:
```sh
cp /etc/config/wireless /etc/config/wireless.bak
# wifi-iface section: ssid 'TargetSSID', encryption 'psk2', key '<passphrase>'
wifi reload
hostapd_cli -i wlan1wpa get_config # verify ssid + key_mgmt=WPA-PSK
```
## pineapd health & the crash-loop
- Symptom: `PINEAPPLE_*` / deauth returns `could not connect to pineap: dial unix /tmp/pineap_sock: connect: connection refused`, and `logread` shows `do_page_fault(): sending SIGSEGV to pineapd for invalid read access from 00000004`.
- Cause observed: the **SSID-pool broadcast** (68 SSIDs loaded from `/etc/config/pineapd`) segfaults pineapd on a ~15 s-to-minutes cadence; procd respawns it.
- Fix: `_pineap SSIDPOOL DISABLE && /etc/init.d/pineapd restart`, verify with `_pineap PING` (PONG) and that the SIGSEGV count in `logread` stops climbing. The SSID pool is separate from hostapd evil twins — disabling it does not affect them.
- `PING` to `/tmp/pineap_sock` failing while the socket file exists = stale socket (pineapd down/restarting).
## Recon DB
`pineapd` runs `--recon --reconpath /root/recon/ --handshakepath /root/loot/handshakes`. pineapd holds the DB — always read via the read-only URI with a timeout:
```sh
timeout 30 sqlite3 -header -column "file:/root/recon/recon.db?mode=ro" \
"SELECT bssid, CAST(ssid AS TEXT), channel, freq, signal, datetime(time,'unixepoch') FROM ssid ORDER BY time DESC LIMIT 40"
```
Tables: `ssid` (ssid is BLOB — `CAST(ssid AS TEXT)`; has bssid/channel/freq/signal/encryption/hidden), `wifi_device` (mac/freq/signal/packets), `scan`, `handshake` (beacon/hs1..hs4 — captures for any nearby AP), `hostap_handshake` (mic/nonce/eapol — captures for the Pineapple's OWN evil-twin APs), plus `hostap_basic`/`hostap_chalresp` (PineAPE enterprise creds) and `hostap_client`. `RECON CLIENTS` does not exist — use `RECON DEVICES`.
## Captures
- Raw monitor capture (802.11+radiotap; EAPOL is cleartext on the wire):
```sh
tcpdump -i wlan1mon -s 3000 -w /root/loot/pcap/mon_$(date +%s).cap
```
- **Own-TX visibility differs by radio.** On phy0 (2.4 GHz) `wlan0mon` captures the Pineapple's own beacons/EAPOL; on phy1 (5 GHz) `wlan1mon` does NOT see the Pineapple's own TX. A 5 GHz evil twin's M1/M3 will be invisible to the monitor — rely on `hostap_handshake`/`/root/loot/handshakes` for own-AP 4-ways. Client uplink frames (M2/M4, assoc) ARE visible on both.
- PineAP's `PCAP START` export is management/control frames only — never rely on it for handshakes.
- Standing capture that survives SSH disconnect (detaches via `setsid`, rotates 5 min, keeps 48 files ≈ 4 h; `/mmc` had ~3.3 GB free):
```sh
setsid tcpdump -i wlan1mon -s 3000 -G 300 -W 48 -w '/root/loot/pcap/nc_%Y%m%d_%H%M%S.cap' >/dev/null 2>&1 </dev/null &
```
- Stop captures: `killall tcpdump` (`pkill` missing).
- Pull evidence locally with `scp`; analyze with `tshark`/`capinfos`/`hcxpcapngtool` (brew `wireshark`, `hcxtools`).
## Verification & troubleshooting
- AP up but silent? `iw dev <iface> info` for ssid/type/channel; `hostapd_cli -i <iface> status` (state=ENABLED) and `get_config`. Static `tx_packets` on the netdev does NOT mean not-beaconing — beacons are driver-offloaded; check `dmesg` for driver errors instead.
- Deauth channel targeting: `PINEAPPLE_DEAUTH_CLIENT` injects via the phy of the configured inject interface (here `wlan1mon`, 5 GHz) regardless of the channel argument — a "ch1" deauth goes out on 5 GHz. To reach 2.4 GHz clients the inject interface must be phy0. Verify on the wire with a monitor capture (SA=spoofed BSSID).
- `hostapd_cli -p /var/run/hostapd -i global interface` lists managed interfaces.
## Teardown & hygiene
- Stop captures: `killall tcpdump`; kill only the standing capture's PID if you must keep others.
- Leave `/root/loot/**` pcap artifacts as evidence; scp them off before leaving.
- If you disabled the SSID pool to fix a crash, tell the user it stays disabled (re-enabling re-crashes pineapd).
- Report persistent config changes you made (e.g. an AP converted in `/etc/config/wireless`) so the user knows their device differs from the UI default.