Move command registry from components/command/ into components/core/. New modules: mod_canbus, mod_honeypot, mod_fallback, mod_redteam, mod_ota. Replace mod_proxy with tun_core (multiplexed SOCKS5 tunnel). Kconfig extended with per-module settings and async worker config.
273 lines
8.2 KiB
C
273 lines
8.2 KiB
C
/*
|
|
* rt_stealth.c
|
|
* OPSEC: MAC randomization, TX power control, passive scan.
|
|
*/
|
|
#include "sdkconfig.h"
|
|
#include "rt_stealth.h"
|
|
|
|
#ifdef CONFIG_MODULE_REDTEAM
|
|
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_random.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
static const char *TAG = "RT_STEALTH";
|
|
|
|
/* ============================================================
|
|
* MAC randomization
|
|
* ============================================================ */
|
|
|
|
static uint8_t s_orig_mac[6] = {0};
|
|
static bool s_mac_saved = false;
|
|
|
|
void rt_stealth_save_original_mac(void)
|
|
{
|
|
if (esp_wifi_get_mac(WIFI_IF_STA, s_orig_mac) == ESP_OK) {
|
|
s_mac_saved = true;
|
|
ESP_LOGI(TAG, "Original MAC: %02X:%02X:%02X:%02X:%02X:%02X",
|
|
s_orig_mac[0], s_orig_mac[1], s_orig_mac[2],
|
|
s_orig_mac[3], s_orig_mac[4], s_orig_mac[5]);
|
|
}
|
|
}
|
|
|
|
void rt_stealth_randomize_mac(void)
|
|
{
|
|
uint8_t mac[6];
|
|
esp_fill_random(mac, 6);
|
|
mac[0] &= 0xFE; /* unicast */
|
|
mac[0] |= 0x02; /* locally administered */
|
|
|
|
/* Must disconnect before changing MAC */
|
|
esp_wifi_disconnect();
|
|
vTaskDelay(pdMS_TO_TICKS(50));
|
|
|
|
esp_err_t err = esp_wifi_set_mac(WIFI_IF_STA, mac);
|
|
if (err == ESP_OK) {
|
|
ESP_LOGI(TAG, "MAC randomized: %02X:%02X:%02X:%02X:%02X:%02X",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
} else {
|
|
ESP_LOGW(TAG, "MAC set failed: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
void rt_stealth_restore_mac(void)
|
|
{
|
|
if (s_mac_saved) {
|
|
esp_wifi_disconnect();
|
|
vTaskDelay(pdMS_TO_TICKS(50));
|
|
esp_wifi_set_mac(WIFI_IF_STA, s_orig_mac);
|
|
ESP_LOGI(TAG, "MAC restored: %02X:%02X:%02X:%02X:%02X:%02X",
|
|
s_orig_mac[0], s_orig_mac[1], s_orig_mac[2],
|
|
s_orig_mac[3], s_orig_mac[4], s_orig_mac[5]);
|
|
}
|
|
}
|
|
|
|
void rt_stealth_get_current_mac(uint8_t mac[6])
|
|
{
|
|
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
|
}
|
|
|
|
/* ============================================================
|
|
* TX power control
|
|
* ============================================================ */
|
|
|
|
void rt_stealth_low_tx_power(void)
|
|
{
|
|
/* 8 dBm (arg * 0.25 dBm, so 32 = 8 dBm) */
|
|
esp_err_t err = esp_wifi_set_max_tx_power(32);
|
|
if (err == ESP_OK) {
|
|
ESP_LOGI(TAG, "TX power reduced to 8 dBm");
|
|
} else {
|
|
ESP_LOGW(TAG, "TX power set failed: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
void rt_stealth_restore_tx_power(void)
|
|
{
|
|
esp_wifi_set_max_tx_power(80); /* 20 dBm */
|
|
ESP_LOGI(TAG, "TX power restored to 20 dBm");
|
|
}
|
|
|
|
/* ============================================================
|
|
* Passive scan — promiscuous mode beacon capture
|
|
* ============================================================ */
|
|
|
|
/* WiFi management frame header */
|
|
typedef struct {
|
|
unsigned frame_ctrl:16;
|
|
unsigned duration_id:16;
|
|
uint8_t addr1[6]; /* Destination */
|
|
uint8_t addr2[6]; /* Source */
|
|
uint8_t addr3[6]; /* BSSID */
|
|
unsigned seq_ctrl:16;
|
|
} __attribute__((packed)) wifi_mgmt_hdr_t;
|
|
|
|
/* Beacon frame body (partial — just what we need) */
|
|
/* Fixed fields: timestamp(8) + beacon_interval(2) + capability(2) = 12 bytes */
|
|
#define BEACON_FIXED_LEN 12
|
|
/* Tag: SSID = tag_number 0, followed by length, then SSID string */
|
|
|
|
static rt_scan_ap_t s_scan_results[RT_MAX_SCAN_APS];
|
|
static volatile int s_scan_count = 0;
|
|
|
|
/* Check if we already have this BSSID */
|
|
static int find_bssid(const uint8_t bssid[6])
|
|
{
|
|
for (int i = 0; i < s_scan_count; i++) {
|
|
if (memcmp(s_scan_results[i].bssid, bssid, 6) == 0)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void IRAM_ATTR passive_scan_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;
|
|
wifi_mgmt_hdr_t *hdr = (wifi_mgmt_hdr_t *)pkt->payload;
|
|
|
|
/* Check frame type: beacon = 0x80, probe response = 0x50 */
|
|
uint16_t fc = hdr->frame_ctrl;
|
|
uint8_t subtype = (fc >> 4) & 0x0F;
|
|
if (subtype != 8 && subtype != 5) return; /* 8=beacon, 5=probe_resp */
|
|
|
|
/* BSSID is addr3 for beacons */
|
|
const uint8_t *bssid = hdr->addr3;
|
|
|
|
/* Skip if already seen */
|
|
if (find_bssid(bssid) >= 0) {
|
|
/* Update RSSI if stronger */
|
|
int idx = find_bssid(bssid);
|
|
if (pkt->rx_ctrl.rssi > s_scan_results[idx].rssi) {
|
|
s_scan_results[idx].rssi = pkt->rx_ctrl.rssi;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (s_scan_count >= RT_MAX_SCAN_APS) return;
|
|
|
|
/* Parse beacon body for SSID */
|
|
size_t hdr_len = sizeof(wifi_mgmt_hdr_t);
|
|
size_t body_offset = hdr_len + BEACON_FIXED_LEN;
|
|
|
|
if ((int)pkt->rx_ctrl.sig_len < (int)(body_offset + 2))
|
|
return;
|
|
|
|
/* Parse tagged parameters for SSID (tag 0) and RSN/WPA (security) */
|
|
const uint8_t *body = pkt->payload + body_offset;
|
|
size_t body_len = pkt->rx_ctrl.sig_len - body_offset;
|
|
/* Remove FCS (4 bytes) if present */
|
|
if (body_len > 4) body_len -= 4;
|
|
|
|
rt_scan_ap_t *ap = &s_scan_results[s_scan_count];
|
|
memset(ap, 0, sizeof(*ap));
|
|
memcpy(ap->bssid, bssid, 6);
|
|
ap->rssi = pkt->rx_ctrl.rssi;
|
|
ap->channel = pkt->rx_ctrl.channel;
|
|
ap->auth_mode = 0; /* Assume open until we find RSN/WPA tag */
|
|
|
|
/* Parse IEs (Information Elements) */
|
|
size_t pos = 0;
|
|
while (pos + 2 <= body_len) {
|
|
uint8_t tag_id = body[pos];
|
|
uint8_t tag_len = body[pos + 1];
|
|
|
|
if (pos + 2 + tag_len > body_len) break;
|
|
|
|
if (tag_id == 0) { /* SSID */
|
|
size_t ssid_len = tag_len;
|
|
if (ssid_len > 32) ssid_len = 32;
|
|
memcpy(ap->ssid, body + pos + 2, ssid_len);
|
|
ap->ssid[ssid_len] = '\0';
|
|
} else if (tag_id == 48) { /* RSN (WPA2) */
|
|
ap->auth_mode = 3; /* WPA2 */
|
|
} else if (tag_id == 221) { /* Vendor specific — check for WPA OUI */
|
|
if (tag_len >= 4 &&
|
|
body[pos + 2] == 0x00 && body[pos + 3] == 0x50 &&
|
|
body[pos + 4] == 0xF2 && body[pos + 5] == 0x01) {
|
|
if (ap->auth_mode == 0) ap->auth_mode = 2; /* WPA */
|
|
}
|
|
}
|
|
|
|
pos += 2 + tag_len;
|
|
}
|
|
|
|
s_scan_count++;
|
|
}
|
|
|
|
int rt_stealth_passive_scan(int duration_ms)
|
|
{
|
|
s_scan_count = 0;
|
|
memset(s_scan_results, 0, sizeof(s_scan_results));
|
|
|
|
/* Enable promiscuous mode */
|
|
esp_wifi_disconnect();
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
|
|
esp_err_t ret = esp_wifi_set_promiscuous_rx_cb(passive_scan_cb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Promiscuous CB 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);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Promiscuous enable failed: %s", esp_err_to_name(ret));
|
|
return 0;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Passive scan started (%d ms)", duration_ms);
|
|
|
|
/* Channel hop: ~200ms per channel, 13 channels per cycle */
|
|
int channels = 13;
|
|
int hop_ms = 200;
|
|
int elapsed = 0;
|
|
|
|
while (elapsed < duration_ms) {
|
|
for (int ch = 1; ch <= channels && elapsed < duration_ms; ch++) {
|
|
esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
|
|
vTaskDelay(pdMS_TO_TICKS(hop_ms));
|
|
elapsed += hop_ms;
|
|
}
|
|
}
|
|
|
|
/* Disable promiscuous mode */
|
|
esp_wifi_set_promiscuous(false);
|
|
|
|
ESP_LOGI(TAG, "Passive scan done: %d unique APs", s_scan_count);
|
|
return s_scan_count;
|
|
}
|
|
|
|
int rt_stealth_get_scan_results(rt_scan_ap_t *out, int max_count)
|
|
{
|
|
int count = s_scan_count;
|
|
if (count > max_count) count = max_count;
|
|
memcpy(out, s_scan_results, count * sizeof(rt_scan_ap_t));
|
|
return count;
|
|
}
|
|
|
|
#else /* !CONFIG_MODULE_REDTEAM — empty stubs */
|
|
|
|
#include <string.h>
|
|
|
|
void rt_stealth_save_original_mac(void) {}
|
|
void rt_stealth_randomize_mac(void) {}
|
|
void rt_stealth_restore_mac(void) {}
|
|
void rt_stealth_get_current_mac(uint8_t mac[6]) { memset(mac, 0, 6); }
|
|
void rt_stealth_low_tx_power(void) {}
|
|
void rt_stealth_restore_tx_power(void) {}
|
|
int rt_stealth_passive_scan(int duration_ms) { (void)duration_ms; return 0; }
|
|
int rt_stealth_get_scan_results(rt_scan_ap_t *out, int max_count) { (void)out; (void)max_count; return 0; }
|
|
|
|
#endif /* CONFIG_MODULE_REDTEAM */
|