Retry and serialize pineapd/hak5 calls, queue virtual-pager keys, and grey out buttons until the pager finishes. Deploy now installs python3-light after factory firmware. Bump version to 1.3.2. Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
11 KiB
Markdown
149 lines
11 KiB
Markdown
---
|
|
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`.
|
|
|
|
**The uplink pins phy0 (field-verified 2026-08-19):** while the device's own
|
|
client uplink (`wlan0` STA) is associated, it holds phy0 on the association
|
|
channel (here ch1). `wlan0mon` therefore CANNOT hop off ch1, and 2.4 GHz
|
|
APs on other channels are invisible to recon — even when `hop=1` is set.
|
|
Also, pineapd's per-interface hop is a no-op unless `hopspeed` is set on
|
|
that interface (`pineapd.wlan0mon.hopspeed='fast'`). Workarounds: run the
|
|
2.4 GHz evil twin on the phy's pinned channel (clients rescan all channels
|
|
on reconnect and will find it), or accept ch1-only 2.4 GHz recon while the
|
|
uplink is up.
|
|
|
|
## pineapd crash stack (Pager 24.10.1 — all five verified)
|
|
|
|
1. **SSID-pool broadcast** — segfaults pineapd (~15 s cadence, `ra=004e1237`). Keep `pineapd.@ssidpool[0].disable=1`.
|
|
2. **wlan2mon** — a 6 GHz monitor this hardware never creates; hopping it segfaults pineapd. Keep `pineapd.wlan2mon.disable=1` + `hop=0`.
|
|
3. **wlan1mon 6 GHz fast-hop** — stalls the command socket; the stock daemon's watchdog then SIGTERMs pineapd every ~30 s ("[PineAp] Error writing"). Keep `pineapd.wlan1mon.bands=5`.
|
|
4. **Refilled pool list** — collect (`autossidpool`) refills the pool; a large list crashes even with broadcast off. Clear `pineapd.@ssidpool[0].ssid` when pineapd fails.
|
|
5. **Active socket polling** — pinging pineapd from a health loop collides with the stock daemon's writes. Health checks must be passive (`pidof`).
|
|
|
|
The Mark VIII health monitor enforces all five automatically; `/api/health` reports state. An evil twin / enterprise deploy pauses `wlan1mon` hop and resumes it on stop.
|
|
|
|
## Standalone PineAPE enterprise engine (field-verified)
|
|
|
|
The stock daemon's enterprise AP config generation is BROKEN on this build
|
|
(it hardcodes `eap_server_erp=1`, which hostapd rejects with "Invalid IEEE
|
|
802.1X configuration (no EAP authenticator configured)"). Working engine,
|
|
run entirely by Mark VIII on phy1 outside the daemon's interface set:
|
|
|
|
```sh
|
|
iw phy phy1 interface add wlan1ent type managed
|
|
iw dev wlan1ent set type ap && ip link set wlan1ent up
|
|
# hostapd config: interface=wlan1ent, ieee8021x=1, eap_server=1,
|
|
# eap_user_file=/root/loot/eap_users ("*" MSCHAPV2 "dummy"),
|
|
# wpa_key_mgmt=WPA-EAP, ctrl_interface=/var/run/hostapd-mk8
|
|
/usr/sbin/hostapd -B -P /var/run/hostapd-mk8.pid /root/loot/enterprise.conf
|
|
# enable karma + PineAPE + auth capture on the INSTANCE's ctrl socket:
|
|
hostapd_cli -p /var/run/hostapd-mk8 -i wlan1ent pineap_enable
|
|
hostapd_cli -p /var/run/hostapd-mk8 -i wlan1ent pineape_enable
|
|
hostapd_cli -p /var/run/hostapd-mk8 -i wlan1ent pineape_auth_enable
|
|
```
|
|
|
|
Captured credentials flow to pineapd's socket and land in
|
|
`hostap_basic` / `hostap_chalresp` in recon.db. Mark VIII exposes them as
|
|
EAP identities + MSCHAPv2 (RADIUS inner-auth equivalent) at
|
|
`/api/pineap/enterprise/radius`, with hashcat `-m 5500` and john `netntlm`
|
|
export. The Pager is an EAP terminator (PineAPE), not a UDP/1812 RADIUS
|
|
proxy. Tear down: kill the pidfile pid, `iw dev wlan1ent del`, resume hop.
|
|
|
|
## 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.
|