Files
c5-project/ESP32-C5-Toolkit/main/frame_analyzer.c
2026-05-03 23:19:51 -07:00

337 lines
11 KiB
C

/**
* @file frame_analyzer.c
* @brief Frame analyzer implementation for EAPOL/Handshake/PMKID capture
*
* Parses 802.11 data frames to extract WPA handshake components
*/
#include "frame_analyzer.h"
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
static const char *TAG = "frame_analyzer";
// Global state
static uint8_t target_bssid[6] = {0};
static uint8_t target_ssid[33] = {0};
static uint8_t target_ssid_len = 0;
static search_type_t current_search_type = SEARCH_HANDSHAKE;
static handshake_data_t captured_handshake = {0};
static pmkid_item_t *captured_pmkids = NULL;
static SemaphoreHandle_t analyzer_mutex = NULL;
static bool capture_active = false;
// EAPOL/LLC constants
#define LLC_SNAP_HEADER_SIZE 8
#define EAPOL_TYPE_KEY 0x03
void frame_analyzer_init(void) {
if (analyzer_mutex == NULL) {
analyzer_mutex = xSemaphoreCreateMutex();
}
frame_analyzer_reset();
ESP_LOGI(TAG, "Frame analyzer initialized");
}
// Internal reset function without mutex (called when mutex is already held)
static void frame_analyzer_reset_internal(void) {
memset(&captured_handshake, 0, sizeof(captured_handshake));
// Free PMKIDs
while (captured_pmkids) {
pmkid_item_t *next = captured_pmkids->next;
free(captured_pmkids);
captured_pmkids = next;
}
captured_pmkids = NULL;
}
void frame_analyzer_reset(void) {
if (analyzer_mutex && xSemaphoreTake(analyzer_mutex, portMAX_DELAY)) {
frame_analyzer_reset_internal();
xSemaphoreGive(analyzer_mutex);
}
}
void frame_analyzer_capture_start(search_type_t search_type, const uint8_t *bssid, const uint8_t *ssid, uint8_t ssid_len) {
if (analyzer_mutex && xSemaphoreTake(analyzer_mutex, portMAX_DELAY)) {
frame_analyzer_reset_internal(); // Use internal version (mutex already held)
current_search_type = search_type;
memcpy(target_bssid, bssid, 6);
if (ssid && ssid_len > 0) {
memcpy(target_ssid, ssid, ssid_len);
target_ssid_len = ssid_len;
memcpy(captured_handshake.ssid, ssid, ssid_len);
captured_handshake.ssid_len = ssid_len;
}
memcpy(captured_handshake.ap_mac, bssid, 6);
capture_active = true;
ESP_LOGI(TAG, "Capture started for BSSID: %02x:%02x:%02x:%02x:%02x:%02x, SSID: %s",
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5],
ssid_len > 0 ? (char*)target_ssid : "(unknown)");
xSemaphoreGive(analyzer_mutex);
}
}
void frame_analyzer_capture_stop(void) {
if (analyzer_mutex && xSemaphoreTake(analyzer_mutex, portMAX_DELAY)) {
capture_active = false;
ESP_LOGI(TAG, "Capture stopped");
xSemaphoreGive(analyzer_mutex);
}
}
bool is_frame_bssid_matching(const wifi_promiscuous_pkt_t *frame, const uint8_t *target_bssid) {
const data_frame_t *data_frame = (const data_frame_t *)frame->payload;
// Check addr1, addr2, addr3 for BSSID match
if (memcmp(data_frame->mac_header.addr1, target_bssid, 6) == 0 ||
memcmp(data_frame->mac_header.addr2, target_bssid, 6) == 0 ||
memcmp(data_frame->mac_header.addr3, target_bssid, 6) == 0) {
return true;
}
return false;
}
eapol_packet_t* parse_eapol_packet(data_frame_t *frame) {
// Skip MAC header to get LLC/SNAP header
uint8_t *body = frame->body;
// Check LLC/SNAP header
llc_snap_header_t *llc = (llc_snap_header_t *)body;
if (llc->snap_dsap != 0xAA || llc->snap_ssap != 0xAA || llc->control != 0x03) {
return NULL;
}
// Check EtherType for EAPOL (0x888e)
uint16_t *ether_type = (uint16_t *)(body + 6);
if (ntohs(*ether_type) != ETHER_TYPE_EAPOL) {
return NULL;
}
// Return pointer to EAPOL packet (after LLC/SNAP header)
return (eapol_packet_t *)(body + LLC_SNAP_HEADER_SIZE);
}
eapol_key_packet_t* parse_eapol_key_packet(eapol_packet_t *eapol_packet) {
if (eapol_packet->header.packet_type != EAPOL_KEY) {
return NULL;
}
return (eapol_key_packet_t *)eapol_packet->packet_body;
}
pmkid_item_t* parse_pmkid(eapol_key_packet_t *eapol_key_packet) {
uint16_t key_data_len = ntohs(eapol_key_packet->key_data_length);
if (key_data_len == 0) {
return NULL;
}
pmkid_item_t *head = NULL;
pmkid_item_t *tail = NULL;
uint8_t *key_data = eapol_key_packet->key_data;
unsigned offset = 0;
while (offset < key_data_len) {
key_data_field_t *field = (key_data_field_t *)(key_data + offset);
if (field->type == KEY_DATA_TYPE) {
uint32_t oui = ntohl(field->oui << 8);
if (oui == KEY_DATA_OUI_IEEE80211 && field->data_type == KEY_DATA_DATA_TYPE_PMKID_KDE) {
// Found PMKID
pmkid_item_t *item = malloc(sizeof(pmkid_item_t));
if (item) {
memcpy(item->pmkid, field->data, 16);
item->next = NULL;
if (!head) {
head = item;
tail = item;
} else {
tail->next = item;
tail = item;
}
ESP_LOGI(TAG, "PMKID captured!");
}
}
}
offset += 2 + field->length; // type + length + data
if (field->length == 0) break; // Prevent infinite loop
}
return head;
}
// Helper: Check if array is all zeros
static bool is_zero_array(const uint8_t *arr, size_t len) {
for (size_t i = 0; i < len; i++) {
if (arr[i] != 0) return false;
}
return true;
}
// Process handshake message from AP (M1 or M3)
static void process_ap_message(data_frame_t *frame, eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
// Copy AP MAC if not set
if (is_zero_array(captured_handshake.ap_mac, 6)) {
memcpy(captured_handshake.ap_mac, frame->mac_header.addr2, 6);
}
// Determine M1 or M3 by checking Key MIC
// M1: Key MIC is empty, M3: Key MIC is present
if (is_zero_array(eapol_key->key_mic, 16)) {
// This is M1 - contains ANonce
ESP_LOGI(TAG, "Captured M1 (AP -> STA, ANonce)");
memcpy(captured_handshake.anonce, eapol_key->key_nonce, 32);
if (captured_handshake.state < HANDSHAKE_STATE_M1_CAPTURED) {
captured_handshake.state = HANDSHAKE_STATE_M1_CAPTURED;
}
} else {
// This is M3 - also contains ANonce
ESP_LOGI(TAG, "Captured M3 (AP -> STA)");
if (captured_handshake.state < HANDSHAKE_STATE_M3_CAPTURED) {
if (captured_handshake.state < HANDSHAKE_STATE_M1_CAPTURED) {
// Didn't see M1, copy ANonce from M3
memcpy(captured_handshake.anonce, eapol_key->key_nonce, 32);
}
captured_handshake.state = HANDSHAKE_STATE_M3_CAPTURED;
}
// If we already have M2, we have a complete handshake
if (captured_handshake.state >= HANDSHAKE_STATE_M2_CAPTURED) {
captured_handshake.complete = true;
captured_handshake.message_pair = 2; // M1+M2 or M3+M2
ESP_LOGI(TAG, "*** HANDSHAKE COMPLETE! ***");
}
}
}
// Process handshake message from STA (M2 or M4)
static void process_sta_message(data_frame_t *frame, eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
// Copy STA MAC if not set
if (is_zero_array(captured_handshake.sta_mac, 6)) {
memcpy(captured_handshake.sta_mac, frame->mac_header.addr2, 6);
}
// Determine M2 or M4 by checking SNonce
// M2: SNonce is present, M4: SNonce is empty
if (!is_zero_array(eapol_key->key_nonce, 32)) {
// This is M2 - contains SNonce and MIC
ESP_LOGI(TAG, "Captured M2 (STA -> AP, SNonce + MIC)");
memcpy(captured_handshake.snonce, eapol_key->key_nonce, 32);
memcpy(captured_handshake.mic, eapol_key->key_mic, 16);
// Save EAPOL packet for cracking
uint16_t eapol_len = sizeof(eapol_packet_header_t) + ntohs(eapol->header.packet_body_length);
if (eapol_len <= sizeof(captured_handshake.eapol)) {
memcpy(captured_handshake.eapol, eapol, eapol_len);
captured_handshake.eapol_len = eapol_len;
// Zero out MIC in saved EAPOL for verification during cracking
memset(captured_handshake.eapol + 81, 0, 16);
}
if (captured_handshake.state < HANDSHAKE_STATE_M2_CAPTURED) {
captured_handshake.state = HANDSHAKE_STATE_M2_CAPTURED;
}
// If we have M1, we have a complete handshake (M1+M2)
if (captured_handshake.state >= HANDSHAKE_STATE_M1_CAPTURED) {
captured_handshake.complete = true;
captured_handshake.message_pair = 0; // M1+M2
ESP_LOGI(TAG, "*** HANDSHAKE COMPLETE! ***");
}
} else {
// This is M4
ESP_LOGI(TAG, "Captured M4 (STA -> AP)");
if (captured_handshake.state < HANDSHAKE_STATE_M4_CAPTURED) {
captured_handshake.state = HANDSHAKE_STATE_M4_CAPTURED;
}
}
}
void frame_analyzer_process_frame(wifi_promiscuous_pkt_t *pkt, wifi_promiscuous_pkt_type_t type) {
if (!capture_active || type != WIFI_PKT_DATA) {
return;
}
if (!is_frame_bssid_matching(pkt, target_bssid)) {
return;
}
data_frame_t *frame = (data_frame_t *)pkt->payload;
// Parse EAPOL packet
eapol_packet_t *eapol = parse_eapol_packet(frame);
if (!eapol) {
return;
}
// Parse EAPOL-Key packet
eapol_key_packet_t *eapol_key = parse_eapol_key_packet(eapol);
if (!eapol_key) {
return;
}
ESP_LOGD(TAG, "Got EAPOL-Key frame");
if (analyzer_mutex && xSemaphoreTake(analyzer_mutex, pdMS_TO_TICKS(10))) {
if (current_search_type == SEARCH_PMKID) {
// Looking for PMKID
pmkid_item_t *pmkids = parse_pmkid(eapol_key);
if (pmkids) {
// Add to list
if (!captured_pmkids) {
captured_pmkids = pmkids;
} else {
pmkid_item_t *tail = captured_pmkids;
while (tail->next) tail = tail->next;
tail->next = pmkids;
}
}
} else if (current_search_type == SEARCH_HANDSHAKE) {
// Looking for handshake
// Determine frame direction by comparing addr2 (source) with addr3 (BSSID)
if (memcmp(frame->mac_header.addr2, frame->mac_header.addr3, 6) == 0) {
// Source == BSSID, this is from AP
process_ap_message(frame, eapol, eapol_key);
} else if (memcmp(frame->mac_header.addr1, frame->mac_header.addr3, 6) == 0) {
// Dest == BSSID, this is from STA
process_sta_message(frame, eapol, eapol_key);
}
}
xSemaphoreGive(analyzer_mutex);
}
}
handshake_state_t frame_analyzer_get_handshake_state(void) {
return captured_handshake.state;
}
const handshake_data_t* frame_analyzer_get_handshake(void) {
return &captured_handshake;
}
bool frame_analyzer_handshake_complete(void) {
return captured_handshake.complete;
}
pmkid_item_t* frame_analyzer_get_pmkids(void) {
return captured_pmkids;
}