commit a7f26f88e4a67bfdb1b1ae95dd775148ad86cfb5 Author: rpriven Date: Mon Feb 23 00:03:07 2026 -0700 Loki Phase 1: BLE identity cycling countermeasure 16 fake device profiles (Galaxy S24, iPhone, Pixel 9, AirPods Pro, etc.) cycling every 2s with randomized MACs and realistic manufacturer data. Targets ESP32-C3 Super Mini with NimBLE 1.4.3 on ESP-IDF 4.4.x. Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9f3806 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..d38f438 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,11 @@ +[env:seeed_xiao_esp32c3] +platform = espressif32@6.9.0 +framework = arduino +board = seeed_xiao_esp32c3 +monitor_speed = 115200 +upload_speed = 115200 +build_flags = + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 +lib_deps = + h2zero/NimBLE-Arduino@^1.4.3 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cf56862 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,119 @@ +// Loki / Chaff — BLE Countermeasure (Phase 1) +// Broadcasts fake BLE device advertisements with randomized MACs +// to confuse passive BLE surveillance scanners. +// +// Hardware: ESP32-C3 Super Mini +// Framework: Arduino + NimBLE 1.4.x (ESP-IDF 4.4.x required for C3 BLE) + +#include +#include + +// ── Device profiles ── + +struct DeviceProfile { + const char* name; + uint16_t appearance; + uint8_t mfgId[2]; // Company ID (little-endian) + uint8_t mfgData[4]; + uint8_t mfgDataLen; +}; + +static const DeviceProfile PROFILES[] = { + {"Galaxy S24", 0x0040, {0x75, 0x00}, {0x42, 0x04, 0x01, 0x80}, 4}, + {"iPhone", 0x0040, {0x4C, 0x00}, {0x02, 0x15, 0x06, 0x01}, 4}, + {"Pixel 9", 0x0040, {0xE0, 0x00}, {0x01, 0x03, 0x00, 0x22}, 4}, + {"OnePlus 12", 0x0040, {0x75, 0x00}, {0x42, 0x04, 0x02, 0x10}, 4}, + {"iPad", 0x0080, {0x4C, 0x00}, {0x02, 0x15, 0x05, 0x03}, 4}, + {"Galaxy Watch6", 0x00C0, {0x75, 0x00}, {0x42, 0x04, 0x03, 0x60}, 4}, + {"Apple Watch", 0x00C0, {0x4C, 0x00}, {0x02, 0x15, 0x0C, 0x01}, 4}, + {"Mi Band 8", 0x02C0, {0x10, 0x03}, {0x01, 0x02, 0x08, 0x00}, 4}, + {"Fitbit Charge 6", 0x00C0, {0xE0, 0x00}, {0x01, 0x01, 0x06, 0x04}, 4}, + {"AirPods Pro", 0x0941, {0x4C, 0x00}, {0x07, 0x19, 0x01, 0x0E}, 4}, + {"Galaxy Buds3", 0x0941, {0x75, 0x00}, {0x42, 0x04, 0x05, 0x21}, 4}, + {"Surface Laptop", 0x0080, {0x06, 0x00}, {0x01, 0x09, 0x02, 0x00}, 4}, + {"Nothing Phone (2)", 0x0040, {0x75, 0x00}, {0x42, 0x04, 0x01, 0x44}, 4}, + {"Bose QC Ultra", 0x0941, {0x0F, 0x00}, {0x02, 0x01, 0x04, 0x08}, 4}, + {"JBL Flip 6", 0x0941, {0x0F, 0x00}, {0x02, 0x01, 0x06, 0x12}, 4}, + {"Sony WH-1000XM5", 0x0941, {0x0F, 0x00}, {0x02, 0x01, 0x05, 0x20}, 4}, +}; + +static const int NUM_PROFILES = sizeof(PROFILES) / sizeof(PROFILES[0]); + +static const uint32_t ADV_DURATION_MS = 2000; +static const uint32_t ADV_GAP_MS = 100; + +static int currentProfile = 0; +static NimBLEAdvertising* pAdvertising = nullptr; + +static void generateRandomMAC(uint8_t* mac) { + uint32_t r1 = esp_random(); + uint32_t r2 = esp_random(); + mac[0] = (r1 & 0xFF) | 0xC0; // Random static address bits + mac[1] = (r1 >> 8) & 0xFF; + mac[2] = (r1 >> 16) & 0xFF; + mac[3] = r2 & 0xFF; + mac[4] = (r2 >> 8) & 0xFF; + mac[5] = (r2 >> 16) & 0xFF; +} + +static void advertiseProfile(int idx) { + const DeviceProfile& p = PROFILES[idx]; + + pAdvertising->stop(); + + // Set new random MAC + uint8_t mac[6]; + generateRandomMAC(mac); + ble_hs_id_set_rnd(mac); + + // Build advertisement data + NimBLEAdvertisementData advData; + advData.setFlags(BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); + advData.setName(p.name); + advData.setAppearance(p.appearance); + + // Manufacturer-specific data + std::string mfg; + mfg += (char)p.mfgId[0]; + mfg += (char)p.mfgId[1]; + for (int i = 0; i < p.mfgDataLen; i++) { + mfg += (char)p.mfgData[i]; + } + advData.setManufacturerData(mfg); + + pAdvertising->setAdvertisementData(advData); + pAdvertising->setAdvertisementType(BLE_GAP_CONN_MODE_NON); + pAdvertising->start(); + + Serial.printf("[%4lus] %-20s MAC %02X:%02X:%02X:%02X:%02X:%02X\n", + millis() / 1000, p.name, + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +void setup() { + Serial.begin(115200); + delay(500); + + Serial.println(); + Serial.println("============================="); + Serial.println(" LOKI — BLE Countermeasure"); + Serial.println(" Phase 1: Identity Cycling"); + Serial.printf(" Profiles: %d devices\n", NUM_PROFILES); + Serial.println("============================="); + Serial.println(); + + NimBLEDevice::init(""); + NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM); + + pAdvertising = NimBLEDevice::getAdvertising(); + + Serial.println("Broadcasting...\n"); +} + +void loop() { + advertiseProfile(currentProfile); + delay(ADV_DURATION_MS); + pAdvertising->stop(); + delay(ADV_GAP_MS); + currentProfile = (currentProfile + 1) % NUM_PROFILES; +}