loki-ble-chaff/src/main.cpp
rpriven 7f15705a8d
Fix LED polarity — active LOW on C3 Super Mini
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:43:01 -07:00

125 lines
4.2 KiB
C++

// 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 <Arduino.h>
#include <NimBLEDevice.h>
// ── 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;
}