Executed non-invasive pentest against amber-maple (v1.12-dev): - Phase 1: mDNS, port scan, binary analysis, eFuse readout - Phase 2: HMAC timing, command injection (27 tests), replay (6 tests) - Phase 3: NVS analysis, CVE check (12 CVEs), binary structure All network-facing tests PASS. Physical security gaps documented.
428 lines
16 KiB
Markdown
428 lines
16 KiB
Markdown
# Cheatsheet
|
|
|
|
## Environment Setup
|
|
|
|
```bash
|
|
source ~/esp/esp-idf/export.sh # Activate ESP-IDF (every shell)
|
|
```
|
|
|
|
## Build & Flash
|
|
|
|
```bash
|
|
cd ~/git/esp32-hacking/get-started/csi_recv_router
|
|
|
|
idf.py set-target esp32 # Set chip target
|
|
idf.py menuconfig # Configure WiFi/UDP settings
|
|
idf.py build # Compile firmware
|
|
idf.py -p /dev/ttyUSB0 -b 460800 flash # Flash to device (460800 baud)
|
|
idf.py -p /dev/ttyUSB0 monitor # Serial monitor (Ctrl+] to exit)
|
|
idf.py -p /dev/ttyUSB0 -b 460800 flash monitor # Flash + monitor combined
|
|
idf.py fullclean # Clean build directory
|
|
idf.py reconfigure # Re-fetch managed components
|
|
```
|
|
|
|
## Deployed Sensors
|
|
|
|
| Name | IP | mDNS | Location |
|
|
|------|-----|------|----------|
|
|
| muddy-storm | 192.168.129.29 | muddy-storm.local | Living Room |
|
|
| amber-maple | 192.168.129.30 | amber-maple.local | Office |
|
|
| hollow-acorn | 192.168.129.31 | hollow-acorn.local | Kitchen |
|
|
|
|
**Target:** `192.168.129.11:5500` (Pi) | **Cmd port:** `5501`
|
|
|
|
## Remote Management (esp-cmd)
|
|
|
|
```bash
|
|
esp-cmd <host> STATUS # Full device status (see STATUS Fields below)
|
|
esp-cmd <host> PROFILE # Heap, stack watermarks, CPU runtime stats
|
|
esp-cmd <host> IDENTIFY # LED solid 5s (find the device)
|
|
esp-cmd <host> RATE 50 # Set ping rate to 50 Hz (disables adaptive)
|
|
esp-cmd <host> POWER 15 # Set TX power to 15 dBm (NVS saved)
|
|
esp-cmd <host> ADAPTIVE ON # Enable adaptive sampling (NVS saved)
|
|
esp-cmd <host> ADAPTIVE OFF # Disable adaptive sampling
|
|
esp-cmd <host> THRESHOLD 0.005 # Set motion sensitivity (NVS saved)
|
|
esp-cmd <host> OTA http://pi:8070/fw # Trigger OTA update (use esp-ota instead)
|
|
esp-cmd <host> HOSTNAME mydevice # Set hostname (NVS saved, mDNS updated)
|
|
esp-cmd <host> SCANRATE 60 # BLE scan restart interval (5-300s)
|
|
esp-cmd <host> PROBERATE 5 # Probe dedup cooldown (1-300s)
|
|
esp-cmd <host> CSIMODE # Query current CSI output mode
|
|
esp-cmd <host> CSIMODE RAW # Full I/Q array (default, ~900 B/pkt)
|
|
esp-cmd <host> CSIMODE COMPACT # Features only (~200 B/pkt)
|
|
esp-cmd <host> CSIMODE HYBRID 10 # Compact + raw every Nth packet
|
|
esp-cmd <host> AUTH # Query auth status (on/off)
|
|
esp-cmd <host> AUTH mysecret123 # Enable HMAC auth (8-64 char secret)
|
|
esp-cmd <host> FLOODTHRESH # Query deauth flood threshold (5/10s)
|
|
esp-cmd <host> FLOODTHRESH 10 30 # Set: 10 deauths in 30s = flood
|
|
esp-cmd <host> CALIBRATE # Start baseline capture (default 10s, room must be empty)
|
|
esp-cmd <host> CALIBRATE 20 # Calibrate for 20 seconds
|
|
esp-cmd <host> CALIBRATE STATUS # Show baseline info (valid/invalid, nsub)
|
|
esp-cmd <host> CALIBRATE CLEAR # Delete baseline, disable presence
|
|
esp-cmd <host> PRESENCE # Query: on/off, baseline, threshold, score
|
|
esp-cmd <host> PRESENCE ON # Enable presence detection (needs baseline)
|
|
esp-cmd <host> PRESENCE OFF # Disable presence detection
|
|
esp-cmd <host> PRESENCE THRESHOLD 0.08 # Set presence threshold (0.001-1.0)
|
|
esp-cmd <host> REBOOT # Restart device
|
|
```
|
|
|
|
Host can be an IP or mDNS name (`amber-maple.local`).
|
|
|
|
```bash
|
|
esp-cmd amber-maple.local STATUS # Single device via mDNS
|
|
esp-cmd 192.168.129.29 IDENTIFY # Single device via IP
|
|
```
|
|
|
|
## Fleet Management (esp-fleet)
|
|
|
|
```bash
|
|
esp-fleet status # Query all sensors at once
|
|
esp-fleet identify # Blink all LEDs
|
|
esp-fleet rate 50 # Set rate on all devices
|
|
esp-fleet reboot # Reboot entire fleet
|
|
esp-fleet ota # OTA update all (sequential)
|
|
esp-fleet ota /path/to/firmware.bin # OTA with custom firmware
|
|
```
|
|
|
|
## OTA Updates (esp-ota)
|
|
|
|
```bash
|
|
esp-ota amber-maple.local # OTA with default build
|
|
esp-ota amber-maple.local -f fw.bin # OTA with custom firmware
|
|
esp-ota amber-maple.local --no-wait # Fire and forget
|
|
```
|
|
|
|
**First flash after enabling OTA requires USB** (partition table change).
|
|
After that, all updates are OTA.
|
|
|
|
### OTA Flow
|
|
|
|
1. Verifies device is alive via STATUS
|
|
2. Starts temp HTTP server on Pi (port 8070)
|
|
3. Sends `OTA http://<pi>:8070/<fw>.bin` via UDP
|
|
4. Device downloads, flashes, reboots
|
|
5. Verifies device responds post-reboot
|
|
|
|
### Rollback
|
|
|
|
If new firmware crashes or hangs, the 30s watchdog reboots and bootloader
|
|
automatically rolls back to the previous firmware.
|
|
|
|
## CSI Output Modes
|
|
|
|
| Mode | Payload | Size | BW @ 100 Hz |
|
|
|------|---------|------|-------------|
|
|
| RAW (default) | `"[I,Q,I,Q,...]"` (128 values) | ~900 B | ~90 KB/s |
|
|
| COMPACT | `"F:rms,std,max,idx,energy"` | ~200 B | ~20 KB/s |
|
|
| HYBRID N | Compact every packet, raw every Nth | ~270 B avg (N=10) | ~27 KB/s |
|
|
|
|
Compact features (per packet, from 64 I/Q subcarrier pairs):
|
|
|
|
| Feature | Type | Description |
|
|
|---------|------|-------------|
|
|
| amp_rms | float | RMS amplitude = sqrt(mean(I²+Q²)) |
|
|
| amp_std | float | Std dev of per-subcarrier amplitudes |
|
|
| amp_max | float | Peak subcarrier amplitude |
|
|
| amp_max_idx | uint8 | Index (0-63) of peak subcarrier |
|
|
| energy | uint32 | L1 norm (same as adaptive sampling) |
|
|
|
|
```bash
|
|
esp-cmd amber-maple.local CSIMODE COMPACT # Switch to compact
|
|
esp-cmd amber-maple.local CSIMODE HYBRID 10 # Raw every 10th packet
|
|
esp-cmd amber-maple.local CSIMODE RAW # Back to full I/Q
|
|
esp-cmd amber-maple.local CSIMODE # Query current mode
|
|
```
|
|
|
|
Mode is NVS-persisted and survives reboots.
|
|
|
|
## Adaptive Sampling
|
|
|
|
When enabled, the device automatically adjusts ping rate based on CSI wander:
|
|
|
|
- **Motion detected** (wander > threshold): 100 pkt/s
|
|
- **Idle** (wander < threshold for 3s): 10 pkt/s
|
|
- Rate changes send `EVENT,<hostname>,motion=<0|1> rate=<hz> wander=<value>` via UDP
|
|
|
|
```bash
|
|
esp-cmd amber-maple.local ADAPTIVE ON # Enable
|
|
esp-cmd amber-maple.local THRESHOLD 0.005 # Tune sensitivity
|
|
# Lower threshold = more sensitive, higher = less sensitive
|
|
# Good starting range: 0.001 - 0.01
|
|
```
|
|
|
|
## Presence Detection
|
|
|
|
CSI-based presence detection compares live per-subcarrier amplitudes against
|
|
a baseline captured with the room empty.
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# 1. Ensure room is empty, CSI is flowing (200+ packets)
|
|
esp-cmd amber-maple.local CALIBRATE 10 # Capture 10s baseline
|
|
# Wait for: EVENT,amber-maple,calibrate=done packets=1000 nsub=52
|
|
|
|
# 2. Verify baseline
|
|
esp-cmd amber-maple.local CALIBRATE STATUS # Should show "valid nsub=52"
|
|
|
|
# 3. Enable presence detection
|
|
esp-cmd amber-maple.local PRESENCE ON # Starts scoring
|
|
|
|
# 4. Tune threshold (optional)
|
|
esp-cmd amber-maple.local PRESENCE THRESHOLD 0.08 # Higher = less sensitive
|
|
```
|
|
|
|
### How It Works
|
|
|
|
- **Baseline:** Average per-subcarrier amplitude over N seconds (stored in NVS)
|
|
- **Scoring:** Normalized Euclidean distance: `sqrt(sum((live-baseline)^2) / sum(baseline^2))`
|
|
- **Window:** Rolling average of 50 scores
|
|
- **Threshold:** Default 0.05 (5% normalized distance)
|
|
- **Holdoff:** 10s debounce between state transitions
|
|
- **Events:** `EVENT,<hostname>,presence=1 score=0.0832` / `EVENT,<hostname>,presence=0 score=0.0234`
|
|
- **Memory:** ~1 KB total (baseline + accumulators + score buffer)
|
|
|
|
### LED States
|
|
|
|
Default mode is **quiet** (LED off unless noteworthy). Use `LED AUTO` for constant blink.
|
|
|
|
| LED (quiet mode) | Meaning |
|
|
|-------------------|---------|
|
|
| Off | Normal operation |
|
|
| Solid | Motion or presence detected |
|
|
| Double blink | OTA in progress |
|
|
| Solid (5s) | IDENTIFY command active |
|
|
|
|
| LED (auto mode) | Meaning |
|
|
|------------------|---------|
|
|
| Off | Not connected to WiFi |
|
|
| Slow blink (1 Hz) | Connected, no CSI activity |
|
|
| Fast blink (5 Hz) | CSI data flowing |
|
|
| Double blink | OTA in progress |
|
|
|
|
## Sensor Discovery
|
|
|
|
```bash
|
|
esp-ctl discover # Find all sensors via mDNS
|
|
esp-ctl discover -t 5 # Longer browse (5s timeout)
|
|
esp-ctl status --discover # Status using discovered fleet
|
|
esp-ctl target --discover # Query targets via discovery
|
|
```
|
|
|
|
Sensors register their hostname via mDNS on boot.
|
|
|
|
## HMAC Command Authentication
|
|
|
|
```bash
|
|
# Set auth secret on device (requires existing auth or serial access)
|
|
esp-ctl cmd amber-maple.local "AUTH mysecretkey123"
|
|
|
|
# Set env var so all tools sign commands automatically
|
|
export ESP_CMD_SECRET="mysecretkey123" # add to ~/.bashrc.secrets
|
|
|
|
# All esp-cmd/esp-ctl/esp-fleet/esp-ota commands auto-sign when ESP_CMD_SECRET is set
|
|
# Unsigned commands are rejected with "ERR AUTH required"
|
|
```
|
|
|
|
Protocol: `HMAC:<32hex>:<uptime_s>:<cmd>` — first 32 hex chars of HMAC-SHA256(secret, `<uptime_s>:<cmd>`).
|
|
Replay window: +/-5s from device uptime.
|
|
|
|
### Serial Console (physical access)
|
|
|
|
Connect via USB serial (921600 baud) for auth management without network auth:
|
|
|
|
```bash
|
|
# Connect to serial console
|
|
idf.py -p /dev/ttyUSB0 monitor # or: screen /dev/ttyUSB0 921600
|
|
|
|
# Serial commands (type directly):
|
|
AUTH # Show full secret (unredacted)
|
|
AUTH <secret> # Set new secret (8-64 chars)
|
|
AUTH OFF # Clear secret (disable auth)
|
|
STATUS # Basic device info
|
|
HELP # List serial commands
|
|
```
|
|
|
|
### Provisioning Tool
|
|
|
|
```bash
|
|
esp-provision # Auto-generate secret, set via serial
|
|
esp-provision mysecretkey123 # Set specific secret via serial
|
|
esp-provision --serial # Set via serial console (device running)
|
|
esp-provision --generate-only # Just print a random secret
|
|
esp-provision -p /dev/ttyACM0 # Use different serial port
|
|
```
|
|
|
|
## OUI Vendor Lookup
|
|
|
|
```bash
|
|
esp-ctl oui --update # Download IEEE OUI database (~30k entries)
|
|
esp-ctl oui b0:be:76:a1:2d:c0 # Look up vendor for a MAC
|
|
```
|
|
|
|
## Watch Daemon & OSINT
|
|
|
|
```bash
|
|
esp-ctl watch # Listen on :5500, store probes/BLE/alerts in DB
|
|
esp-ctl watch -c ~/my-config.yaml # Custom config (HA webhooks, known MACs)
|
|
esp-ctl watch -v # Verbose logging
|
|
|
|
esp-ctl osint probes # Probe SSID history table
|
|
esp-ctl osint probes --mac AA:BB:CC:DD:EE:FF # Filter by MAC
|
|
esp-ctl osint devices # All device sightings
|
|
esp-ctl osint devices -t ble # BLE only
|
|
esp-ctl osint mac AA:BB:CC:DD:EE:FF # Full profile for one MAC
|
|
esp-ctl osint stats # Summary counts
|
|
```
|
|
|
|
DB: `~/.local/share/esp-ctl/osint.db` (SQLite with WAL).
|
|
Config: `~/.config/esp-ctl/watch.yaml` (HA webhooks, known MACs file).
|
|
|
|
## Test CSI Reception
|
|
|
|
```bash
|
|
nc -lu 5500 # Listen for CSI packets
|
|
socat UDP-RECV:5500 STDOUT # Alternative listener
|
|
nc -lu 5500 | head -1 # See one packet
|
|
nc -lu 5500 | wc -l # Count packets/sec (Ctrl+C)
|
|
esp-ctl listen -f probe -n 5 # Capture 5 probe requests (ESP32-C6+ only)
|
|
esp-ctl listen -f alert # Monitor deauth/disassoc alerts (ESP32-C6+ only)
|
|
```
|
|
|
|
## Firmware Variants
|
|
|
|
| Firmware | Dir | Output | Needs |
|
|
|----------|-----|--------|-------|
|
|
| csi_recv_router | `get-started/csi_recv_router/` | UDP | WiFi router |
|
|
| csi_recv | `get-started/csi_recv/` | Serial | csi_send device |
|
|
| csi_send | `get-started/csi_send/` | N/A | csi_recv device |
|
|
|
|
## Key Config (menuconfig)
|
|
|
|
| Path | Setting |
|
|
|------|---------|
|
|
| Example Connection Configuration → SSID | WiFi network name |
|
|
| Example Connection Configuration → Password | WiFi password |
|
|
| CSI UDP Configuration → IP | `192.168.129.11` |
|
|
| CSI UDP Configuration → Port | `5500` |
|
|
| CSI UDP Configuration → Cmd port | `5501` |
|
|
| CSI UDP Configuration → Hostname | mDNS name (e.g., `amber-maple`) |
|
|
|
|
## USB Flash Notes
|
|
|
|
- **Use 460800 baud** (`-b 460800`) — 921600 causes connection failures on some boards
|
|
- Hostname can be changed at runtime via `esp-ctl cmd <host> HOSTNAME <name>`
|
|
- First flash after enabling OTA partitions must be via USB
|
|
|
|
## Data Packet Formats
|
|
|
|
All data packets include sensor hostname after the type tag:
|
|
|
|
```
|
|
CSI_DATA,<hostname>,seq,mac,rssi,rate,...,len,first_word,"[I,Q,...]" # RAW mode
|
|
CSI_DATA,<hostname>,seq,mac,rssi,rate,...,len,first_word,"F:rms,std,max,idx,energy" # COMPACT mode
|
|
BLE_DATA,<hostname>,mac,rssi,pub|rnd,name
|
|
EVENT,<hostname>,motion=0|1 rate=<hz> wander=<value>
|
|
EVENT,<hostname>,calibrate=done packets=<n> nsub=<n>
|
|
EVENT,<hostname>,presence=0|1 score=<float>
|
|
ALERT_DATA,<hostname>,deauth|disassoc,sender_mac,target_mac,rssi
|
|
ALERT_DATA,<hostname>,deauth_flood,<count>,<window_s>
|
|
PROBE_DATA,<hostname>,mac,rssi,ssid
|
|
```
|
|
|
|
**CSI mode discriminator:** quoted field starts with `[` (raw) or `F:` (compact).
|
|
|
|
**Note:** On original ESP32, promiscuous mode (ALERT_DATA, PROBE_DATA) is disabled
|
|
because it breaks CSI data collection at the driver level. These packet types are
|
|
only generated on ESP32-C6 and newer chips.
|
|
|
|
## STATUS Fields
|
|
|
|
Unauthenticated STATUS returns a minimal subset. Full fields require HMAC auth.
|
|
|
|
| Field | Example | Auth | Description |
|
|
|-------|---------|------|-------------|
|
|
| hostname | amber-maple | no | Device hostname |
|
|
| uptime | 1h23m | no | Human-readable uptime |
|
|
| uptime_s | 4980 | no | Raw uptime in seconds |
|
|
| rssi | -67 | no | Current AP RSSI (dBm) |
|
|
| channel | 11 | no | WiFi channel |
|
|
| version | 27aeddb | no | Firmware git commit |
|
|
| motion | 0/1 | no | Motion detected |
|
|
| presence | on/off | no | Presence detection |
|
|
| heap | 108744 | yes | Free heap bytes |
|
|
| tx_power | 10 | yes | TX power (dBm) |
|
|
| rate | 100 | yes | Target CSI rate (Hz) |
|
|
| csi_rate | 97 | yes | Actual CSI rate (Hz, computed) |
|
|
| adaptive | on/off | yes | Adaptive sampling |
|
|
| ble | on/off | yes | BLE scanning |
|
|
| target | 192.168.129.11:5500 | yes | UDP destination |
|
|
| temp | 0.0 | yes | Chip temperature (ESP32-S2/C3/C6 only) |
|
|
| csi_count | 30002 | yes | Total CSI frames since boot |
|
|
| boots | 3 | yes | Boot count (NVS persisted) |
|
|
| rssi_min | -71 | yes | Lowest RSSI since boot |
|
|
| rssi_max | -62 | yes | Highest RSSI since boot |
|
|
| csi_mode | raw/compact/hybrid | yes | CSI output mode |
|
|
| hybrid_n | 10 | yes | Raw packet interval (hybrid mode) |
|
|
| auth | on/off | yes | HMAC command authentication |
|
|
| flood_thresh | 5/10 | yes | Deauth flood: count/window_seconds |
|
|
| powersave | on/off | yes | WiFi modem sleep |
|
|
| pr_score | 0.0432 | yes | Current presence score |
|
|
|
|
## PROFILE Sections
|
|
|
|
| Section | Fields | Description |
|
|
|---------|--------|-------------|
|
|
| HEAP | free, min, dram, iram | Heap usage and watermarks |
|
|
| TASKS | stack_free per task | Per-task stack high watermark |
|
|
| CPU | % per task | FreeRTOS runtime stats (requires `CONFIG_FREERTOS_USE_TRACE_FACILITY`) |
|
|
|
|
## Source Paths
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `get-started/csi_recv_router/main/app_main.c` | Main firmware source |
|
|
| `get-started/csi_recv_router/main/Kconfig.projbuild` | UDP/cmd port config |
|
|
| `tools/esp-cmd` | Pi-side management CLI |
|
|
| `tools/esp-ota` | Pi-side OTA update tool |
|
|
| `tools/esp-fleet` | Fleet-wide command tool |
|
|
| `get-started/csi_recv_router/sdkconfig.defaults` | SDK defaults |
|
|
| `get-started/csi_recv_router/main/idf_component.yml` | Dependencies |
|
|
| `get-started/csi_recv_router/CMakeLists.txt` | Build config |
|
|
|
|
## ESP-IDF Paths
|
|
|
|
| Path | Contents |
|
|
|------|----------|
|
|
| `~/esp/esp-idf/` | ESP-IDF v5.5.2 |
|
|
| `~/esp/esp-csi/` | Original esp-csi examples |
|
|
| `~/.espressif/tools/` | Xtensa toolchain |
|
|
|
|
## USB Serial
|
|
|
|
```bash
|
|
ls /dev/ttyUSB* /dev/ttyACM* # Find connected devices
|
|
dmesg | tail # Check USB detection
|
|
sudo usermod -aG dialout $USER # Fix permissions (re-login)
|
|
```
|
|
|
|
## Security Testing
|
|
|
|
```bash
|
|
# eFuse status (read-only, safe)
|
|
source ~/esp/esp-idf/export.sh && espefuse.py -p /dev/ttyUSB0 summary
|
|
|
|
# NVS dump (read-only)
|
|
esptool.py -p /dev/ttyUSB0 -b 921600 read_flash 0x9000 0x4000 /tmp/nvs_dump.bin
|
|
|
|
# Port scan
|
|
sudo nmap -sU -p 5500,5501,5353 --open <sensor-ip>
|
|
sudo nmap -sT -p 1-1000 <sensor-ip>
|
|
|
|
# Firmware binary analysis
|
|
binwalk build/csi_recv_router.bin
|
|
strings -n 6 build/csi_recv_router.bin | grep -iE 'password|secret|key'
|
|
```
|
|
|
|
Full pentest guide: `docs/PENTEST.md`
|
|
Pentest results: `docs/PENTEST-RESULTS.md`
|