266 lines
7.5 KiB
C
266 lines
7.5 KiB
C
/**
|
|
* @file hccapx_serializer.c
|
|
* @brief HCCAPX serializer for Hashcat-compatible output
|
|
*
|
|
* Generates HCCAPX format files for password cracking with Hashcat
|
|
*/
|
|
#include "hccapx_serializer.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <arpa/inet.h>
|
|
#include "esp_log.h"
|
|
#include "frame_analyzer.h"
|
|
|
|
static const char *TAG = "hccapx_serializer";
|
|
|
|
// HCCAPX constants
|
|
#define HCCAPX_SIGNATURE 0x58504348 // "HCPX"
|
|
#define HCCAPX_VERSION 4
|
|
#define HCCAPX_MAX_EAPOL_SIZE 256
|
|
|
|
// Static HCCAPX buffer
|
|
static hccapx_t hccapx = {
|
|
.signature = HCCAPX_SIGNATURE,
|
|
.version = HCCAPX_VERSION,
|
|
.message_pair = 255, // Invalid until we have a complete handshake
|
|
.keyver = HCCAPX_KEYVER_WPA2
|
|
};
|
|
|
|
// State tracking
|
|
static unsigned message_ap = 0;
|
|
static unsigned message_sta = 0;
|
|
static unsigned eapol_source = 0;
|
|
|
|
// Helper: Check if array is all zeros
|
|
static bool is_array_zero(const uint8_t *array, unsigned size) {
|
|
for (unsigned i = 0; i < size; i++) {
|
|
if (array[i] != 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void hccapx_serializer_init(const uint8_t *ssid, unsigned ssid_len) {
|
|
hccapx_serializer_reset();
|
|
|
|
if (ssid && ssid_len > 0 && ssid_len <= 32) {
|
|
hccapx.essid_len = ssid_len;
|
|
memcpy(hccapx.essid, ssid, ssid_len);
|
|
}
|
|
|
|
ESP_LOGI(TAG, "HCCAPX serializer initialized for SSID: %.*s", ssid_len, ssid);
|
|
}
|
|
|
|
void hccapx_serializer_reset(void) {
|
|
memset(&hccapx, 0, sizeof(hccapx_t));
|
|
hccapx.signature = HCCAPX_SIGNATURE;
|
|
hccapx.version = HCCAPX_VERSION;
|
|
hccapx.message_pair = 255;
|
|
hccapx.keyver = HCCAPX_KEYVER_WPA2;
|
|
|
|
message_ap = 0;
|
|
message_sta = 0;
|
|
eapol_source = 0;
|
|
|
|
ESP_LOGI(TAG, "HCCAPX serializer reset");
|
|
}
|
|
|
|
// Save EAPOL packet to HCCAPX
|
|
static unsigned save_eapol(eapol_packet_t *eapol_packet, eapol_key_packet_t *eapol_key) {
|
|
unsigned eapol_len = sizeof(eapol_packet_header_t) + ntohs(eapol_packet->header.packet_body_length);
|
|
|
|
if (eapol_len > HCCAPX_MAX_EAPOL_SIZE) {
|
|
ESP_LOGW(TAG, "EAPOL too long (%u > %u)", eapol_len, HCCAPX_MAX_EAPOL_SIZE);
|
|
return 1;
|
|
}
|
|
|
|
hccapx.eapol_len = eapol_len;
|
|
memcpy(hccapx.eapol, eapol_packet, eapol_len);
|
|
memcpy(hccapx.keymic, eapol_key->key_mic, 16);
|
|
|
|
// Clear MIC in saved EAPOL so Hashcat can calculate it
|
|
// MIC is at offset 81 in EAPOL-Key packet (after 4-byte EAPOL header)
|
|
memset(&hccapx.eapol[81], 0, 16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Handle M1 from AP
|
|
static void ap_message_m1(eapol_key_packet_t *eapol_key) {
|
|
ESP_LOGI(TAG, "Processing M1 (AP)");
|
|
message_ap = 1;
|
|
memcpy(hccapx.nonce_ap, eapol_key->key_nonce, 32);
|
|
}
|
|
|
|
// Handle M3 from AP
|
|
static void ap_message_m3(eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
|
|
ESP_LOGI(TAG, "Processing M3 (AP)");
|
|
message_ap = 3;
|
|
|
|
if (message_ap == 0) {
|
|
// No M1 seen, copy ANonce from M3
|
|
memcpy(hccapx.nonce_ap, eapol_key->key_nonce, 32);
|
|
}
|
|
|
|
if (eapol_source == 2) {
|
|
// Already have EAPOL from M2
|
|
hccapx.message_pair = 2;
|
|
return;
|
|
}
|
|
|
|
if (save_eapol(eapol, eapol_key) != 0) return;
|
|
|
|
eapol_source = 3;
|
|
if (message_sta == 2) {
|
|
hccapx.message_pair = 3;
|
|
}
|
|
}
|
|
|
|
// Handle AP messages (M1 or M3)
|
|
static void process_ap_message(data_frame_t *frame, eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
|
|
// Verify STA MAC consistency
|
|
if (!is_array_zero(hccapx.mac_sta, 6) &&
|
|
memcmp(frame->mac_header.addr1, hccapx.mac_sta, 6) != 0) {
|
|
ESP_LOGW(TAG, "Different STA, ignoring");
|
|
return;
|
|
}
|
|
|
|
if (message_ap == 0) {
|
|
memcpy(hccapx.mac_ap, frame->mac_header.addr2, 6);
|
|
}
|
|
|
|
// M1 has empty Key MIC, M3 has filled Key MIC
|
|
if (is_array_zero(eapol_key->key_mic, 16)) {
|
|
ap_message_m1(eapol_key);
|
|
} else {
|
|
ap_message_m3(eapol, eapol_key);
|
|
}
|
|
}
|
|
|
|
// Handle M2 from STA
|
|
static void sta_message_m2(eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
|
|
ESP_LOGI(TAG, "Processing M2 (STA)");
|
|
message_sta = 2;
|
|
memcpy(hccapx.nonce_sta, eapol_key->key_nonce, 32);
|
|
|
|
if (save_eapol(eapol, eapol_key) != 0) return;
|
|
|
|
eapol_source = 2;
|
|
if (message_ap == 1) {
|
|
hccapx.message_pair = 0; // M1+M2
|
|
}
|
|
}
|
|
|
|
// Handle M4 from STA
|
|
static void sta_message_m4(eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
|
|
ESP_LOGI(TAG, "Processing M4 (STA)");
|
|
|
|
if (message_sta == 2 && eapol_source != 0) {
|
|
ESP_LOGD(TAG, "Already have M2, M4 not needed");
|
|
return;
|
|
}
|
|
|
|
if (message_ap == 0) {
|
|
ESP_LOGW(TAG, "No AP message received yet");
|
|
return;
|
|
}
|
|
|
|
if (eapol_source == 3) {
|
|
hccapx.message_pair = 4;
|
|
return;
|
|
}
|
|
|
|
if (save_eapol(eapol, eapol_key) != 0) return;
|
|
|
|
eapol_source = 4;
|
|
if (message_ap == 1) {
|
|
hccapx.message_pair = 1; // M1+M4
|
|
}
|
|
if (message_ap == 3) {
|
|
hccapx.message_pair = 5; // M3+M4
|
|
}
|
|
}
|
|
|
|
// Handle STA messages (M2 or M4)
|
|
static void process_sta_message(data_frame_t *frame, eapol_packet_t *eapol, eapol_key_packet_t *eapol_key) {
|
|
if (is_array_zero(hccapx.mac_sta, 6)) {
|
|
memcpy(hccapx.mac_sta, frame->mac_header.addr2, 6);
|
|
} else if (memcmp(frame->mac_header.addr2, hccapx.mac_sta, 6) != 0) {
|
|
ESP_LOGW(TAG, "Different STA, ignoring");
|
|
return;
|
|
}
|
|
|
|
// M2 has SNonce, M4 has empty SNonce
|
|
if (!is_array_zero(eapol_key->key_nonce, 32)) {
|
|
sta_message_m2(eapol, eapol_key);
|
|
} else {
|
|
sta_message_m4(eapol, eapol_key);
|
|
}
|
|
}
|
|
|
|
void hccapx_serializer_add_frame(data_frame_t *frame) {
|
|
// Parse EAPOL
|
|
eapol_packet_t *eapol = parse_eapol_packet(frame);
|
|
if (!eapol) return;
|
|
|
|
eapol_key_packet_t *eapol_key = parse_eapol_key_packet(eapol);
|
|
if (!eapol_key) return;
|
|
|
|
// Determine direction: compare addr2 (source) with addr3 (BSSID)
|
|
if (memcmp(frame->mac_header.addr2, frame->mac_header.addr3, 6) == 0) {
|
|
// Source == BSSID => From AP
|
|
process_ap_message(frame, eapol, eapol_key);
|
|
} else if (memcmp(frame->mac_header.addr1, frame->mac_header.addr3, 6) == 0) {
|
|
// Dest == BSSID => From STA
|
|
process_sta_message(frame, eapol, eapol_key);
|
|
} else {
|
|
ESP_LOGW(TAG, "Unknown frame format");
|
|
}
|
|
}
|
|
|
|
void hccapx_serializer_build(const handshake_data_t *handshake) {
|
|
if (!handshake || !handshake->complete) {
|
|
ESP_LOGW(TAG, "Incomplete handshake, cannot build HCCAPX");
|
|
return;
|
|
}
|
|
|
|
hccapx_serializer_reset();
|
|
|
|
// Copy data from handshake
|
|
hccapx.essid_len = handshake->ssid_len;
|
|
memcpy(hccapx.essid, handshake->ssid, handshake->ssid_len);
|
|
|
|
memcpy(hccapx.mac_ap, handshake->ap_mac, 6);
|
|
memcpy(hccapx.mac_sta, handshake->sta_mac, 6);
|
|
|
|
memcpy(hccapx.nonce_ap, handshake->anonce, 32);
|
|
memcpy(hccapx.nonce_sta, handshake->snonce, 32);
|
|
|
|
memcpy(hccapx.keymic, handshake->mic, 16);
|
|
|
|
if (handshake->eapol_len > 0 && handshake->eapol_len <= HCCAPX_MAX_EAPOL_SIZE) {
|
|
hccapx.eapol_len = handshake->eapol_len;
|
|
memcpy(hccapx.eapol, handshake->eapol, handshake->eapol_len);
|
|
}
|
|
|
|
hccapx.message_pair = handshake->message_pair;
|
|
hccapx.keyver = HCCAPX_KEYVER_WPA2;
|
|
|
|
ESP_LOGI(TAG, "HCCAPX built from handshake data");
|
|
}
|
|
|
|
hccapx_t* hccapx_serializer_get(void) {
|
|
if (hccapx.message_pair == 255) {
|
|
return NULL; // No valid handshake
|
|
}
|
|
return &hccapx;
|
|
}
|
|
|
|
unsigned hccapx_serializer_get_size(void) {
|
|
return sizeof(hccapx_t);
|
|
}
|
|
|
|
bool hccapx_serializer_is_valid(void) {
|
|
return hccapx.message_pair != 255;
|
|
}
|