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 <noreply@anthropic.com>
This commit is contained in:
commit
a7f26f88e4
3 changed files with 132 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.pio
|
||||
.vscode
|
||||
11
platformio.ini
Normal file
11
platformio.ini
Normal file
|
|
@ -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
|
||||
119
src/main.cpp
Normal file
119
src/main.cpp
Normal file
|
|
@ -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 <Arduino.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
// ── 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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue