Politician 1.0.0
WiFi Auditing Library for ESP32
Loading...
Searching...
No Matches
Politician.h
Go to the documentation of this file.
1#pragma once
2#include "politician_compat.h"
3#include <esp_wifi.h>
4#include <esp_wifi_types.h>
5#include <freertos/FreeRTOS.h>
6#include <freertos/task.h>
7#include <freertos/ringbuf.h>
8#include <freertos/semphr.h>
9#include "PoliticianTypes.h"
10
11namespace politician {
12
13#ifndef POLITICIAN_MAX_AP_CACHE
14#define POLITICIAN_MAX_AP_CACHE 48
15#endif
16
17#ifndef POLITICIAN_MAX_SESSIONS
18#define POLITICIAN_MAX_SESSIONS 8
19#endif
20
21#ifndef POLITICIAN_MAX_CAPTURED
22#define POLITICIAN_MAX_CAPTURED 128
23#endif
24
25#ifndef POLITICIAN_MAX_CHANNELS
26#define POLITICIAN_MAX_CHANNELS 50
27#endif
28
29/**
30 * Maximum number of concurrent Politician instances that can be active at once.
31 * Each instance gets its own ring buffer and worker task, but they share the
32 * single Wi-Fi radio — channel changes made by one instance affect all others.
33 * On standard ESP32/ESP32-S2/ESP32-C3 the practical limit is 1; on ESP32-S3
34 * with an external SPI radio a second instance can operate on that interface.
35 * Define before including Politician.h to override the default of 2.
36 */
37#ifndef POLITICIAN_MAX_INSTANCES
38#define POLITICIAN_MAX_INSTANCES 2
39#endif
40
41// ─── 802.11 Frame Structures ──────────────────────────────────────────────────
42
43typedef struct {
44 uint16_t frame_ctrl;
45 uint16_t duration;
46 uint8_t addr1[6];
47 uint8_t addr2[6];
48 uint8_t addr3[6];
49 uint16_t seq_ctrl;
50} __attribute__((packed)) ieee80211_hdr_t;
51
52typedef struct {
53 ieee80211_hdr_t hdr;
54 uint8_t payload[0];
55} __attribute__((packed)) ieee80211_frame_t;
56
57// ─── Frame Control Masks ──────────────────────────────────────────────────────
58#define FC_TYPE_MASK 0x000C
59#define FC_SUBTYPE_MASK 0x00F0
60#define FC_TODS_MASK 0x0100
61#define FC_FROMDS_MASK 0x0200
62#define FC_TYPE_MGMT 0x0000
63#define FC_TYPE_CTRL 0x0004
64#define FC_TYPE_DATA 0x0008
65#define FC_ORDER_MASK 0x8000
66
67#define MGMT_SUB_ASSOC_REQ 0x00
68#define MGMT_SUB_ASSOC_RESP 0x10
69#define MGMT_SUB_PROBE_REQ 0x40
70#define MGMT_SUB_PROBE_RESP 0x50
71#define MGMT_SUB_BEACON 0x80
72#define MGMT_SUB_AUTH 0xB0
73#define MGMT_SUB_DISASSOC 0xA0
74#define MGMT_SUB_DEAUTH 0xC0
75
76// ─── EAPOL ────────────────────────────────────────────────────────────────────
77#define EAPOL_LLC_OFFSET 0
78#define EAPOL_ETHERTYPE_HI 0x88
79#define EAPOL_ETHERTYPE_LO 0x8E
80#define EAPOL_LLC_SIZE 8
81#define EAPOL_MIN_FRAME_LEN (EAPOL_LLC_SIZE + 4)
82
83#define EAPOL_KEY_DESC_TYPE 0
84#define EAPOL_KEY_INFO 1
85#define EAPOL_REPLAY_COUNTER 5
86#define EAPOL_KEY_NONCE 13
87#define EAPOL_KEY_MIC 77
88#define EAPOL_KEY_DATA_LEN 93
89#define EAPOL_KEY_DATA 95
90
91#define KEYINFO_TYPE_MASK 0x0007
92#define KEYINFO_PAIRWISE 0x0008
93#define KEYINFO_ACK 0x0080
94#define KEYINFO_MIC 0x0100
95#define KEYINFO_SECURE 0x0200
96#define KEYINFO_INSTALL 0x0040
97
98// ─── Politician (The Handshaker) ──────────────────────────────────────────────
99
100/**
101 * @brief The core WiFi handshake capturing engine.
102 */
104public:
105 Politician();
106
107 /**
108 * @brief Initializes the WiFi driver in promiscuous mode.
109 * @param cfg Optional configuration struct.
110 * @return OK on success, or an error code.
111 */
112 Error begin(const Config &cfg = Config());
113
114 /**
115 * @brief Sets a custom logging callback to intercept library output.
116 */
117 void setLogger(LogCb cb) { _logCb = cb; }
118
119 /**
120 * @brief Manually adds a BSSID to the "already captured" list to skip it.
121 */
122 void markCaptured(const uint8_t *bssid);
123
124 /**
125 * @brief Clears the captured BSSID list.
126 */
127 void clearCapturedList();
128
129 /**
130 * @brief Sets a list of BSSIDs that should always be ignored by the engine.
131 */
132 void setIgnoreList(const uint8_t (*bssids)[6], uint8_t count);
133
134 /**
135 * @brief Enables or disables frame processing.
136 */
137 void setActive(bool active);
138
139 /**
140 * @brief Manually sets the WiFi radio to a specific channel.
141 * @param ch Channel number (2.4GHz: 1-14, 5GHz: 36-165)
142 * @return OK on success, ERR_INVALID_CH if ch is invalid.
143 */
144 Error setChannel(uint8_t ch);
145
146 /**
147 * @brief Starts autonomous channel hopping.
148 * @param dwellMs Time in milliseconds to stay on each channel (0 = use config).
149 */
150 void startHopping(uint16_t dwellMs = 0);
151
152 /**
153 * @brief Stops autonomous channel hopping and goes idle.
154 */
155 void stopHopping();
156
157 /**
158 * @brief Full engine teardown. Aborts any in-progress attack, clears the
159 * target, stops hopping, and disables frame processing in one call.
160 * Use this instead of combining stopHopping() + clearTarget() + setActive(false).
161 */
162 void stop();
163
164 /**
165 * @brief Stops hopping and locks the radio to a specific channel.
166 * @return OK on success, or an error code.
167 */
168 Error lockChannel(uint8_t ch);
169
170 /**
171 * @brief Restricts hopping to a specific list of channels.
172 * @param channels Array of channel numbers (2.4GHz: 1-14, 5GHz: 36-165)
173 * @param count Number of channels in array
174 */
175 void setChannelList(const uint8_t *channels, uint8_t count);
176
177 /**
178 * @brief Restricts hopping to 2.4GHz, 5GHz, or both bands.
179 * @param ghz24 Include 2.4GHz channels (1-13)
180 * @param ghz5 Include 5GHz common channels (36-165)
181 */
182 void setChannelBands(bool ghz24, bool ghz5);
183
184 /**
185 * @brief Searches the AP cache by SSID and locks onto the strongest match.
186 * Equivalent to calling setTarget() on the best matching AP.
187 * @param ssid Null-terminated SSID string to search for.
188 * @return OK on success, ERR_NOT_FOUND if SSID is not in cache,
189 * ERR_ALREADY_CAPTURED if BSSID is already captured, ERR_NOT_ACTIVE if not initialized.
190 */
191 Error setTargetBySsid(const char *ssid);
192
193 /**
194 * @brief Main worker method. Must be called frequently from loop().
195 */
196 void tick();
197
198 /**
199 * @brief Configures which attack techniques are enabled globally.
200 */
201 void setAttackMask(uint8_t mask);
202
203 /**
204 * @brief Overrides the attack mask for a specific BSSID.
205 * When the engine targets this BSSID the override mask is used instead of the global mask.
206 * The override table holds up to 8 entries; oldest is evicted if full.
207 */
208 void setAttackMaskForBssid(const uint8_t *bssid, uint8_t mask);
209
210 /**
211 * @brief Sets an attack mask for all APs whose SSID matches the given string.
212 *
213 * @param ssid The SSID string to match
214 * @param mask Attack mask to apply (ATTACK_PASSIVE, ATTACK_PMKID, etc.)
215 * @param substring If true, matches any AP whose SSID contains @p ssid as a
216 * substring. If false (default), requires an exact match.
217 *
218 * Overrides are applied after per-BSSID overrides and before the global mask.
219 * If an AP matches both a BSSID override and an SSID override, the BSSID
220 * override takes precedence. Up to MAX_SSID_OVERRIDES entries are stored;
221 * subsequent calls overwrite the oldest entry on overflow.
222 */
223 void setAttackMaskForSsid(const char *ssid, uint8_t mask, bool substring = false);
224
225 /**
226 * @brief Clears all per-BSSID attack mask overrides.
227 */
229
230 /**
231 * @brief Configures how the engine handles disconnection when both CSA and Deauth are enabled.
232 * @param strategy STRATEGY_AUTO_FALLBACK (default) or STRATEGY_SIMULTANEOUS.
233 */
234 void setDisconnectionStrategy(DisconnectStrategy strategy) { _disconnectStrategy = strategy; }
235
236 /**
237 * @brief Focuses the engine on a single BSSID.
238 * @return OK on success, ERR_ALREADY_CAPTURED if BSSID is on the captured/ignore list.
239 */
240 Error setTarget(const uint8_t *bssid, uint8_t channel);
241
242 /**
243 * @brief Clears the specific target and resumes autonomous wardriving.
244 */
245 void clearTarget();
246
247 /** @return True if currently focusing on a specific target BSSID. */
248 bool hasTarget() const { return _hasTarget; }
249
250 /** @return True if an active attack (PMKID fishing or CSA/Deauth) is in progress. */
251 bool isAttacking() const { return _fishState != FISH_IDLE; }
252
253 /**
254 * @brief Continuously locks onto the strongest uncaptured AP in the cache.
255 * After each attack attempt (success or failure), automatically moves to the next best target.
256 * @param enable True to enable, false to disable and resume normal hopping.
257 */
258 void setAutoTarget(bool enable);
259
260 /** @brief Resets all frame and capture statistics to zero. */
261 void resetStats() { memset(&_stats, 0, sizeof(_stats)); }
262
263 /** @return The current operating channel. */
264 uint8_t getChannel() const { return _channel; }
265
266 /** @return True if the engine is currently processing frames. */
267 bool isActive() const { return _active; }
268
269 /** @return Signal strength (RSSI) of the last received frame. */
270 int8_t getLastRssi() const { return _lastRssi; }
271
272 /** @return Reference to the internal statistics counter. */
273 Stats& getStats() { return _stats; }
274
275 /** @return Reference to the internal configuration struct for runtime mutations. */
276 Config& getConfig() { return _cfg; }
277
278 /** @return Number of unique APs currently in the discovery cache. */
279 int getApCount() const;
280
281 /**
282 * @brief Reads an AP from the discovery cache by index.
283 * @param idx Zero-based index (0 to getApCount()-1).
284 * @param out Populated with the AP's details on success.
285 * @return True if idx is valid, false otherwise.
286 */
287 bool getAp(int idx, ApRecord &out) const;
288
289 /**
290 * @brief Looks up an AP in the discovery cache by BSSID.
291 * @param bssid 6-byte BSSID to search for.
292 * @param out Populated with the AP's details on success.
293 * @return True if found, false if the BSSID is not in cache.
294 */
295 bool getApByBssid(const uint8_t *bssid, ApRecord &out) const;
296
297 /**
298 * @brief Returns the N most active channels sorted by descending frame count.
299 * Uses the per-channel frame counters accumulated since the last resetStats() call.
300 * @param out Output array to receive sorted channel numbers
301 * @param count Maximum number of channels to return
302 * @return Actual number of channels written (≤ count)
303 */
304 uint8_t getChannelsSortedByActivity(uint8_t *out, uint8_t count) const;
305
306 /**
307 * @brief Feeds the top-N most active channels (from getChannelsSortedByActivity)
308 * into setChannelList(), replacing the current hop sequence.
309 * Call this periodically (e.g., every 60s) to adapt the hopper to live traffic.
310 * @param topN Number of top channels to keep (clamped to POLITICIAN_MAX_CHANNELS)
311 * @return Number of channels in the new hop list
312 */
313 uint8_t setAutoChannelList(uint8_t topN = 13);
314
315 /**
316 * @brief Iterates all active APs in the cache, calling @p cb for each one.
317 *
318 * The internal mutex is held for the entire iteration, making this safe on a
319 * multi-core FreeRTOS system. Do not call any blocking Politician API from
320 * within the callback.
321 *
322 * @param cb Callback invoked with each AP snapshot
323 * @param ctx Opaque user pointer passed unchanged to @p cb (may be nullptr)
324 */
325 void forEachAp(void (*cb)(const ApRecord &ap, void *ctx), void *ctx = nullptr) const;
326
327#ifndef POLITICIAN_NO_STD_FUNCTION
328 /**
329 * @brief std::function overload of forEachAp() — supports lambda captures.
330 * Gated by POLITICIAN_NO_STD_FUNCTION.
331 */
332 void forEachAp(std::function<void(const ApRecord &ap)> cb) const;
333#endif
334
335 /**
336 * @brief Returns the number of unique clients seen associated to a given AP.
337 * @param bssid 6-byte BSSID of the AP.
338 * @return Client count (0-4), or 0 if BSSID is not in cache.
339 */
340 int getClientCount(const uint8_t *bssid) const;
341
342 /**
343 * @brief Reads a client MAC from the per-AP client table.
344 * @param bssid 6-byte BSSID of the AP.
345 * @param idx Zero-based client index (0 to getClientCount()-1).
346 * @param out_sta Output buffer for the 6-byte client MAC.
347 * @return True if idx is valid, false otherwise.
348 */
349 bool getClient(const uint8_t *bssid, int idx, uint8_t out_sta[6]) const;
350
351 using _FpHookCb = void (*)(const uint8_t *mac, const char *ssid, uint8_t ssid_len, uint8_t ch, int8_t rssi, const uint8_t *ie, uint16_t ie_len);
352 void _setFingerprintHook(_FpHookCb cb) { _fpHook = cb; }
353
354#ifndef POLITICIAN_NO_STD_FUNCTION
355 using EapolCb = std::function<void(const HandshakeRecord &rec)>;
356 using ApFoundCb = std::function<void(const ApRecord &ap)>;
357 using TargetFilterCb = std::function<bool(const ApRecord &ap)>;
358 using TargetScoreCb = std::function<int(const ApRecord &ap, const char *vendor)>;
359 using PacketCb = std::function<void(const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)>;
360 using IdentityCb = std::function<void(const EapIdentityRecord &rec)>;
361 using AttackResultCb = std::function<void(const AttackResultRecord &rec)>;
362 using ProbeRequestCb = std::function<void(const ProbeRequestRecord &rec)>;
363 using DisruptCb = std::function<void(const DisruptRecord &rec)>;
364 using ClientFoundCb = std::function<void(const ClientRecord &rec)>;
365 using WpsCb = std::function<void(const WpsRecord &rec)>;
366#ifndef POLITICIAN_NO_MSCHAPV2
367 using MsChapCb = std::function<void(const MsChapRecord &rec)>;
368#endif
369#else
370 using EapolCb = void (*)(const HandshakeRecord &rec);
371 using ApFoundCb = void (*)(const ApRecord &ap);
372 using TargetFilterCb = bool (*)(const ApRecord &ap);
373 using TargetScoreCb = int (*)(const ApRecord &ap, const char *vendor);
374 using PacketCb = void (*)(const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec);
375 using IdentityCb = void (*)(const EapIdentityRecord &rec);
376 using AttackResultCb = void (*)(const AttackResultRecord &rec);
377 using ProbeRequestCb = void (*)(const ProbeRequestRecord &rec);
378 using DisruptCb = void (*)(const DisruptRecord &rec);
379 using ClientFoundCb = void (*)(const ClientRecord &rec);
380 using WpsCb = void (*)(const WpsRecord &rec);
381#ifndef POLITICIAN_NO_MSCHAPV2
382 using MsChapCb = void (*)(const MsChapRecord &rec);
383#endif
384#endif
385
386 /**
387 * @brief Looks up the vendor name for a given MAC address (OUI).
388 * @param mac 6-byte MAC address.
389 * @return The vendor string (e.g., "Apple") or an empty string if unknown.
390 */
391 static const char* getVendor(const uint8_t *mac);
392
393 /**
394 * @brief Sets the callback for calculating a custom priority score during autoTarget.
395 */
396 void setTargetScoreCallback(TargetScoreCb cb) { _targetScoreCb = cb; }
397
398 /**
399 * @brief Injects a custom 802.11 frame.
400 * @param payload The raw 802.11 frame bytes.
401 * @param len Length of the frame.
402 * @param channel The 2.4GHz or 5GHz channel to transmit on.
403 * @param lock_ms Optional. If > 0, the engine disables hopping and stays on the channel for this duration.
404 * @param wait_for_channel If true, the frame is queued until the hopper naturally reaches the channel (stealth). If false, the engine immediately switches to the channel and fires.
405 * @return OK on success, or an error code if the queue is full or engine is not initialized.
406 */
407 Error injectCustomFrame(const uint8_t *payload, size_t len, uint8_t channel, uint32_t lock_ms = 0, bool wait_for_channel = false);
408
409 /**
410 * @brief Sets the callback for when a handshake (EAPOL or PMKID) is captured.
411 */
412 void setEapolCallback(EapolCb cb) { _eapolCb = cb; }
413
414 /**
415 * @brief Sets the callback for when a new Access Point is discovered.
416 */
417 void setApFoundCallback(ApFoundCb cb) { _apFoundCb = cb; }
418
419 /**
420 * @brief Sets an early filter callback. If it returns false, the AP is ignored completely.
421 */
422 void setTargetFilter(TargetFilterCb cb) { _filterCb = cb; }
423
424 /**
425 * @brief Sets the callback for raw promiscuous mode packets.
426 */
427 void setPacketLogger(PacketCb cb) { _packetCb = cb; }
428
429 /**
430 * @brief Sets the callback for passive 802.1X Enterprise Identity harvesting.
431 */
432 void setIdentityCallback(IdentityCb cb) { _identityCb = cb; }
433
434 /**
435 * @brief Sets the callback fired when an attack attempt exhausts all options without capturing.
436 * Useful for logging failed targets or adjusting strategy at runtime.
437 */
438 void setAttackResultCallback(AttackResultCb cb) { _attackResultCb = cb; }
439
440 /**
441 * @brief Sets the callback fired on every probe request frame.
442 * Exposes the probing client MAC and requested SSID for device history reconstruction.
443 */
444 void setProbeRequestCallback(ProbeRequestCb cb) { _probeReqCb = cb; }
445
446 /**
447 * @brief Sets the callback fired on deauthentication and disassociation frames.
448 * Exposes source, destination, BSSID, reason code, and direction for attack/roaming detection.
449 */
450 void setDisruptCallback(DisruptCb cb) { _disruptCb = cb; }
451
452 /**
453 * @brief Sets the callback fired when a new client (STA) is first seen associated to an AP.
454 * Fired at most once per unique BSSID+STA pair (tracked per AP cache entry, up to 4 clients).
455 */
456 void setClientFoundCallback(ClientFoundCb cb) { _clientFoundCb = cb; }
457
458 /**
459 * @brief Sets the callback fired when a WPS Enrollee's M1 message is captured.
460 * The M1 message is the first EAP-WSC frame sent by the Enrollee and is unencrypted,
461 * revealing device attributes (name, manufacturer, model, auth/config capabilities).
462 * Only fired if the callback is set — has zero overhead otherwise.
463 */
464 void setWpsCallback(WpsCb cb) { _wpsCb = cb; }
465
466#ifndef POLITICIAN_NO_MSCHAPV2
467 /**
468 * @brief Sets the callback fired on a bare EAP-MSCHAPv2 challenge/response exchange.
469 * Only fires when an AP serves MSCHAPv2 directly without a TLS tunnel (no PEAP/TTLS).
470 * The captured MsChapRecord contains everything needed for offline cracking with
471 * asleap or hashcat mode 5500. Zero overhead if not set.
472 */
473 void setMsChapCallback(MsChapCb cb) { _msChapCb = cb; }
474#endif
475
476 /**
477 * @brief Sets the callback fired when a potential evil twin or rogue AP is detected.
478 * Triggered when a newly observed BSSID advertises the same SSID as an already-cached AP on the same channel.
479 */
480 void setRogueApCallback(RogueApCb cb) { _rogueApCb = cb; }
481
482#ifndef POLITICIAN_NO_KARMA
483 /**
484 * @brief Enable or disable the KARMA rogue AP responder at runtime.
485 *
486 * When enabled, the engine intercepts named probe requests (SSIDs != wildcard)
487 * and immediately injects a matching probe response + beacon, spoofing an open
488 * AP with that SSID. Clients configured to auto-join known networks will then
489 * associate with the engine's soft AP.
490 *
491 * @note Only effective when `cfg.karma_enabled = true` was set in `begin()`, or
492 * when called after `begin()` to toggle dynamically.
493 * @param en true to enable, false to disable
494 */
495 void enableKarma(bool en) { _karmaEnabled = en; }
496
497 /**
498 * @brief Sets the callback fired each time the KARMA responder echoes a probe.
499 * The record contains the client MAC, requested SSID, channel, and spoofed AP MAC.
500 * Zero overhead if not set.
501 */
502 void setKarmaCallback(KarmaCb cb) { _karmaCb = cb; }
503#endif
504
505 /**
506 * @brief Sets an SSID wordlist for directed probes against hidden access points.
507 * When set and probe_hidden_interval_ms > 0, the engine cycles through each SSID
508 * per hidden AP instead of sending a wildcard probe. Each hidden AP maintains its
509 * own position in the wordlist so no word is skipped.
510 *
511 * The array must remain valid for the lifetime of the engine (use PROGMEM / static storage).
512 * Pass nullptr to revert to wildcard-only probing.
513 *
514 * @param wordlist Array of null-terminated SSID strings (max 32 chars each)
515 * @param count Number of entries in the array
516 */
517 void setProbeWordlist(const char * const *wordlist, uint8_t count) {
518 _probeWordlist = wordlist;
519 _probeWordlistLen = count;
520 }
521
522private:
523 static void IRAM_ATTR _promiscuousCb(void *buf, wifi_promiscuous_pkt_type_t type);
524 static void _workerTask(void *pvParameters);
525 /** Per-process instance registry; populated by begin(), cleared by stop(). */
526 static Politician *_instances[POLITICIAN_MAX_INSTANCES];
527 /** Set to true after the first successful begin() so subsequent calls skip WiFi driver init. */
528 static bool _wifiInitialized;
529
530 RingbufHandle_t _rb = nullptr;
531 TaskHandle_t _task = nullptr;
532 SemaphoreHandle_t _lock = nullptr;
533
534 void _handleFrame(const wifi_promiscuous_pkt_t *pkt, wifi_promiscuous_pkt_type_t type);
535 void _handleMgmt(const ieee80211_hdr_t *hdr, const uint8_t *payload, uint16_t len, int8_t rssi);
536 void _handleData(const ieee80211_hdr_t *hdr, const uint8_t *payload, uint16_t len, int8_t rssi);
537 bool _parseEapol(const uint8_t *bssid, const uint8_t *sta,
538 const uint8_t *eapol, uint16_t len, int8_t rssi);
539 void _parseEapIdentity(const uint8_t *bssid, const uint8_t *sta,
540 const uint8_t *eapol, uint16_t len, int8_t rssi);
541 void _parseWpsFrame(const uint8_t *bssid, const uint8_t *sta,
542 const uint8_t *eapol, uint16_t len, int8_t rssi);
543#ifndef POLITICIAN_NO_MSCHAPV2
544 void _parseEapMsChap(const uint8_t *bssid, const uint8_t *sta,
545 const uint8_t *eapol, uint16_t len, int8_t rssi);
546#endif
547 void _parseSsid(const uint8_t *ie, uint16_t ie_len, char *out, uint8_t &out_len);
548 uint8_t _classifyEnc(const uint8_t *ie, uint16_t ie_len);
549 uint8_t _classifyPairwiseCipher(const uint8_t *ie, uint16_t ie_len);
550 bool _detectWpa3Only(const uint8_t *ie, uint16_t ie_len);
551 void _detectPmfFlags(const uint8_t *ie, uint16_t ie_len, bool &pmf_capable, bool &pmf_required);
552 bool _detectFt(const uint8_t *ie, uint16_t ie_len);
553#ifndef POLITICIAN_NO_KARMA
554 void _sendKarmaResponse(const uint8_t *client, const char *ssid, uint8_t ssid_len, uint8_t channel, int8_t rssi);
555#endif
556
557 bool _initialized = false;
558 volatile bool _active;
559 uint8_t _channel;
560 uint8_t _rxChannel;
561 bool _hopping;
562 volatile bool _channelTrafficSeen;
563 uint32_t _lastHopMs;
564 uint32_t _lastDiagMs;
565 int8_t _lastRssi;
566 uint8_t _hopIndex;
567 uint8_t _attackMask;
568 DisconnectStrategy _disconnectStrategy;
569 uint32_t _csaFallbackMs;
570
571 static const int MAX_INJECT_QUEUE = 4;
572 struct InjectFrame {
573 bool active;
574 uint8_t payload[256];
575 uint16_t len;
576 uint8_t channel;
577 uint32_t lock_ms;
578 };
579 InjectFrame _injectQueue[MAX_INJECT_QUEUE];
580
581 static const int MAX_ATTACK_OVERRIDES = 8;
582 struct AttackOverride { bool active; uint8_t bssid[6]; uint8_t mask; };
583 AttackOverride _attackOverrides[MAX_ATTACK_OVERRIDES];
584 struct SsidOverride { bool active; char ssid[33]; uint8_t ssid_len; uint8_t mask; bool substring; };
585 static const int MAX_SSID_OVERRIDES = 8;
586 SsidOverride _ssidOverrides[MAX_SSID_OVERRIDES];
587 uint8_t _ssidOverrideIdx = 0; ///< Circular eviction pointer for the SSID override table.
588 struct EapMethodSeen { uint8_t bssid[6]; uint8_t method; };
589 static const uint8_t MAX_EAP_METHODS = 8;
590 EapMethodSeen _eapMethods[MAX_EAP_METHODS];
591 uint8_t _eapMethodIdx = 0;
592 uint8_t _getAttackMask(const uint8_t *bssid) const;
593
594 bool _hasTarget;
595 uint8_t _targetBssid[6];
596 uint8_t _targetChannel;
597
598 bool _m1Locked;
599 uint32_t _m1LockEndMs;
600
601 bool _probeLocked;
602 uint32_t _probeLockEndMs;
603
604 uint8_t _customChannels[POLITICIAN_MAX_CHANNELS];
605 uint8_t _customChannelCount;
606 Config _cfg;
607 Stats _stats;
608
609 bool _autoTarget = false;
610 bool _autoTargetActive = false;
611
612 uint8_t _lastCapBssid[6] = {};
613 uint8_t _lastCapSta[6] = {};
614 uint32_t _lastCapMs = 0;
615
616 TargetScoreCb _targetScoreCb = nullptr;
617 LogCb _logCb = nullptr;
618 ApFoundCb _apFoundCb = nullptr;
619 TargetFilterCb _filterCb = nullptr;
620 EapolCb _eapolCb = nullptr;
621 PacketCb _packetCb = nullptr;
622 IdentityCb _identityCb = nullptr;
623 AttackResultCb _attackResultCb = nullptr;
624 ProbeRequestCb _probeReqCb = nullptr;
625 DisruptCb _disruptCb = nullptr;
626 ClientFoundCb _clientFoundCb = nullptr;
627 RogueApCb _rogueApCb = nullptr;
628 _FpHookCb _fpHook = nullptr;
629 WpsCb _wpsCb = nullptr;
630#ifndef POLITICIAN_NO_KARMA
631 KarmaCb _karmaCb = nullptr;
632 bool _karmaEnabled = false;
633 // Circular SSID dedup table — avoid spamming one client with repeated responses
634 static const int MAX_KARMA_SEEN = 16;
635 struct KarmaSeen { uint8_t client[6]; char ssid[33]; uint32_t last_ms; };
636 KarmaSeen _karmaSeen[MAX_KARMA_SEEN];
637 uint8_t _karmaSeenIdx = 0;
638#endif
639#ifndef POLITICIAN_NO_MSCHAPV2
640 MsChapCb _msChapCb = nullptr;
641 // Challenge session table: correlates server challenge (from AP) with response (from client)
642 static const int MAX_MSCHAP_SESSIONS = 4;
643 struct MsChapSession {
644 bool active;
645 uint8_t bssid[6];
646 uint8_t ms_id; // MS-CHAPv2 identifier — ties challenge to response
647 uint8_t challenge[16]; // Server challenge from MSCHAPv2 Challenge frame
648 };
649 MsChapSession _msChapSessions[MAX_MSCHAP_SESSIONS];
650#endif
651
652 const char * const * _probeWordlist = nullptr;
653 uint8_t _probeWordlistLen = 0;
654
655 void _log(const char *fmt, ...);
656
657 static const int MAX_IGNORE = 128;
658 uint8_t _ignoreList[MAX_IGNORE][6];
659 uint8_t _ignoreCount;
660
661 static const int MAX_AP_CACHE = POLITICIAN_MAX_AP_CACHE;
662 struct ApCacheEntry {
663 uint8_t bssid[6];
664 char ssid[33];
665 uint8_t ssid_len;
666 uint8_t enc;
667 uint8_t channel;
668 int8_t rssi;
669 uint32_t first_seen_ms;
670 uint32_t last_seen_ms;
671 uint32_t last_probe_ms;
672 uint32_t last_stimulate_ms;
673 uint32_t last_hidden_probe_ms; // Timestamp of last directed probe for hidden SSID
674 uint8_t known_stas[4][6]; // Up to 4 persistently tracked client MACs
675 uint8_t known_sta_count;
676 uint16_t beacon_count; // Times this AP has been observed
677 uint8_t total_attempts; // Total failed attack attempts against this AP
678 char country[3]; // IE 7 country code (e.g. "US"), empty if absent
679 uint16_t beacon_interval; // Beacon interval in TUs from fixed fields
680 uint8_t max_rate_mbps; // Highest rate from Supported Rates IE (Mbps)
681 uint16_t sta_count; // Connected client count from BSS Load
682 uint8_t chan_util; // Channel utilization (0-255)
683 uint8_t venue_group; // 802.11u Venue Group
684 uint8_t venue_type; // 802.11u Venue Type
685 uint8_t network_type; // 802.11u Access Network Type
686 struct {
687 uint16_t active : 1;
688 uint16_t has_active_clients : 1;
689 uint16_t is_wpa3_only : 1;
690 uint16_t is_hidden : 1;
691 uint16_t wps_enabled : 1;
692 uint16_t pmf_capable : 1;
693 uint16_t pmf_required : 1;
694 uint16_t ft_capable : 1;
695 uint16_t is_vht : 1; // 802.11ac capable
696 uint16_t is_he : 1; // 802.11ax capable
697 } flags;
698 uint8_t chan_width; // Max channel width: 0=20 1=40 2=80 3=160 4=80+80 (MHz)
699 uint8_t probe_word_idx; // Next wordlist index to probe for this hidden AP
700 uint32_t last_attack_ms; // millis() when the most recent attack was initiated
701 uint8_t capture_count; // Number of successful captures for this BSSID
702 uint8_t pairwise_cipher; // CIPHER_TKIP/CIPHER_CCMP/CIPHER_UNKNOWN from RSN IE
703 };
704 ApCacheEntry _apCache[MAX_AP_CACHE];
705
706 ApCacheEntry* _cacheAp(const uint8_t *bssid, const char *ssid, uint8_t ssid_len,
707 uint8_t enc, uint8_t channel, int8_t rssi,
708 bool is_wpa3_only = false, bool wps = false,
709 bool pmf_capable = false, bool pmf_required = false,
710 bool ft_capable = false, uint16_t sta_count = 0, uint8_t chan_util = 0,
711 uint8_t venue_group = 0, uint8_t venue_type = 0, uint8_t network_type = 0);
712 bool _lookupSsid(const uint8_t *bssid, char *out_ssid, uint8_t &out_len) const;
713 bool _lookupEnc(const uint8_t *bssid, uint8_t &out_enc) const;
714 bool _lookupCipher(const uint8_t *bssid, uint8_t &out_cipher) const;
715
716 enum FishState : uint8_t { FISH_IDLE = 0, FISH_CONNECTING = 1, FISH_CSA_WAIT = 2 };
717 FishState _fishState;
718 uint32_t _fishStartMs;
719 uint8_t _fishBssid[6];
720 uint8_t _fishRetry;
721 char _fishSsid[33];
722 uint8_t _fishSsidLen;
723 uint8_t _fishChannel;
724 uint8_t _fishSta[6]; // Known client MAC for unicast deauth (zeros = unknown)
725 uint8_t _ownStaMac[6];
726 bool _fishAuthLogged;
727 bool _fishAssocLogged;
728 bool _csaSecondBurstSent;
729
730 void _startFishing(const uint8_t *bssid, const char *ssid,
731 uint8_t ssid_len, uint8_t channel);
732 void _processFishing();
733 void _randomizeMac();
734 void _sendCsaBurst();
735 void _sendDeauthBurst(uint8_t count, const uint8_t *sta = nullptr);
736 void _sendBtmRequest(const uint8_t *bssid, const uint8_t *sta);
737 void _sendProbeRequest(const uint8_t *bssid, const char *ssid = nullptr, uint8_t ssid_len = 0);
738 void _recordClientForAp(const uint8_t *bssid, const uint8_t *sta, int8_t rssi = 0);
739 void _markCapturedSsidGroup(const char *ssid, uint8_t ssid_len);
740 void _markCaptured(const uint8_t *bssid);
741 void _incCaptureCount(const uint8_t *bssid);
742
743 static const int MAX_SESSIONS = POLITICIAN_MAX_SESSIONS;
744 struct Session {
745 uint8_t bssid[6];
746 uint8_t sta[6];
747 char ssid[33];
748 uint8_t ssid_len;
749 uint8_t channel;
750 int8_t rssi;
751 uint8_t anonce[32];
752 uint8_t snonce[32];
753 uint8_t m1_replay_counter[8];
754 uint8_t mic[16];
755 uint32_t created_ms;
756
757 // Dynamic EAPOL Buffer (M2, M3, M4 packed)
758 uint8_t eapol_buffer[400];
759 uint16_t m2_off, m2_len;
760 uint16_t m3_off, m3_len;
761 uint16_t m4_off, m4_len;
762
763 struct {
764 uint8_t active : 1;
765 uint8_t has_m1 : 1;
766 uint8_t has_m2 : 1;
767 uint8_t has_m3 : 1;
768 uint8_t has_m4 : 1;
769 } flags;
770 };
771 Session _sessions[MAX_SESSIONS];
772
773 Session* _findSession(const uint8_t *bssid, const uint8_t *sta);
774 Session* _createSession(const uint8_t *bssid, const uint8_t *sta);
775 void _expireSessions(uint32_t timeoutMs);
776
777 static const int MAX_CAPTURED = POLITICIAN_MAX_CAPTURED;
778 uint8_t _captured[MAX_CAPTURED][6];
779 int _capturedCount;
780
781 bool _isCaptured(const uint8_t *bssid) const;
782
783 static const uint8_t HOP_SEQ[];
784 static const uint8_t HOP_COUNT;
785};
786
787} // namespace politician
#define POLITICIAN_MAX_CHANNELS
Definition Politician.h:26
#define POLITICIAN_MAX_AP_CACHE
Definition Politician.h:14
#define POLITICIAN_MAX_INSTANCES
Maximum number of concurrent Politician instances that can be active at once.
Definition Politician.h:38
#define POLITICIAN_MAX_SESSIONS
Definition Politician.h:18
#define POLITICIAN_MAX_CAPTURED
Definition Politician.h:22
The core WiFi handshake capturing engine.
Definition Politician.h:103
void stop()
Full engine teardown.
std::function< void(const ClientRecord &rec)> ClientFoundCb
Definition Politician.h:364
Error lockChannel(uint8_t ch)
Stops hopping and locks the radio to a specific channel.
void clearCapturedList()
Clears the captured BSSID list.
void markCaptured(const uint8_t *bssid)
Manually adds a BSSID to the "already captured" list to skip it.
static const char * getVendor(const uint8_t *mac)
Looks up the vendor name for a given MAC address (OUI).
void setAttackMaskForSsid(const char *ssid, uint8_t mask, bool substring=false)
Sets an attack mask for all APs whose SSID matches the given string.
void setDisruptCallback(DisruptCb cb)
Sets the callback fired on deauthentication and disassociation frames.
Definition Politician.h:450
std::function< void(const MsChapRecord &rec)> MsChapCb
Definition Politician.h:367
Error injectCustomFrame(const uint8_t *payload, size_t len, uint8_t channel, uint32_t lock_ms=0, bool wait_for_channel=false)
Injects a custom 802.11 frame.
bool getClient(const uint8_t *bssid, int idx, uint8_t out_sta[6]) const
Reads a client MAC from the per-AP client table.
void clearAttackMaskOverrides()
Clears all per-BSSID attack mask overrides.
void setAutoTarget(bool enable)
Continuously locks onto the strongest uncaptured AP in the cache.
void setMsChapCallback(MsChapCb cb)
Sets the callback fired on a bare EAP-MSCHAPv2 challenge/response exchange.
Definition Politician.h:473
int getClientCount(const uint8_t *bssid) const
Returns the number of unique clients seen associated to a given AP.
void resetStats()
Resets all frame and capture statistics to zero.
Definition Politician.h:261
void tick()
Main worker method.
int8_t getLastRssi() const
Definition Politician.h:270
void setProbeRequestCallback(ProbeRequestCb cb)
Sets the callback fired on every probe request frame.
Definition Politician.h:444
void setPacketLogger(PacketCb cb)
Sets the callback for raw promiscuous mode packets.
Definition Politician.h:427
std::function< void(const ProbeRequestRecord &rec)> ProbeRequestCb
Definition Politician.h:362
Error setTargetBySsid(const char *ssid)
Searches the AP cache by SSID and locks onto the strongest match.
void setLogger(LogCb cb)
Sets a custom logging callback to intercept library output.
Definition Politician.h:117
void(*)(const uint8_t *mac, const char *ssid, uint8_t ssid_len, uint8_t ch, int8_t rssi, const uint8_t *ie, uint16_t ie_len) _FpHookCb
Definition Politician.h:351
void setWpsCallback(WpsCb cb)
Sets the callback fired when a WPS Enrollee's M1 message is captured.
Definition Politician.h:464
void setActive(bool active)
Enables or disables frame processing.
void setChannelBands(bool ghz24, bool ghz5)
Restricts hopping to 2.4GHz, 5GHz, or both bands.
void setKarmaCallback(KarmaCb cb)
Sets the callback fired each time the KARMA responder echoes a probe.
Definition Politician.h:502
void setRogueApCallback(RogueApCb cb)
Sets the callback fired when a potential evil twin or rogue AP is detected.
Definition Politician.h:480
void forEachAp(void(*cb)(const ApRecord &ap, void *ctx), void *ctx=nullptr) const
Iterates all active APs in the cache, calling cb for each one.
void setApFoundCallback(ApFoundCb cb)
Sets the callback for when a new Access Point is discovered.
Definition Politician.h:417
bool isAttacking() const
Definition Politician.h:251
std::function< void(const HandshakeRecord &rec)> EapolCb
Definition Politician.h:355
uint8_t getChannel() const
Definition Politician.h:264
std::function< void(const ApRecord &ap)> ApFoundCb
Definition Politician.h:356
void setDisconnectionStrategy(DisconnectStrategy strategy)
Configures how the engine handles disconnection when both CSA and Deauth are enabled.
Definition Politician.h:234
void _setFingerprintHook(_FpHookCb cb)
Definition Politician.h:352
std::function< void(const DisruptRecord &rec)> DisruptCb
Definition Politician.h:363
void stopHopping()
Stops autonomous channel hopping and goes idle.
void setClientFoundCallback(ClientFoundCb cb)
Sets the callback fired when a new client (STA) is first seen associated to an AP.
Definition Politician.h:456
void setAttackMaskForBssid(const uint8_t *bssid, uint8_t mask)
Overrides the attack mask for a specific BSSID.
std::function< void(const EapIdentityRecord &rec)> IdentityCb
Definition Politician.h:360
void setTargetFilter(TargetFilterCb cb)
Sets an early filter callback.
Definition Politician.h:422
std::function< int(const ApRecord &ap, const char *vendor)> TargetScoreCb
Definition Politician.h:358
void setTargetScoreCallback(TargetScoreCb cb)
Sets the callback for calculating a custom priority score during autoTarget.
Definition Politician.h:396
void setIgnoreList(const uint8_t(*bssids)[6], uint8_t count)
Sets a list of BSSIDs that should always be ignored by the engine.
void setEapolCallback(EapolCb cb)
Sets the callback for when a handshake (EAPOL or PMKID) is captured.
Definition Politician.h:412
Error setTarget(const uint8_t *bssid, uint8_t channel)
Focuses the engine on a single BSSID.
bool hasTarget() const
Definition Politician.h:248
void setIdentityCallback(IdentityCb cb)
Sets the callback for passive 802.1X Enterprise Identity harvesting.
Definition Politician.h:432
Error setChannel(uint8_t ch)
Manually sets the WiFi radio to a specific channel.
bool getApByBssid(const uint8_t *bssid, ApRecord &out) const
Looks up an AP in the discovery cache by BSSID.
void clearTarget()
Clears the specific target and resumes autonomous wardriving.
std::function< void(const WpsRecord &rec)> WpsCb
Definition Politician.h:365
void enableKarma(bool en)
Enable or disable the KARMA rogue AP responder at runtime.
Definition Politician.h:495
uint8_t setAutoChannelList(uint8_t topN=13)
Feeds the top-N most active channels (from getChannelsSortedByActivity) into setChannelList(),...
uint8_t getChannelsSortedByActivity(uint8_t *out, uint8_t count) const
Returns the N most active channels sorted by descending frame count.
bool getAp(int idx, ApRecord &out) const
Reads an AP from the discovery cache by index.
Error begin(const Config &cfg=Config())
Initializes the WiFi driver in promiscuous mode.
bool isActive() const
Definition Politician.h:267
std::function< void(const AttackResultRecord &rec)> AttackResultCb
Definition Politician.h:361
void startHopping(uint16_t dwellMs=0)
Starts autonomous channel hopping.
void setAttackResultCallback(AttackResultCb cb)
Sets the callback fired when an attack attempt exhausts all options without capturing.
Definition Politician.h:438
void setAttackMask(uint8_t mask)
Configures which attack techniques are enabled globally.
void setChannelList(const uint8_t *channels, uint8_t count)
Restricts hopping to a specific list of channels.
void setProbeWordlist(const char *const *wordlist, uint8_t count)
Sets an SSID wordlist for directed probes against hidden access points.
Definition Politician.h:517
std::function< void(const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)> PacketCb
Definition Politician.h:359
std::function< bool(const ApRecord &ap)> TargetFilterCb
Definition Politician.h:357
ieee80211_hdr_t hdr
Definition Politician.h:53
std::function< void(const KarmaRecord &rec)> KarmaCb
std::function< void(const char *msg)> LogCb
std::function< void(const RogueApRecord &rec)> RogueApCb
Snapshot of a discovered Access Point from the internal cache.
Identifies the AP and failure reason for a failed attack, delivered to the AttackResultCb callback.
Snapshot of a client station observed associated with an AP.
Configuration for the Politician engine.
A deauthentication or disassociation frame observed on the air, delivered to the DisruptCb callback.
A harvested 802.1X Enterprise plaintext identity, delivered to the IdentityCb callback.
A captured handshake or PMKID record delivered to the EapolCb callback.
Bare EAP-MSCHAPv2 challenge/response pair harvested passively.
A probe request frame observed on the air, delivered to the ProbeRequestCb callback.
Cumulative frame and capture counters for the engine session.
WPS M1 device attributes harvested from an EAP-WSC exchange.