A sophisticated WiFi auditing library for ESP32 microcontrollers

Politician is an embedded C++ library designed for WiFi security auditing on ESP32 platforms. It provides a clean, modern API for capturing WPA/WPA2/WPA3 handshakes and harvesting enterprise credentials using advanced 802.11 protocol techniques.
Key Capabilities
- PMKID Capture: Extract PMKIDs from association responses without client disconnection
- CSA (Channel Switch Announcement) Injection: Modern alternative to deauthentication attacks
- Enterprise Credential Harvesting: Capture EAP-Identity frames from 802.1X networks
- Hidden Network Discovery: Automatic SSID decloaking via probe response interception
- Device Fingerprinting: Passively identify 150+ consumer IoT/smart home brands via MAC OUI and IE signatures without network association
- Client Stimulation: Wake sleeping mobile devices using QoS Null Data frames
- WPA3/PMF Detection: Intelligent filtering to skip Protected Management Frame-enabled networks
- Export Formats: PCAPNG capture files; optional HC22000 text export for direct Hashcat ingestion
Architecture
The library is built around a non-blocking state machine that manages channel hopping, target selection, attack execution, and capture processing. All operations are contained within the politician namespace.
Core Components
| Component | Description |
Politician | Main engine class managing the audit lifecycle |
PoliticianFormat | PCAPNG capture serialization; auxiliary HC22000 text export |
PoliticianStorage | Optional SD card logging and NVS persistence |
PoliticianStress | Decoupled DoS/disruption payload delivery (opt-in) |
PoliticianTypes | Core data structures and enumerations |
Attack Modes
Traditional deauthentication attacks are ineffective against modern WPA3 and WPA2 networks with Protected Management Frames (PMF/802.11w). Politician implements modern alternatives:
| Mode | Description | Effectiveness |
ATTACK_PMKID | Extract PMKID via dummy authentication | Works on all WPA2/WPA3-Transition |
ATTACK_CSA | Channel Switch Announcement injection | Bypasses PMF protections |
ATTACK_DEAUTH | Legacy deauthentication (Reason 7) | WPA2 without PMF only |
ATTACK_STIMULATE | QoS Null Data for sleeping clients | Non-intrusive client wake-up |
ATTACK_PASSIVE | Listen-only mode | Zero transmission |
ATTACK_ALL | Enable all active attack vectors | Maximum aggression |
Installation
PlatformIO
Add to your platformio.ini:
[env:myboard]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
Politician
Or clone directly into your project's lib/ directory:
cd lib/
git clone https://github.com/0ldev/Politician.git
Arduino IDE
- Download the library as a ZIP file
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library
- Select the downloaded ZIP file
ESP-IDF
Clone the repository into your project's components/ directory:
cd components/
git clone https://github.com/0ldev/Politician.git
Create a components/Politician/CMakeLists.txt component descriptor:
idf_component_register(
SRCS
"src/Politician.cpp"
"src/PoliticianFormat.cpp"
"src/PoliticianStress.cpp"
INCLUDE_DIRS "src"
)
PoliticianStorage.h is not available under ESP-IDF — it emits a #error at compile time if included outside Arduino. Use ESP-IDF's VFS and nvs_flash APIs directly for any persistence you need.
Quick Start
Basic Handshake Capture
#include <Arduino.h>
#include <SD.h>
Serial.printf("\n[✓] Captured: %s ch%d rssi=%d type=%d\n",
PcapngFileLogger::append(SD, "/captures.pcapng", rec);
}
Serial.begin(115200);
SD.begin();
}
}
The core WiFi handshake capturing engine.
void onHandshake(const HandshakeRecord &rec)
Configuration for the Politician engine.
A captured handshake or PMKID record delivered to the EapolCb callback.
Bare ESP-IDF Quick Start
Under ESP-IDF, begin() calls esp_wifi_init() internally but expects NVS and the default event loop to already be initialized. Call those before begin(), then drive the engine from a FreeRTOS task.
#include <nvs_flash.h>
#include <esp_event.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
printf("[+] Captured: %s ch%d rssi=%d type=%d\n",
}
static void audit_task(void *) {
engine.setEapolCallback(on_handshake);
if (
engine.begin(cfg) != OK) {
printf("[!] WiFi init failed\n");
vTaskDelete(nullptr);
return;
}
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1));
}
}
extern "C" void app_main(void) {
nvs_flash_init();
esp_event_loop_create_default();
xTaskCreate(audit_task, "politician", 8192, nullptr, 5, nullptr);
}
API Reference
Politician Class
The main engine class. Must call tick() in your main loop.
Initialization
Initialize the engine. Returns OK on success or an Error code on failure. Must be called before any other method.
Configuration Structure
uint16_t hop_dwell_ms = 200;
bool smart_hopping = true;
uint16_t hop_min_dwell_ms = 50;
uint16_t hop_max_dwell_ms = 400;
uint32_t m1_lock_ms = 800;
uint32_t fish_timeout_ms = 2000;
uint8_t fish_max_retries = 2;
uint32_t csa_wait_ms = 4000;
uint8_t csa_beacon_count = 8;
uint8_t deauth_burst_count = 16;
uint8_t csa_deauth_count = 15;
uint16_t probe_aggr_interval_s = 30;
uint32_t session_timeout_ms = 60000;
bool capture_half_handshakes = false;
bool skip_immune_networks = true;
int8_t min_rssi = -100;
uint32_t ap_expiry_ms = 300000;
bool unicast_deauth = true;
uint32_t probe_hidden_interval_ms = 0;
uint8_t deauth_reason = 7;
bool deauth_reason_cycling = true;
bool capture_group_keys = false;
uint8_t min_beacon_count = 0;
uint8_t max_total_attempts = 0;
uint8_t sta_filter[6] = {};
char ssid_filter[33] = {};
bool ssid_filter_exact = true;
uint8_t enc_filter_mask = 0xFF;
bool require_active_clients = false;
};
#define LOG_FILTER_HANDSHAKES
#define LOG_FILTER_PROBES
Advanced Features
Autonomous Hunter (Fingerprint-Aware Targeting)
Prioritize specific device vendors during autoTarget using the built-in OUI database:
int hunterScore(
const ApRecord &ap,
const char *vendor) {
if (strstr(vendor, "Apple")) score += 50;
if (strstr(vendor, "Hikvision")) score += 80;
return score;
}
engine.setTargetScoreCallback(hunterScore);
}
Snapshot of a discovered Access Point from the internal cache.
Custom Frame Injection & Fuzzing
Inject arbitrary 802.11 frames with precise channel control:
uint8_t malformedFrame[] = { 0x40, 0x00, ... };
engine.injectCustomFrame(malformedFrame,
sizeof(malformedFrame), 6, 100);
engine.injectCustomFrame(malformedFrame,
sizeof(malformedFrame), 11, 0,
true);
}
Disconnection Strategy
Optimize stealth by chaining attack methods sequentially:
engine.setDisconnectionStrategy(STRATEGY_AUTO_FALLBACK);
}
802.11u Interworking Discovery
Discover the physical venue context of public networks:
Callbacks
void setEapolCallback(EapolCb cb);
void setApFoundCallback(ApFoundCb cb);
void setIdentityCallback(IdentityCb cb);
void setAttackResultCallback(AttackResultCb cb);
void setTargetFilter(TargetFilterCb cb);
void setPacketLogger(PacketCb cb);
void setProbeRequestCallback(ProbeRequestCb cb);
void setDisruptCallback(DisruptCb cb);
void setClientFoundCallback(ClientFoundCb cb);
void setRogueApCallback(RogueApCb cb);
State & Stats
bool isActive() const;
bool isAttacking() const;
bool hasTarget() const;
uint8_t getChannel() const;
int8_t getLastRssi() const;
void resetStats();
int getApCount() const;
bool getAp(
int idx,
ApRecord &out)
const;
bool getApByBssid(
const uint8_t* bssid,
ApRecord &out)
const;
int getClientCount(const uint8_t* bssid) const;
bool getClient(const uint8_t* bssid, int idx, uint8_t out_sta[6]) const;
Cumulative frame and capture counters for the engine session.
Engine Control
void setActive(bool active);
void setLogger(LogCb cb);
Target & Channel Control
Error setTarget(
const uint8_t* bssid, uint8_t channel);
void clearTarget();
Error setChannel(uint8_t ch);
Error lockChannel(uint8_t ch);
void startHopping(uint16_t dwellMs = 0);
void stopHopping();
void stop();
void setChannelList(const uint8_t* channels, uint8_t count);
void setChannelBands(bool ghz24, bool ghz5);
Error setTargetBySsid(
const char* ssid);
void setAutoTarget(bool enable);
Captured List
void markCaptured(const uint8_t* bssid);
void clearCapturedList();
void setIgnoreList(const uint8_t (*bssids)[6], uint8_t count);
Attack Control
void setAttackMask(uint8_t mask);
void setAttackMaskForBssid(const uint8_t* bssid, uint8_t mask);
void clearAttackMaskOverrides();
Attack Mode Constants
#define ATTACK_PMKID 0x01
#define ATTACK_CSA 0x02
#define ATTACK_PASSIVE 0x04
#define ATTACK_DEAUTH 0x08
#define ATTACK_STIMULATE 0x10
#define ATTACK_ALL 0x1F
Capture Type Constants
#define CAP_PMKID 0x01
#define CAP_EAPOL 0x02
#define CAP_EAPOL_CSA 0x03
#define CAP_EAPOL_HALF 0x04
#define CAP_EAPOL_GROUP 0x05
Capture Filter Constants
#define LOG_FILTER_HANDSHAKES 0x01
#define LOG_FILTER_PROBES 0x02
#define LOG_FILTER_BEACONS 0x04
#define LOG_FILTER_PROBE_REQ 0x08
#define LOG_FILTER_MGMT_DISRUPT 0x10
#define LOG_FILTER_ALL 0xFF
Data Structures
Stats
uint32_t total;
uint32_t mgmt;
uint32_t ctrl;
uint32_t data;
uint32_t eapol;
uint32_t pmkid_found;
uint32_t beacons;
uint32_t captures;
uint32_t failed_pmkid;
uint32_t failed_csa;
uint16_t channel_frames[14];
};
HandshakeRecord
uint8_t type;
uint8_t channel;
int8_t rssi;
uint8_t bssid[6];
uint8_t sta[6];
char ssid[33];
uint8_t ssid_len;
uint8_t enc;
uint8_t pmkid[16];
uint8_t anonce[32];
uint8_t mic[16];
uint8_t eapol_m2[256];
uint16_t eapol_m2_len;
bool has_mic;
bool has_anonce;
};
EapIdentityRecord
uint8_t bssid[6];
uint8_t client[6];
char identity[65];
uint8_t channel;
int8_t rssi;
};
A harvested 802.1X Enterprise plaintext identity, delivered to the IdentityCb callback.
ApRecord
uint8_t bssid[6];
char ssid[33];
uint8_t ssid_len;
uint8_t channel;
int8_t rssi;
uint8_t enc;
bool wps_enabled;
bool pmf_capable;
bool pmf_required;
uint8_t total_attempts;
bool captured;
bool ft_capable;
uint32_t first_seen_ms;
uint32_t last_seen_ms;
char country[3];
uint16_t beacon_interval;
uint8_t max_rate_mbps;
};
AttackResultRecord
RESULT_PMKID_EXHAUSTED = 1,
RESULT_CSA_EXPIRED = 2,
};
uint8_t bssid[6];
char ssid[33];
uint8_t ssid_len;
};
Identifies the AP and failure reason for a failed attack, delivered to the AttackResultCb callback.
RogueApRecord
uint8_t known_bssid[6];
uint8_t rogue_bssid[6];
char ssid[33];
uint8_t ssid_len;
uint8_t channel;
int8_t rssi;
};
Fired when a second BSSID advertising the same SSID is observed on the same channel.
ProbeRequestRecord
uint8_t client[6];
uint8_t channel;
int8_t rssi;
char ssid[33];
uint8_t ssid_len;
bool rand_mac;
};
A probe request frame observed on the air, delivered to the ProbeRequestCb callback.
DisruptRecord
uint8_t src[6];
uint8_t dst[6];
uint8_t bssid[6];
uint16_t reason;
uint8_t subtype;
uint8_t channel;
int8_t rssi;
bool rand_mac;
};
A deauthentication or disassociation frame observed on the air, delivered to the DisruptCb callback.
Format Utilities
PCAPNG is the primary capture format — it is tool-agnostic, preserves full frame context, and can be opened in Wireshark or piped through hcxpcapngtool. HC22000 is an auxiliary text export for users who want to feed captures directly into hashcat without an intermediate conversion step.
size_t writePcapngGlobalHeader(uint8_t* buffer);
size_t writePcapngRecord(
const HandshakeRecord& rec, uint8_t* buffer,
size_t max_len);
size_t writePcapngPacket(const uint8_t* payload, size_t len,
int8_t rssi, uint8_t channel, uint64_t ts_usec,
uint8_t* buffer, size_t max_len);
Stress Utilities (Opt-in)
Requires #include <PoliticianStress.h>. Not linked unless explicitly included.
stress::saeCommitFlood(const uint8_t* bssid, uint32_t count = 1000);
stress::probeRequestFlood(uint32_t count = 1000);
Storage Utilities (Optional)
Requires #include <PoliticianStorage.h>.
PcapngFileLogger::append(fs::FS& fs, const char* path,
PcapngFileLogger::appendPacket(fs::FS& fs, const char* path,
const uint8_t* payload, uint16_t len,
int8_t rssi, uint32_t ts_usec);
WigleCsvLogger::append(fs::FS& fs, const char* path,
float alt = 0.0, float acc = 10.0,
const char* timestamp = nullptr);
WigleCsvLogger::appendAp(fs::FS& fs, const char* path,
const ApRecord& ap,
float lat,
float lon,
float alt = 0.0, float acc = 10.0,
const char* timestamp = nullptr);
Hc22000FileLogger::append(fs::FS& fs, const char* path,
EnterpriseCsvLogger::append(fs::FS& fs, const char* path,
Usage Examples
Targeted Network Auditing
Use callbacks to filter networks by signal strength, encryption type, or SSID pattern:
if (ap.
rssi < -70)
return false;
if (ap.
enc < 3)
return false;
if (strstr(ap.
ssid,
"CORP-") !=
nullptr)
return false;
return true;
});
Selective Attack Modes
Enterprise Credential Harvesting
char bssid[18];
snprintf(bssid, sizeof(bssid), "%02X:%02X:%02X:%02X:%02X:%02X",
rec.bssid[0], rec.bssid[1], rec.bssid[2],
rec.bssid[3], rec.bssid[4], rec.bssid[5]);
Serial.printf("[802.1X] %s → %s\n", bssid, rec.identity);
EnterpriseCsvLogger::append(SD, "/identities.csv", rec);
}
engine.setIdentityCallback(onIdentity);
cfg.hop_dwell_ms = 800;
}
Persistent Storage
The core library is decoupled from filesystem dependencies. Optionally include PoliticianStorage.h for SD card logging:
#include <SD.h>
PcapngFileLogger::append(SD, "/captures.pcapng", rec);
}
void onPacket(const uint8_t* payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts) {
PcapngFileLogger::appendPacket(SD, "/intel.pcapng", payload, len, rssi, channel, ts);
}
SD.begin();
engine.setPacketLogger(onPacket);
}
⚠️ Logging Performance Warning
Beacon logging (LOG_FILTER_BEACONS) can generate 500+ writes/second. Standard SPI SD card writes are blocking and will freeze the engine. For high-volume logging, use ESP32 boards with native SDMMC (4-bit) hardware support and DMA.
GPS Integration (Wigle.net)
Combine with a GPS module for wardriving datasets:
#include <TinyGPS++.h>
TinyGPSPlus gps;
if (gps.location.isValid()) {
WigleCsvLogger::appendAp(SD, "/wardrive.csv", ap,
gps.location.lat(),
gps.location.lng());
}
}
if (gps.location.isValid()) {
WigleCsvLogger::append(SD, "/wardrive.csv", rec,
gps.location.lat(),
gps.location.lng());
}
}
Advanced Features
Half-Handshakes and Smart Pivot
When cfg.capture_half_handshakes = true, the engine fires the EAPOL callback with type = CAP_EAPOL_HALF on M2-only captures. These records have no anonce so they cannot be directly cracked, but they confirm an active client is present.
The engine immediately executes a Smart Pivot:
- Marks the network as having active clients
- Launches CSA/Deauth to force a fresh 4-way handshake
- Captures the complete M1+M2 on reconnection
Attack Result Callbacks
Register setAttackResultCallback() to be notified when an attack exhausts all options without capturing anything. Useful for logging failed targets or adjusting strategy at runtime:
char bssid[18];
snprintf(bssid, sizeof(bssid), "%02X:%02X:%02X:%02X:%02X:%02X",
res.bssid[0], res.bssid[1], res.bssid[2],
res.bssid[3], res.bssid[4], res.bssid[5]);
if (res.result == RESULT_PMKID_EXHAUSTED)
Serial.printf("[!] PMKID failed: %s (%s)\n", res.ssid, bssid);
else if (res.result == RESULT_CSA_EXPIRED)
Serial.printf("[!] CSA/Deauth timed out: %s (%s)\n", res.ssid, bssid);
});
802.11r Fast Transition Detection
The engine detects 802.11r Fast Transition AKMs (FT-PSK suite type 4, FT-EAP suite type 3) in beacon and probe-response RSN IEs. When detected, ApRecord.ft_capable is set to true and a log note is emitted during PMKID fishing.
For FT Transition Mode APs (advertising both FT-PSK and regular WPA2-PSK), standard PMKID capture via the WPA2-PSK path works normally. For FT-only APs, the captured PMKID is FT-derived — save it as PCAPNG and use FT-aware offline tools (e.g. hcxpcapngtool --enable_ft) for cracking.
Hidden Network Discovery
Probe Response frames triggered by deauth bursts automatically reveal hidden SSIDs. The engine caches these with zero configuration required.
PMF/WPA3 Detection
RSNE (Robust Security Network Element) parsing automatically identifies networks with PMF Required. These are skipped to save time, but WPA3 Transition Mode networks (PMF Capable but not Required) are still targeted.
ApRecord exposes pmf_capable and pmf_required so setTargetFilter callbacks can make finer-grained decisions than the binary skip_immune_networks config field — for example, to target only WPA3 Transition networks (PMF capable but not required).
Examples
The library includes complete examples demonstrating various use cases:
| Example | Description |
DeviceFingerprinting | Passive discovery of IoT and consumer electronics |
TargetedAuditing | Network filtering with callbacks |
EnterpriseAuditing | 802.1X identity harvesting |
StorageAndNVS | SD card PCAPNG logging and NVS persistence |
WigleIntegration | GPS wardriving with Wigle CSV export |
ExportFormats | PCAPNG capture and auxiliary HC22000 text export |
DynamicControl | Runtime attack mode switching |
AutoEnterpriseHunter | Automatic enterprise network targeting |
SerialStreaming | Real-time packet streaming |
StressTest | Performance and memory testing |
See the examples/ directory for complete source code.
Documentation
Full API documentation is available in the docs/ directory. Generate fresh documentation:
Then open docs/html/index.html in your browser.
Hardware Requirements
- Platform: ESP32, ESP32-S2, ESP32-S3, ESP32-C3 (ESP32-C6 pending Arduino framework support in PlatformIO)
- Framework: Arduino or ESP-IDF — both are supported natively via
src/politician_compat.h. PoliticianStorage.h requires Arduino and will not compile under ESP-IDF.
- Memory: Minimum 4MB flash recommended
- Optional: SD card module for persistent logging
- Optional: GPS module for Wigle integration
Performance Considerations
- Channel Hopping: Default 200ms dwell time balances discovery speed vs. capture reliability
- Memory: Core engine uses ~45KB RAM. Storage helpers are opt-in
- CPU: Non-blocking state machine keeps
loop() responsive
- Half-Handshakes: Enable for better capture rate on fast-hopping scenarios
Troubleshooting
No handshakes captured:
- Verify WiFi is enabled and promiscuous mode works
- Increase
hop_dwell_ms for slow-reconnecting devices
- Check if target networks use PMF Required (will be auto-skipped)
- Try
ATTACK_ALL mask for maximum aggression
SD card writes fail:
- Ensure SD.begin() succeeds before logging
- Check file permissions and available space
- Disable
LOG_FILTER_BEACONS if using SPI SD cards
Enterprise identities not captured:
- Increase
hop_dwell_ms to 800-1200ms for EAP exchanges
- Use
ATTACK_PASSIVE or ATTACK_STIMULATE only
- Aggressive attacks may interrupt EAP authentication
Legal & Ethical Use
This library is intended for:
- ✅ Authorized penetration testing
- ✅ Security research in controlled environments
- ✅ Educational purposes with permission
- ✅ Auditing your own networks
Unauthorized access to networks you do not own or have permission to test is illegal under laws such as the Computer Fraud and Abuse Act (CFAA) in the United States and similar legislation worldwide.
The authors and contributors assume no liability for misuse of this software.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests/examples for new features
- Submit a pull request
License
MIT License - see [LICENSE](LICENSE) for details.
Acknowledgments
Special thanks to justcallmekoko for inspiring this project and the broader hardware hacking community through the ESP32 Marauder project. Years of learning from Marauder's innovative approaches to WiFi security research have been invaluable.