// 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 // ── Blue status LED on GPIO8 ── #define LED_PIN 8 // ── 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(); // Blue LED — Loki is active (active LOW on C3 Super Mini) pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); 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; }