diff --git a/lib/Bad_Usb_Lib/BleKeyboard.cpp b/lib/Bad_Usb_Lib/BleKeyboard.cpp index 63727bec8b..94dfbeb51c 100644 --- a/lib/Bad_Usb_Lib/BleKeyboard.cpp +++ b/lib/Bad_Usb_Lib/BleKeyboard.cpp @@ -206,11 +206,9 @@ void BleKeyboard::end(void) { for (j = 0; j < i; j++) pServer->disconnect(pServer->getPeerInfo(i).getConnHandle()); } delete hid; -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else - BLEDevice::deinit(); -#endif + hid = nullptr; + + BLEDevice::deinit(true); this->connected = false; } @@ -242,12 +240,13 @@ void BleKeyboard::set_version(uint16_t version) { this->version = version; } // full, made worse by reducing CONFIG_BT_NIMBLE_MSYS_*_BLOCK_COUNT). If untreated, // the key is silently dropped. Retry while yielding 1 tick for the host to drain. // Kept local because this library is self-contained (does not depend on src/). -static bool bleKbNotifyRetry(BLECharacteristic *chr, uint8_t retries = 8) { + +static bool bleKbNotifyRetry(BLECharacteristic *chr, const uint8_t *value, size_t len, uint8_t retries = 8) { if (chr == nullptr) return false; - if (chr->notify()) return true; + if (chr->notify(value, len)) return true; for (uint8_t i = 0; i < retries; i++) { vTaskDelay(1); - if (chr->notify()) return true; + if (chr->notify(value, len)) return true; } return false; } @@ -260,7 +259,7 @@ void BleKeyboard::sendReport(KeyReport *keys) { #endif { this->inputKeyboard->setValue((uint8_t *)keys, sizeof(KeyReport)); - bleKbNotifyRetry(this->inputKeyboard); + bleKbNotifyRetry(this->inputKeyboard, (uint8_t *)keys, sizeof(KeyReport)); #if defined(USE_NIMBLE) // vTaskDelay(delayTicks); this->delay_ms(_delay_ms); @@ -276,7 +275,7 @@ void BleKeyboard::sendReport(MediaKeyReport *keys) { #endif { this->inputMediaKeys->setValue((uint8_t *)keys, sizeof(MediaKeyReport)); - bleKbNotifyRetry(this->inputMediaKeys); + bleKbNotifyRetry(this->inputMediaKeys, (uint8_t *)keys, sizeof(MediaKeyReport)); #if defined(USE_NIMBLE) // vTaskDelay(delayTicks); this->delay_ms(_delay_ms); @@ -434,11 +433,11 @@ size_t BleKeyboard::write(const uint8_t *buffer, size_t size) { } #ifdef NIMBLE_V2_PLUS void BleKeyboard::ServerCallbacks::onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) { - // BleKeyboard::connected = true; + parent->connected = true; Serial.println("BRUCE KEYBOARD: lib connected"); } void BleKeyboard::ServerCallbacks::onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) { - // BleKeyboard::connected = true; + parent->connected = false; Serial.println("BRUCE KEYBOARD: lib disconnected"); } void BleKeyboard::ServerCallbacks::onAuthenticationComplete(NimBLEConnInfo &connInfo) { diff --git a/src/core/menu_items/BleMenu.cpp b/src/core/menu_items/BleMenu.cpp index f13169e386..223116dd47 100644 --- a/src/core/menu_items/BleMenu.cpp +++ b/src/core/menu_items/BleMenu.cpp @@ -17,15 +17,10 @@ void BleMenu::optionsMenu() { #if !defined(LITE_VERSION) if (BLEConnected) { options.push_back({"Disconnect", [=]() { -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else - BLEDevice::deinit(); -#endif + BLEDevice::deinit(); BLEConnected = false; delete hid_ble; hid_ble = nullptr; - if (_Ask_for_restart == 1) _Ask_for_restart = 2; }}); } #endif diff --git a/src/core/radio_mem.h b/src/core/radio_mem.h index deb995ad60..7c6ba3edc3 100644 --- a/src/core/radio_mem.h +++ b/src/core/radio_mem.h @@ -17,6 +17,8 @@ */ #include #include +#include "core/wifi/wifi_common.h" +#include // Largest contiguous DMA-capable internal block, in bytes. This is the number // that gates Wi-Fi/BLE controller init. @@ -36,7 +38,35 @@ static inline bool radioHasMemForWifi() { static inline bool radioHasMemForBle() { // return true; // uncomment to disable it - return radioLargestDmaBlock() >= RADIO_BLE_MIN_DMA_BLOCK; + + // First check if we have enough DMA memory + if (radioLargestDmaBlock() >= RADIO_BLE_MIN_DMA_BLOCK) { + return true; + } + + // Not enough DMA memory - try to free WiFi + Serial.println("[RAM] Low contiguous DMA memory for BLE, attempting to free WiFi..."); + + // Disconnect WiFi if active + if (WiFi.getMode() != WIFI_MODE_NULL || wifiConnected) { + wifiDisconnect(); + delay(200); + #ifdef WIFI_DEINIT_ON_DISCONNECT + WiFi.mode(WIFI_OFF); + #endif + delay(300); + } + + // Recheck after freeing WiFi + if (radioLargestDmaBlock() >= RADIO_BLE_MIN_DMA_BLOCK) { + Serial.printf("[RAM] WiFi freed, DMA block: %d bytes\n", radioLargestDmaBlock()); + return true; + } + + // Still not enough - return false, caller shows error + Serial.printf("[RAM] Still only %d bytes DMA block, minimum %d needed\n", + radioLargestDmaBlock(), RADIO_BLE_MIN_DMA_BLOCK); + return false; } #endif // __RADIO_MEM_H__ diff --git a/src/modules/badusb_ble/ducky_typer.cpp b/src/modules/badusb_ble/ducky_typer.cpp index 7f780a79ea..c1a2f5ed73 100644 --- a/src/modules/badusb_ble/ducky_typer.cpp +++ b/src/modules/badusb_ble/ducky_typer.cpp @@ -5,13 +5,15 @@ #include "core/radio_mem.h" #include "core/sd_functions.h" #include "core/utils.h" +#include "esp_mac.h" +#include "modules/ble/ble_common.h" +#include #if defined(USB_as_HID) #include "tusb.h" #endif #define DEF_DELAY 100 -uint8_t _Ask_for_restart = 0; int currentOutputY = 0; #if !defined(USB_as_HID) @@ -21,6 +23,125 @@ HardwareSerial mySerial(1); HIDInterface *hid_usb = nullptr; HIDInterface *hid_ble = nullptr; +// Track active BLE instances to know when to deinit +int activeBLEInstances = 0; + +// ============================================================================ +// PER-FUNCTION MAC ADDRESSES - Logitech OUIs (look like real keyboards!) +// ============================================================================ + +// All using Logitech OUI 88:3B:5F +// The suffixes are fixed but look random +static const uint8_t FUNC_MACS[4][6] = { + {0x88, 0x3B, 0x5F, 0x7A, 0x3F, 0x1E}, // Keyboard - Logitech + {0x88, 0x3B, 0x5F, 0x4B, 0x8C, 0x2A}, // Media - Logitech + {0x88, 0x3B, 0x5F, 0x9E, 0x5F, 0x37}, // BadUSB - Logitech + {0x88, 0x3B, 0x5F, 0x6C, 0x92, 0x4D} // Presenter - Logitech +}; + +// ============================================================================ +// MAC ADDRESS SETTING - Works across ESP32 variants +// ============================================================================ + +void setBleMac(const uint8_t *mac) { +#ifdef ESP_MAC_BT + esp_iface_mac_addr_set(mac, ESP_MAC_BT); + Serial.println("[setBleMac] Set MAC using esp_iface_mac_addr_set"); +#else + esp_base_mac_addr_set(mac); + Serial.println("[setBleMac] Set MAC using esp_base_mac_addr_set"); +#endif +} + +// ============================================================================ +// CLEANUP - Cleans a specific HID instance +// ============================================================================ + +void cleanupDuckyBLE(HIDInterface *&hid) { + if (hid) { + BleKeyboard *bleKb = static_cast(hid); + + // Release all keys first (clean state) + bleKb->releaseAll(); + delay(20); + + // Stop advertising + if (NimBLEDevice::getAdvertising()) { + NimBLEDevice::getAdvertising()->stop(); + Serial.println("[cleanupDuckyBLE] Advertising stopped"); + } + delay(50); + + // If this was the active instance, clear the pointer + if (hid_ble == hid) { + hid_ble = nullptr; + Serial.println("[cleanupDuckyBLE] Cleared hid_ble pointer"); + } + + // Decrement active instance count + if (activeBLEInstances > 0) { + activeBLEInstances--; + Serial.printf("[cleanupDuckyBLE] Active BLE instances: %d\n", activeBLEInstances); + } + + // CRITICAL: Only call end() if this is the LAST instance + // end() deinits the BLE stack, so we only want to do it once + if (activeBLEInstances == 0) { + // Last instance - call end() to fully clean up + bleKb->end(); + Serial.println("[cleanupDuckyBLE] Last instance: BleKeyboard::end() called (deinits BLE)"); + } else { + // Not the last instance - just delete the object without calling end() + Serial.println("[cleanupDuckyBLE] Not last instance: deleting HID without end()"); + } + + // Delete the wrapper object + delete hid; + hid = nullptr; + Serial.println("[cleanupDuckyBLE] hid deleted"); + } + + // Only deinit BLE when NO instances are left (safety) + if (activeBLEInstances == 0 && NimBLEDevice::isInitialized()) { + Serial.println("[cleanupDuckyBLE] Last instance, ensuring BLE deinit..."); + NimBLEDevice::deinit(); + delay(50); + Serial.println("[cleanupDuckyBLE] BLE stack deinitialized"); + } + + BLEConnected = false; +} + +// ============================================================================ +// SAFE CLEANUP - Double cleanup with cooling delay +// ============================================================================ + +void safeCleanupDuckyBLE(HIDInterface *&hid) { + Serial.println("[safeCleanupDuckyBLE] First cleanup pass..."); + cleanupDuckyBLE(hid); + delay(200); + + // Second pass to catch anything lingering + if (hid != nullptr) { + Serial.println("[safeCleanupDuckyBLE] Second cleanup pass..."); + cleanupDuckyBLE(hid); + delay(200); + } + + // Extra safety: if BLE is still initialized, force deinit + if (NimBLEDevice::isInitialized()) { + Serial.println("[safeCleanupDuckyBLE] Force deinitializing BLE..."); + NimBLEDevice::deinit(); + delay(200); + } + + Serial.println("[safeCleanupDuckyBLE] Cleanup complete"); +} + +// ============================================================================ +// DUCKY COMMAND STRUCTURES - Stored in PROGMEM +// ============================================================================ + enum DuckyCommandType { DuckyCommandType_Cmd, DuckyCommandType_Print, @@ -47,7 +168,8 @@ struct DuckyCombination { char key2; char key3; }; -const DuckyCombination duckyComb[]{ + +const DuckyCombination duckyComb[] PROGMEM = { {"CTRL-ALT", KEY_LEFT_CTRL, KEY_LEFT_ALT, 0 }, {"CTRL-SHIFT", KEY_LEFT_CTRL, KEY_LEFT_SHIFT, 0 }, {"CTRL-GUI", KEY_LEFT_CTRL, KEY_LEFT_GUI, 0 }, @@ -63,7 +185,7 @@ const DuckyCombination duckyComb[]{ {"SYSREQ", KEY_LEFT_ALT, KEY_PRINT_SCREEN, 0 } }; -const DuckyCommandLookup duckyCmds[]{ +const DuckyCommandLookup duckyCmds[] PROGMEM = { {"REM", 0, DuckyCommandType_Comment }, {"//", 0, DuckyCommandType_Comment }, {"STRING", 0, DuckyCommandType_Print }, @@ -155,7 +277,7 @@ const DuckyCommandLookup duckyCmds[]{ {"GLOBE", KEYFN, DuckyCommandType_Cmd }, }; -const uint8_t *keyboardLayouts[] = { +const uint8_t *keyboardLayouts[] PROGMEM = { KeyboardLayout_en_US, // 0 KeyboardLayout_da_DK, // 1 KeyboardLayout_en_UK, // 2 @@ -172,6 +294,10 @@ const uint8_t *keyboardLayouts[] = { KeyboardLayout_tr_TR // 13 }; +// ============================================================================ +// MENU KEY STRUCTURES - Stored in PROGMEM +// ============================================================================ + struct KeyboardMenuKey { const char *label; uint8_t key; @@ -182,7 +308,7 @@ struct QueuedHIDKey { String label; }; -static const KeyboardMenuKey modifierMenuKeys[] = { +static const KeyboardMenuKey modifierMenuKeys[] PROGMEM = { {"Ctrl", KEY_LEFT_CTRL }, {"Shift", KEY_LEFT_SHIFT }, {"Alt", KEY_LEFT_ALT }, @@ -194,7 +320,7 @@ static const KeyboardMenuKey modifierMenuKeys[] = { {"Fn", KEYFN }, }; -static const KeyboardMenuKey navigationMenuKeys[] = { +static const KeyboardMenuKey navigationMenuKeys[] PROGMEM = { {"Up Arrow", KEY_UP_ARROW }, {"Down Arrow", KEY_DOWN_ARROW }, {"Left Arrow", KEY_LEFT_ARROW }, @@ -207,7 +333,7 @@ static const KeyboardMenuKey navigationMenuKeys[] = { {"Delete", KEY_DELETE }, }; -static const KeyboardMenuKey specialMenuKeys[] = { +static const KeyboardMenuKey specialMenuKeys[] PROGMEM = { {"Enter", KEY_RETURN }, {"Tab", KEYTAB }, {"Escape", KEY_ESC }, @@ -221,7 +347,7 @@ static const KeyboardMenuKey specialMenuKeys[] = { {"Menu", KEY_MENU }, }; -static const KeyboardMenuKey functionMenuKeys[] = { +static const KeyboardMenuKey functionMenuKeys[] PROGMEM = { {"F1", KEY_F1 }, {"F2", KEY_F2 }, {"F3", KEY_F3 }, @@ -248,7 +374,7 @@ static const KeyboardMenuKey functionMenuKeys[] = { {"F24", KEY_F24}, }; -static const KeyboardMenuKey numpadMenuKeys[] = { +static const KeyboardMenuKey numpadMenuKeys[] PROGMEM = { {"KP /", KEY_KP_SLASH }, {"KP *", KEY_KP_ASTERISK}, {"KP -", KEY_KP_MINUS }, @@ -267,6 +393,10 @@ static const KeyboardMenuKey numpadMenuKeys[] = { {"KP .", KEY_KP_DOT }, }; +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + static bool isModifierKeyForQueue(uint8_t key) { switch (key) { case KEY_LEFT_CTRL: @@ -343,6 +473,10 @@ static void queueOrSendKey( displayTextLine("Queued: " + queueToString(queuedKeys)); } +static void getMenuKey(const KeyboardMenuKey *src, KeyboardMenuKey *dst) { + memcpy_P(dst, src, sizeof(KeyboardMenuKey)); +} + static void openKeySection( HIDInterface *hid, const char *title, const KeyboardMenuKey *menuKeys, size_t keyCount, bool &queueRecording, std::vector &queuedKeys @@ -353,7 +487,8 @@ static void openKeySection( sectionOptions.reserve(keyCount + 1); for (size_t i = 0; i < keyCount; i++) { - KeyboardMenuKey menuKey = menuKeys[i]; + KeyboardMenuKey menuKey; + getMenuKey(&menuKeys[i], &menuKey); sectionOptions.push_back( {menuKey.label, [&, menuKey]() { queueOrSendKey(hid, queueRecording, queuedKeys, menuKey.label, menuKey.key); @@ -398,31 +533,128 @@ sendQueuedKeys(HIDInterface *hid, bool &queueRecording, const std::vector= 0 && functionId < 4) { + Serial.printf("[ducky_startKb] Setting MAC for function %d: ", functionId); + for (int i = 0; i < 6; i++) { + Serial.printf("%02X%s", FUNC_MACS[functionId][i], i < 5 ? ":" : ""); + } + Serial.println(); + setBleMac(FUNC_MACS[functionId]); + delay(50); + } + + // Build device name with suffix for unique identification + String deviceName = bruceConfigPins.bleName; + if (deviceName.isEmpty()) { deviceName = "keyboard_99"; } + + Serial.printf("[ducky_startKb] Device name: %s\n", deviceName.c_str()); + + // Check if BLE needs initialization + if (!NimBLEDevice::isInitialized()) { + Serial.println("[ducky_startKb] Initializing BLE stack..."); + NimBLEDevice::init(std::string(deviceName.c_str())); + Serial.println("[ducky_startKb] BLE stack initialized"); + delay(50); + } else { + Serial.println("[ducky_startKb] BLE stack already initialized"); + // Stop any existing advertising + if (NimBLEDevice::getAdvertising()) { + NimBLEDevice::getAdvertising()->stop(); + Serial.println("[ducky_startKb] Stopped existing advertising"); + } + } + + // Increment active instance count + activeBLEInstances++; + Serial.printf("[ducky_startKb] Active BLE instances: %d\n", activeBLEInstances); + + // Create HID service + hid = new BleKeyboard(deviceName, "BruceFW", 100); + Serial.println("[ducky_startKb] New BleKeyboard created"); + + // Set hid_ble to point to the active instance + hid_ble = hid; + Serial.printf("[ducky_startKb] hid_ble now points to instance %p\n", hid_ble); + + const uint8_t *layout = + (const uint8_t *)pgm_read_ptr(&keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + + // Start the HID service + hid->begin(layout); + hid->setDelay(bruceConfig.badUSBBLEKeyDelay); + + // CRITICAL: Wait for HID service to be fully registered + delay(200); + + // Force a clean state + hid->releaseAll(); + + Serial.println("[ducky_startKb] HID service started, advertising"); + return; } else { #if defined(USB_as_HID) hid = new USBHIDKeyboard(); USB.begin(); - // Wait for USB subsystem to be ready while (!tud_mounted()) { printStatusBadUSBBLE("Waiting USB Host..."); delay(500); @@ -436,31 +668,43 @@ void ducky_startKb(HIDInterface *&hid, bool ble) { #endif } } + if (ble) { if (hid->isConnected()) { - // If connected as media controller and switch to BadBLE, changes the layout - Serial.println("BLE Already connected, changing layout and delay"); - hid->setLayout(keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + Serial.println("BLE Already connected, updating settings"); + const uint8_t *layout = + (const uint8_t *)pgm_read_ptr(&keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + hid->setLayout(layout); hid->setDelay(bruceConfig.badUSBBLEKeyDelay); return; } - if (!_Ask_for_restart) _Ask_for_restart = 1; // arm the flag - hid->begin(keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + + Serial.println("Starting/restarting BLE advertising"); + const uint8_t *layout = + (const uint8_t *)pgm_read_ptr(&keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + hid->begin(layout); hid->setDelay(bruceConfig.badUSBBLEKeyDelay); } else { #if defined(USB_as_HID) - hid->begin(keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + const uint8_t *layout = + (const uint8_t *)pgm_read_ptr(&keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + hid->begin(layout); hid->setDelay(bruceConfig.badUSBBLEKeyDelay); #else mySerial.begin(CH9329_DEFAULT_BAUDRATE, SERIAL_8N1, BAD_RX, BAD_TX); delay(100); - hid->begin(mySerial, keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + const uint8_t *layout = + (const uint8_t *)pgm_read_ptr(&keyboardLayouts[bruceConfig.badUSBBLEKeyboardLayout]); + hid->begin(mySerial, layout); hid->setDelay(bruceConfig.badUSBBLEKeyDelay); #endif } } -// Start badUSBBLE or badBLE ducky runner +// ============================================================================ +// DUCKY SETUP - Main entry for BadUSB/BLE script runner +// ============================================================================ + void ducky_setup(HIDInterface *&hid, bool ble) { Serial.println("Ducky typer begin"); @@ -470,11 +714,6 @@ void ducky_setup(HIDInterface *&hid, bool ble) { tft.fillScreen(bruceConfig.bgColor); - if (ble && _Ask_for_restart == 2) { - displayError("Restart your Device"); - returnToMenu = true; - return; - } FS *fs = nullptr; bool first_time = true; @@ -503,8 +742,10 @@ void ducky_setup(HIDInterface *&hid, bool ble) { if (first_time) { printStatusBadUSBBLE("Preparing USB"); - ducky_startKb(hid, ble); - if (returnToMenu) goto EXIT; // make sure to free the hid object before exiting + // Double cleanup before starting + if (ble) safeCleanupDuckyBLE(hid); + ducky_startKb(hid, ble, 2); // functionId 2 = BadUSB + if (returnToMenu) goto EXIT; first_time = false; if (!ble) { #if !defined(USB_as_HID) @@ -516,14 +757,14 @@ void ducky_setup(HIDInterface *&hid, bool ble) { mySerial.write(0x00); } else break; if (check(EscPress)) { - displayError("CH9329 not found"); // Cancel run + displayError("CH9329 not found"); delay(500); goto EXIT; } } #endif printStatusBadUSBBLE("Preparing USB"); - delay(2000); // Time to Computer or device recognize the USB HID + delay(2000); } else { printStatusBadUSBBLE("Waiting Victim"); while (!hid->isConnected() && !check(EscPress)) { vTaskDelay(pdMS_TO_TICKS(1)); } @@ -549,17 +790,21 @@ void ducky_setup(HIDInterface *&hid, bool ble) { } EXIT: if (!ble) { - delete hid; // Keep the hid object alive for BLE + delete hid; hid = nullptr; #if !defined(USB_as_HID) - mySerial.end(); // Stops UART Serial as HID - Serial.begin(115200); // Force restart of Serial, just in case.... + mySerial.end(); + Serial.begin(115200); #endif } + if (ble) safeCleanupDuckyBLE(hid); returnToMenu = true; } -// Parses a file to run in the badUSBBLE +// ============================================================================ +// KEY_INPUT - Main Ducky script parser +// ============================================================================ + void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { if (!fs.exists(bad_script) || bad_script == "") return; File payloadFile = fs.open(bad_script, "r"); @@ -570,22 +815,19 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { String Argument = ""; String RepeatTmp = ""; - // String delay variables - static int nextStringDelay = -1; // One-time delay for next STRING command (-1 = use default) - static int defaultStringDelay = bruceConfig.badUSBBLEKeyDelay; // Default delay for all STRING commands + static int nextStringDelay = -1; + static int defaultStringDelay = bruceConfig.badUSBBLEKeyDelay; currentOutputY = 0; _hid->releaseAll(); printHeaderBadUSBBLE(bad_script); - printStatusBadUSBBLE("Running"); tft.setTextSize(FP); tft.setTextColor(bruceConfig.priColor); tft.setCursor(BORDER_OFFSET_FROM_SCREEN_EDGE * 2, FP * 8 * 3 + 2 + STATUS_BAR_HEIGHT); tft.print("Run Time:"); - printDecimalTime(0); tft.drawLine( @@ -605,21 +847,17 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { uint32_t startMillisBADUSBBLE = millis(); while (payloadFile.available()) { - - previousMillis = millis(); // resets DimScreen + previousMillis = millis(); if (check(SelPress)) { if (!handlePauseResume()) { goto EXIT; } } - // CRLF is a combination of two control characters: the "Carriage Return" represented by - // the character "\r" and the "Line Feed" represented by the character "\n". lineContent = payloadFile.readStringUntil('\n'); if (lineContent.endsWith("\r")) lineContent.remove(lineContent.length() - 1); - if (lineContent.length() == 0) continue; // skip empty lines + if (lineContent.length() == 0) continue; int spaceIndex = lineContent.indexOf(' '); - // Check if this is a REPEAT command if (spaceIndex > 0 && lineContent.substring(0, spaceIndex) == "REPEAT") { RepeatTmp = lineContent.substring(spaceIndex + 1); if (RepeatTmp.toInt() <= 0) { @@ -648,68 +886,46 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { DuckyCommandLookup *ArgCmd = findDuckyCommand(Argument.c_str()); if (PriCmd != nullptr) { - vTaskDelay(1); // Allow other tasks to run - // REM comment lines are processed here + vTaskDelay(1); if (PriCmd->type == DuckyCommandType_Comment) { - // Do nothing for comments - } - // STRING and STRINGLN are processed here - else if (PriCmd->type == DuckyCommandType_Print) { - // Set appropriate delay for this STRING command + } else if (PriCmd->type == DuckyCommandType_Print) { int currentDelay = (nextStringDelay >= 0) ? nextStringDelay : defaultStringDelay; _hid->setDelay(currentDelay); _hid->print(Argument); if (strcmp(PriCmd->command, "STRINGLN") == 0) _hid->println(); - // Reset one-time delay after use if (nextStringDelay >= 0) { nextStringDelay = -1; } - } - // WAIT_FOR_BUTTON_PRESS is processed here - else if (PriCmd->type == DuckyCommandType_WaitForButtonPress) { + } else if (PriCmd->type == DuckyCommandType_WaitForButtonPress) { printStatusBadUSBBLE("Waiting for button press"); bool waitSelect = false; while (!waitSelect) { waitSelect = check(SelPress); - delay(50); // Small delay to prevent excessive CPU usage + delay(50); } printStatusBadUSBBLE("Running"); tft.setTextSize(1); - } - // DELAY and DEFAULTDELAY are processed here - else if (PriCmd->type == DuckyCommandType_Delay) { - if ((int)PriCmd->key > 0) delay(DEF_DELAY); // Default delay is 10ms + } else if (PriCmd->type == DuckyCommandType_Delay) { + if ((int)PriCmd->key > 0) delay(DEF_DELAY); else { int delayTime = Argument.toInt(); if (delayTime > 0) delay(delayTime); else delay(DEF_DELAY); } - } - // ALTCHAR command is processed here - else if (PriCmd->type == DuckyCommandType_AltChar) { + } else if (PriCmd->type == DuckyCommandType_AltChar) { int charCode = Argument.toInt(); if (charCode > 0 && charCode <= 255) { sendAltChar(_hid, (uint8_t)charCode); } - } - // ALTSTRING and ALTCODE commands are processed here - else if (PriCmd->type == DuckyCommandType_AltString) { + } else if (PriCmd->type == DuckyCommandType_AltString) { sendAltString(_hid, Argument); - } - // STRING_DELAY and STRINGDELAY commands are processed here - else if (PriCmd->type == DuckyCommandType_StringDelay) { + } else if (PriCmd->type == DuckyCommandType_StringDelay) { int delayValue = Argument.toInt(); if (delayValue >= 0) { nextStringDelay = delayValue; } - } - // DEFAULT_STRING_DELAY and DEFAULTSTRINGDELAY commands are processed here - else if (PriCmd->type == DuckyCommandType_DefaultStringDelay) { + } else if (PriCmd->type == DuckyCommandType_DefaultStringDelay) { int delayValue = Argument.toInt(); if (delayValue >= 0) { defaultStringDelay = delayValue; } - } - // Normal commands are processed here - else if (PriCmd->type == DuckyCommandType_Cmd) { + } else if (PriCmd->type == DuckyCommandType_Cmd) { _hid->press(PriCmd->key); - } - // Combinations are processed here - else if (PriCmd->type == DuckyCommandType_Combination) { + } else if (PriCmd->type == DuckyCommandType_Combination) { DuckyCombination *comb = findDuckyCombination(Cmd); if (comb != nullptr) { _hid->press(comb->key1); @@ -718,7 +934,6 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { } } - // Send keys if (PriCmd->type != DuckyCommandType_Comment) { if (ArgCmd != nullptr && PriCmd != nullptr && ArgCmd->type == DuckyCommandType_Cmd && PriCmd->type == DuckyCommandType_Cmd) { @@ -734,7 +949,6 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { } } - // Output to screen if (PriCmd == nullptr) { printTFTBadUSBBLE(Command + " - UNKNOWN COMMAND", ALCOLOR, true); } else if (PriCmd->type != DuckyCommandType_Comment) { @@ -758,27 +972,36 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { _hid->releaseAll(); } -// Sends a simple command -void key_input_from_string(const String &text) { - ducky_startKb(hid_usb, false); - - hid_usb->print(text.c_str()); // buggy with some special chars +// ============================================================================ +// KEY_INPUT_FROM_STRING - Simple text input via USB +// ============================================================================ +void key_input_from_string(const String &text) { + ducky_startKb(hid_usb, false, 0); + hid_usb->print(text.c_str()); delete hid_usb; hid_usb = nullptr; #if !defined(USB_as_HID) mySerial.end(); #endif } + #ifndef KB_HID_EXIT_MSG #define KB_HID_EXIT_MSG "Exit" #endif -// Use device as a keyboard (USB or BLE) + +// ============================================================================ +// DUCKY_KEYBOARD - Interactive keyboard mode (USB or BLE) +// ============================================================================ + void ducky_keyboard(HIDInterface *&hid, bool ble) { String _mymsg = ""; keyStroke key; long debounce = millis(); - ducky_startKb(hid, ble); + + // Double cleanup before starting + if (ble) safeCleanupDuckyBLE(hid); + ducky_startKb(hid, ble, 0); // functionId 0 = Keyboard if (returnToMenu) return; if (ble) { @@ -791,7 +1014,6 @@ void ducky_keyboard(HIDInterface *&hid, bool ble) { goto EXIT; } } else { - // send a key to start communication hid->press(KEY_LEFT_ALT); hid->releaseAll(); } @@ -827,7 +1049,6 @@ void ducky_keyboard(HIDInterface *&hid, bool ble) { hid->releaseAll(); - // only text for tft String keyStr = ""; for (auto i : key.word) { if (keyStr != "") { @@ -842,7 +1063,7 @@ void ducky_keyboard(HIDInterface *&hid, bool ble) { if (_mymsg.length() > keyStr.length()) tft.drawCentreString( " ", tftWidth / 2, tftHeight / 2, 1 - ); // clears screen + ); tft.drawCentreString("Pressed: " + keyStr, tftWidth / 2, tftHeight / 2, 1); _mymsg = keyStr; } @@ -946,31 +1167,31 @@ void ducky_keyboard(HIDInterface *&hid, bool ble) { #endif } EXIT: + if (ble) safeCleanupDuckyBLE(hid); + if (!ble) { - delete hid; // Keep the hid object alive for BLE + delete hid; hid = nullptr; #if !defined(USB_as_HID) - mySerial.end(); // Stops UART Serial as HID - Serial.begin(115200); // Force restart of Serial, just in case.... + mySerial.end(); + Serial.begin(115200); #endif } } -// Send media commands through BLE or USB HID -void MediaCommands(HIDInterface *hid, bool ble) { - if (_Ask_for_restart == 2) { - displayWarning("Restart your Device", true); - return; - } +// ============================================================================ +// MEDIA COMMANDS - BLE Media Controller +// ============================================================================ - ducky_startKb(hid, true); +void MediaCommands(HIDInterface *hid, bool ble) { + // Double cleanup before starting + safeCleanupDuckyBLE(hid); + ducky_startKb(hid, true, 1); // functionId 1 = Media displayTextLine("Pairing..."); while (!hid->isConnected() && !check(EscPress)) { delay(50); }; - _Ask_for_restart = 1; // arm the flag - if (hid->isConnected()) { BLEConnected = true; drawMainBorder(); @@ -998,36 +1219,24 @@ void MediaCommands(HIDInterface *hid, bool ble) { hid->releaseAll(); if (!returnToMenu) goto reMenu; } - returnToMenu = true; -} -DuckyCommandLookup *findDuckyCommand(const char *cmd) { - for (auto &cmds : duckyCmds) { - if (strcmp(cmd, cmds.command) == 0) { return const_cast(&cmds); } - } - return nullptr; + safeCleanupDuckyBLE(hid); + returnToMenu = true; } -DuckyCombination *findDuckyCombination(const char *cmd) { - for (auto &comb : duckyComb) { - if (strcmp(cmd, comb.command) == 0) { return const_cast(&comb); } - } - return nullptr; -} +// ============================================================================ +// ALT CHARACTER FUNCTIONS +// ============================================================================ void sendAltChar(HIDInterface *hid, uint8_t charCode) { - // Hold ALT key hid->press(KEY_LEFT_ALT); delay(bruceConfig.badUSBBLEKeyDelay); - // Convert char code to 3-digit padded string (standard ALT code format) String codeStr = String(charCode); if (codeStr.length() < 3) { - // Pad with leading zeros for proper ALT codes (e.g., 065 instead of 65) while (codeStr.length() < 3) { codeStr = "0" + codeStr; } } - // Send each digit using numpad keys for (int i = 0; i < codeStr.length(); i++) { char digit = codeStr[i]; uint8_t numpadKey = 0; @@ -1043,7 +1252,7 @@ void sendAltChar(HIDInterface *hid, uint8_t charCode) { case '7': numpadKey = KEY_KP_7; break; case '8': numpadKey = KEY_KP_8; break; case '9': numpadKey = KEY_KP_9; break; - default: continue; // Skip invalid characters + default: continue; } hid->press(numpadKey); @@ -1052,7 +1261,6 @@ void sendAltChar(HIDInterface *hid, uint8_t charCode) { delay(bruceConfig.badUSBBLEKeyDelay); } - // Release ALT key (this triggers the character input) hid->release(KEY_LEFT_ALT); delay(bruceConfig.badUSBBLEKeyDelay); } @@ -1065,6 +1273,10 @@ void sendAltString(HIDInterface *hid, const String &text) { } } +// ============================================================================ +// DISPLAY FUNCTIONS +// ============================================================================ + void printTextAtPosition(uint16_t xOffset, uint16_t yOffset, const String &text) { uint16_t currentTextCursorX = tft.getCursorX(); uint16_t currentTextCursorY = tft.getCursorY(); @@ -1110,13 +1322,9 @@ void printTFTBadUSBBLE(const String &text, uint16_t color, bool newline) { const int rightLimit = tftWidth - BORDER_OFFSET_FROM_SCREEN_EDGE * 2; const int lineHeight = 9; - // Use current cursor X if already set int cursorX = tft.getCursorX(); - if (cursorX < leftX || cursorX > rightLimit) { - cursorX = leftX; // reset if out of bounds - } + if (cursorX < leftX || cursorX > rightLimit) { cursorX = leftX; } - // Scroll if we reach the bottom if (currentOutputY == 0 || currentOutputY > tftHeight - BORDER_OFFSET_FROM_SCREEN_EDGE * 2 - lineHeight) { tft.fillRect( leftX, @@ -1133,16 +1341,13 @@ void printTFTBadUSBBLE(const String &text, uint16_t color, bool newline) { tft.setTextColor(color); tft.setTextSize(FP); - // Calculate max characters that fit from current cursor to right edge int charWidth = 6 * FP; int availableWidth = rightLimit - cursorX; int maxChars = availableWidth / charWidth; - // Crop the string if necessary String textToPrint = text; if (text.length() > maxChars) { textToPrint = text.substring(0, maxChars); } - // Print text if (newline) { tft.println(textToPrint); currentOutputY += lineHeight; @@ -1151,43 +1356,40 @@ void printTFTBadUSBBLE(const String &text, uint16_t color, bool newline) { } } -// Helper function to wait for button press (Select or Escape) -// Returns true if Select was pressed, false if Escape was pressed +// ============================================================================ +// BUTTON HANDLING FUNCTIONS +// ============================================================================ + bool waitForButtonPress() { bool exitPressed = false; bool selectPressed = false; while (!selectPressed && !exitPressed) { selectPressed = check(SelPress); exitPressed = check(EscPress); - delay(50); // Small delay to prevent excessive CPU usage + delay(50); } - return selectPressed; // Return true for Select, false for Escape + return selectPressed; } -// Helper function to handle pause/resume logic during script execution -// Returns true to continue, false to exit bool handlePauseResume() { - while (check(SelPress)) { - vTaskDelay(pdMS_TO_TICKS(1)); - } // hold the code in this position until release the btn + while (check(SelPress)) { vTaskDelay(pdMS_TO_TICKS(1)); } printStatusBadUSBBLE("Paused - " + String(BTN_ALIAS) + " to resume"); if (!waitForButtonPress()) { printStatusBadUSBBLE("Canceled"); - return false; // Signal to exit + return false; } printStatusBadUSBBLE("Running"); - return true; // Signal to continue + return true; } -// Presenter mode - simple button press to advance slides -void PresenterMode(HIDInterface *&hid, bool ble) { - if (_Ask_for_restart == 2) { - displayError("Restart your Device"); - delay(1000); - return; - } +// ============================================================================ +// PRESENTER MODE - BLE Presentation Remote +// ============================================================================ - ducky_startKb(hid, ble); +void PresenterMode(HIDInterface *&hid, bool ble) { + // Double cleanup before starting + if (ble) safeCleanupDuckyBLE(hid); + ducky_startKb(hid, ble, 3); // functionId 3 = Presenter displayTextLine("Pairing..."); @@ -1201,43 +1403,33 @@ void PresenterMode(HIDInterface *&hid, bool ble) { BLEConnected = true; - // Initialize presenter state int currentSlide = 1; int lastDisplayedSlide = 0; - unsigned long startTime = 0; // Will be set on first interaction + unsigned long startTime = 0; unsigned long lastDisplayedSeconds = 0; bool timerStarted = false; - bool firstDraw = true; - // Helper function to draw static UI elements (only once) auto drawStaticUI = [&]() { tft.fillScreen(bruceConfig.bgColor); - // Draw title "PRESENTER" at the top tft.setTextSize(FM); tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); tft.drawCentreString("PRESENTER", tftWidth / 2, 10, 1); - // Draw a separator line tft.drawFastHLine(10, 35, tftWidth - 20, bruceConfig.priColor); - // Draw time label tft.setTextSize(FM); tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); tft.drawCentreString("Time", tftWidth / 2, tftHeight / 2 + 15, 1); - // Draw controls hint at bottom tft.setTextSize(1); tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); tft.drawCentreString("<< PREV | SEL | NEXT >>", tftWidth / 2, tftHeight - 15, 1); }; - // Helper function to update slide number (only when changed) auto updateSlideDisplay = [&]() { - // Clear previous slide area tft.fillRect(0, tftHeight / 2 - 35, tftWidth, 40, bruceConfig.bgColor); - // Draw current slide number - large and centered tft.setTextSize(4); tft.setTextColor(TFT_WHITE, bruceConfig.bgColor); String slideStr = "Slide " + String(currentSlide); @@ -1245,7 +1437,6 @@ void PresenterMode(HIDInterface *&hid, bool ble) { lastDisplayedSlide = currentSlide; }; - // Helper function to update timer (only when seconds change) auto updateTimerDisplay = [&]() { unsigned long elapsed = 0; if (timerStarted) { elapsed = (millis() - startTime) / 1000; } @@ -1254,7 +1445,6 @@ void PresenterMode(HIDInterface *&hid, bool ble) { int minutes = (elapsed % 3600) / 60; int seconds = elapsed % 60; - // Format time string char timeBuffer[16]; if (hours > 0) { snprintf(timeBuffer, sizeof(timeBuffer), "%d:%02d:%02d", hours, minutes, seconds); @@ -1262,7 +1452,6 @@ void PresenterMode(HIDInterface *&hid, bool ble) { snprintf(timeBuffer, sizeof(timeBuffer), "%02d:%02d", minutes, seconds); } - // Clear timer area and redraw tft.fillRect(0, tftHeight / 2 + 30, tftWidth, 30, bruceConfig.bgColor); tft.setTextSize(3); tft.setTextColor(timerStarted ? TFT_GREEN : TFT_DARKGREY, bruceConfig.bgColor); @@ -1271,7 +1460,6 @@ void PresenterMode(HIDInterface *&hid, bool ble) { lastDisplayedSeconds = elapsed; }; - // Initial UI draw drawStaticUI(); updateSlideDisplay(); updateTimerDisplay(); @@ -1279,15 +1467,12 @@ void PresenterMode(HIDInterface *&hid, bool ble) { while (1) { bool slideChanged = false; - // Middle button = Next slide (Right Arrow for presentations) if (check(SelPress)) { - delay(50); // Allow system to stabilize after check() - // First press only starts timer, doesn't send key + delay(50); if (!timerStarted) { startTime = millis(); timerStarted = true; updateTimerDisplay(); - // Prime the HID connection with an empty report hid->releaseAll(); delay(50); } else { @@ -1297,17 +1482,13 @@ void PresenterMode(HIDInterface *&hid, bool ble) { currentSlide++; slideChanged = true; } - delay(150); // debounce - } - // Wheel right = Next slide (Right arrow) - else if (check(NextPress)) { - delay(50); // Allow system to stabilize after check() - // First press only starts timer, doesn't send key + delay(150); + } else if (check(NextPress)) { + delay(50); if (!timerStarted) { startTime = millis(); timerStarted = true; updateTimerDisplay(); - // Prime the HID connection with an empty report hid->releaseAll(); delay(50); } else { @@ -1317,17 +1498,13 @@ void PresenterMode(HIDInterface *&hid, bool ble) { currentSlide++; slideChanged = true; } - delay(150); // debounce - } - // Wheel left = Previous slide (Left arrow) - else if (check(PrevPress)) { - delay(50); // Allow system to stabilize after check() - // First press only starts timer, doesn't send key + delay(150); + } else if (check(PrevPress)) { + delay(50); if (!timerStarted) { startTime = millis(); timerStarted = true; updateTimerDisplay(); - // Prime the HID connection with an empty report hid->releaseAll(); delay(50); } else { @@ -1337,28 +1514,26 @@ void PresenterMode(HIDInterface *&hid, bool ble) { if (currentSlide > 1) currentSlide--; slideChanged = true; } - delay(150); // debounce + delay(150); } - // Update slide display only if changed if (slideChanged) { updateSlideDisplay(); updateTimerDisplay(); } - // Update timer display every second (only if timer is running) if (timerStarted) { unsigned long currentSeconds = (millis() - startTime) / 1000; if (currentSeconds != lastDisplayedSeconds) { updateTimerDisplay(); } } - // Escape to exit if (check(EscPress)) break; delay(10); } hid->releaseAll(); + if (ble) safeCleanupDuckyBLE(hid); returnToMenu = true; } #endif diff --git a/src/modules/badusb_ble/ducky_typer.h b/src/modules/badusb_ble/ducky_typer.h index 593f3035b4..bb5aeb4f94 100644 --- a/src/modules/badusb_ble/ducky_typer.h +++ b/src/modules/badusb_ble/ducky_typer.h @@ -15,7 +15,7 @@ extern HIDInterface *hid_usb; extern HIDInterface *hid_ble; -extern uint8_t _Ask_for_restart; +extern int activeBLEInstances; struct DuckyCommand; struct DuckyCommandLookup; @@ -25,7 +25,8 @@ struct DuckyCombination; void ducky_setup(HIDInterface *&hid, bool ble = false); // Setup the keyboard for badUSB or badBLE -void ducky_startKb(HIDInterface *&hid, bool ble); +// functionId: 0=Keyboard, 1=Media, 2=BadUSB, 3=Presenter +void ducky_startKb(HIDInterface *&hid, bool ble, int functionId = 0); // Parses a file to run in the badUSB void key_input(FS fs, const String &bad_script, HIDInterface *hid); @@ -57,5 +58,11 @@ bool handlePauseResume(); // Presenter mode - press button to advance slides void PresenterMode(HIDInterface *&hid, bool ble = true); +// Shared cleanup for ducky_typer BLE functions - cleans a specific instance +void cleanupDuckyBLE(HIDInterface *&hid); + +// Double cleanup with cooling delay +void safeCleanupDuckyBLE(HIDInterface *&hid); + #endif #endif diff --git a/src/modules/ble/BLE_Suite.cpp b/src/modules/ble/BLE_Suite.cpp index 7afa2bed60..73014d51aa 100644 --- a/src/modules/ble/BLE_Suite.cpp +++ b/src/modules/ble/BLE_Suite.cpp @@ -2,7 +2,7 @@ * BLE Suite v3.1 - Complete BLE attack and analysis toolkit * Author: Ninja-jr * Version: 3.1 - * Last Updated: 19/07/2026 + * Last Updated: 21/07/2026 * * Contains: Vulnerability scanning, HID attacks, FastPair exploits, * HFP attacks, Audio attacks, DuckyScript injection, @@ -28,10 +28,9 @@ #include //============================================================================= -// NimBLE Version Detection - Must match ble_common.h +// NimBLE Version Detection //============================================================================= -// Detect NimBLE 2.x by checking for features only available in v2+ #if defined(NIMBLE_VERSION) #if NIMBLE_VERSION >= 20000 #define NIMBLE_V2_PLUS 1 @@ -46,7 +45,6 @@ #define NIMBLE_V2_PLUS 1 #endif -// If none of the above matched, default to v1 behavior (safe fallback) #ifndef NIMBLE_V2_PLUS #define NIMBLE_V2_PLUS 0 #endif @@ -151,7 +149,7 @@ void ScannerData::addDevice( deviceTypes.push_back(type); foundCount++; dataVersion++; - + if (snapshotCache) { delete snapshotCache; snapshotCache = nullptr; @@ -165,13 +163,13 @@ DeviceSnapshot* ScannerData::getSnapshot() { if (snapshotCache && (millis() - cacheTimestamp) < 1000) { return snapshotCache; } - + if (xSemaphoreTake(mutex, 50 / portTICK_PERIOD_MS)) { if (snapshotCache) { delete snapshotCache; snapshotCache = nullptr; } - + snapshotCache = new DeviceSnapshot(); snapshotCache->version = dataVersion; snapshotCache->count = deviceAddresses.size(); @@ -182,7 +180,7 @@ DeviceSnapshot* ScannerData::getSnapshot() { snapshotCache->fastPair = deviceFastPair; snapshotCache->hfp = deviceHasHFP; snapshotCache->types = deviceTypes; - + cacheTimestamp = millis(); xSemaphoreGive(mutex); return snapshotCache; @@ -217,7 +215,7 @@ void ScannerData::clear() { deviceTypes.clear(); foundCount = 0; dataVersion++; - + if (snapshotCache) { delete snapshotCache; snapshotCache = nullptr; @@ -310,7 +308,7 @@ const FastPairModelInfo fastpair_models[] = { }; //============================================================================= -// BLE State Manager +// BLE State Manager - FIXED: Always init, handle deinit'd stack //============================================================================= bool BLEStateManager::initBLE(const String &name, int powerLevel) { @@ -325,8 +323,6 @@ bool BLEStateManager::initBLE(const String &name, int powerLevel) { } } - if (bleInitialized) deinitBLE(true); - if (!radioHasMemForBle()) { displayError("Low RAM: free WiFi/SD first", true); return false; @@ -3282,7 +3278,7 @@ String getScriptFromUser() { } //============================================================================= -// v3.1: FastPair Exploit Engine with Samsung Detection +// FastPair Exploit Engine //============================================================================= bool FastPairExploitEngine::smartExploit(NimBLEAddress target) { @@ -3315,11 +3311,9 @@ std::vector FastPairExploitEngine::scanForFastPairDevices(in pScan->setInterval(97); pScan->setWindow(67); - // Use the same version detection as ble_common.h #if NIMBLE_V2_PLUS NimBLEScanResults results = pScan->getResults(duration * 1000, false); #else - // NimBLE 1.x: start returns NimBLEScanResults directly NimBLEScanResults results = pScan->start(duration, false); #endif @@ -3836,7 +3830,7 @@ void FastPairExploitEngine::generateRandomMac(uint8_t *mac) { } //============================================================================= -// v3.1: BLE Sniffer - Enhanced with more manufacturer IDs +// v3.1: BLE Sniffer - FIXED: Always init //============================================================================= struct SnifferPacket { @@ -3915,6 +3909,9 @@ static String parseManufacturerData(const std::vector &payload) { } void BLE_Sniffer() { + // FIX: Always init - handles case where stack was deinit'd by another module + BLEStateManager::initBLE("BruceSniffer", ESP_PWR_LVL_P9); + drawMainBorderWithTitle("BLE SNIFFER"); padprintln(""); padprintln("Press [SEL] to start/stop capture"); @@ -4168,7 +4165,7 @@ void BLE_Sniffer() { } //============================================================================= -// Target Selection Functions - Uses snapshot for safe data access +// Target Selection Functions //============================================================================= String selectTargetFromScan(const char *title) { @@ -4177,21 +4174,20 @@ String selectTargetFromScan(const char *title) { displayError("Low memory, scan may be unstable", true); // Don't return - let the user decide } - + // DO NOT clear scannerData here - it persists between operations g_selectedDevice.address = ""; g_selectedDevice.name = ""; - + bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); #if !defined(LITE_VERSION) bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; #endif - if (!bleWasActiveBefore) { - if (!BLEStateManager::initBLE("Bruce-Scanner", ESP_PWR_LVL_P9)) { - displayError("Failed to init BLE"); - return ""; - } + // FIX: Always call initBLE - it handles the case where stack was deinit'd + if (!BLEStateManager::initBLE("Bruce-Scanner", ESP_PWR_LVL_P9)) { + displayError("Failed to init BLE"); + return ""; } if (g_pBLEScan == nullptr) { @@ -4229,11 +4225,9 @@ String selectTargetFromScan(const char *title) { tft.setCursor(20, 60); tft.print("Scanning for devices..."); - // Use fixed scan times int activeScanTime = ACTIVE_SCAN_TIME; int passiveScanTime = PASSIVE_SCAN_TIME; - // If memory is very low, use reduced scan times if (heap_caps_get_free_size(MALLOC_CAP_DEFAULT) < 15000) { activeScanTime = 3; passiveScanTime = 3; @@ -4248,7 +4242,6 @@ String selectTargetFromScan(const char *title) { #if NIMBLE_V2_PLUS BLEScanResults activeResults = g_pBLEScan->getResults(activeScanTime * 1000, false); #else - // NimBLE 1.x API: start returns NimBLEScanResults BLEScanResults activeResults = g_pBLEScan->start(activeScanTime, false); #endif @@ -4259,7 +4252,7 @@ String selectTargetFromScan(const char *title) { NimBLEAdvertisedDevice *device = activeResults.getDevice(i); #endif if (!device) continue; - + String address = String(device->getAddress().toString().c_str()); String name = String(device->getName().c_str()); if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { @@ -4303,7 +4296,7 @@ String selectTargetFromScan(const char *title) { NimBLEAdvertisedDevice *device = passiveResults.getDevice(i); #endif if (!device) continue; - + String address = String(device->getAddress().toString().c_str()); String name = String(device->getName().c_str()); if (name.isEmpty() || name == "(null)" || name == "null" || name == "NULL") { @@ -4336,13 +4329,11 @@ String selectTargetFromScan(const char *title) { return ""; } - // Stop the scan but DON'T clear results yet - we need them for display if (g_pBLEScan) { g_pBLEScan->stop(); g_bleScanActive = false; } - // Get snapshot of discovered devices DeviceSnapshot* snapshot = scannerData.getSnapshot(); if (!snapshot || snapshot->count == 0) { tft.fillScreen(TFT_YELLOW); @@ -4363,8 +4354,7 @@ String selectTargetFromScan(const char *title) { } size_t deviceCount = snapshot->count; - - // Sort with manual swap for vector + for (size_t i = 0; i < deviceCount - 1; i++) { for (size_t j = i + 1; j < deviceCount; j++) { bool swapNeeded = false; @@ -4372,27 +4362,25 @@ String selectTargetFromScan(const char *title) { else if (snapshot->fastPair[j] == snapshot->fastPair[i] && snapshot->rssi[j] > snapshot->rssi[i]) swapNeeded = true; - + if (swapNeeded) { std::swap(snapshot->names[i], snapshot->names[j]); std::swap(snapshot->addresses[i], snapshot->addresses[j]); std::swap(snapshot->rssi[i], snapshot->rssi[j]); - - // Manual swap for vector proxy references + bool tempFast = snapshot->fastPair[i]; snapshot->fastPair[i] = snapshot->fastPair[j]; snapshot->fastPair[j] = tempFast; - + bool tempHfp = snapshot->hfp[i]; snapshot->hfp[i] = snapshot->hfp[j]; snapshot->hfp[j] = tempHfp; - + std::swap(snapshot->types[i], snapshot->types[j]); } } } - // UI selection loop int deviceItemHeight = 30, menuStartY = 60; int maxVisibleDevices = (tftHeight - 45 - menuStartY) / deviceItemHeight; if (maxVisibleDevices < 1) maxVisibleDevices = 1; @@ -4486,27 +4474,25 @@ String selectTargetFromScan(const char *title) { } else if (check(SelPress)) { String selectedMAC = snapshot->addresses[selectedIdx]; String selectedName = snapshot->names[selectedIdx]; - + selectedMAC.trim(); selectedMAC.toUpperCase(); - + g_selectedDevice.address = selectedMAC; g_selectedDevice.name = selectedName; g_selectedDevice.rssi = snapshot->rssi[selectedIdx]; g_selectedDevice.hasFastPair = snapshot->fastPair[selectedIdx]; g_selectedDevice.hasHFP = snapshot->hfp[selectedIdx]; g_selectedDevice.deviceType = snapshot->types[selectedIdx]; - + String returnMac = selectedMAC; returnMac.trim(); - // DO NOT clear scannerData here - keep it for potential reuse return returnMac; } delay(50); } - // DO NOT clear scannerData here - keep it for potential reuse return ""; } @@ -4635,11 +4621,11 @@ NimBLEAddress parseAddress(const String &addressInfo) { String cleanAddr = addressInfo; cleanAddr.trim(); cleanAddr.toUpperCase(); - + if (cleanAddr.endsWith(":0")) { cleanAddr = cleanAddr.substring(0, cleanAddr.length() - 2); } - + int start = -1; int colonCount = 0; for (int i = 0; i < cleanAddr.length(); i++) { @@ -4676,7 +4662,7 @@ NimBLEAddress parseAddress(const String &addressInfo) { } } } - + for (int i = 0; i < addressInfo.length() - 17; i++) { String substr = addressInfo.substring(i, i + 17); bool valid = true; @@ -4699,13 +4685,13 @@ NimBLEAddress parseAddress(const String &addressInfo) { return NimBLEAddress(std::string(substr.c_str()), BLE_ADDR_PUBLIC); } } - + Serial.println("[WARN] Invalid MAC address format: " + addressInfo); return NimBLEAddress(std::string(""), BLE_ADDR_PUBLIC); } //============================================================================= -// Attack Functions - Updated to use SelectedDevice +// Attack Functions //============================================================================= void runHFPVulnerabilityTest(NimBLEAddress target) { @@ -4933,7 +4919,7 @@ void runAdvertisingSpam(NimBLEAddress target) { } //============================================================================= -// Menu System - Clear data ONLY at entry and exit +// Menu System //============================================================================= static bool welcomeShown = false; @@ -4961,12 +4947,19 @@ void showWelcomeScreen() { welcomeShown = true; } +//============================================================================= +// BleSuiteMenu - FIXED: Init ONCE at entry +//============================================================================= + void BleSuiteMenu() { + // FIX: Init BLE stack ONCE when entering the suite + BLEStateManager::initBLE("Bruce-BLESuite", ESP_PWR_LVL_P9); + // Clear data when entering the menu scannerData.clear(); g_selectedDevice.address = ""; g_selectedDevice.name = ""; - + showWelcomeScreen(); const int MENU_ITEMS = 12; @@ -5046,6 +5039,9 @@ void BleSuiteMenu() { scannerData.clear(); g_selectedDevice.address = ""; g_selectedDevice.name = ""; + + // Deinit BLE stack when exiting the suite + BLEStateManager::deinitBLE(true); return; } if (check(PrevPress)) { @@ -5063,7 +5059,6 @@ void BleSuiteMenu() { if (check(SelPress)) { if (selected == MENU_ITEMS - 1) { BLE_Sniffer(); - // Don't clear data - keep it for the menu } else { executeAttackWithTargetScan(selected); } @@ -5095,6 +5090,9 @@ const char *getScanTitle(int attackIndex) { } void executeAttackWithTargetScan(int attackIndex) { + // FIX: Ensure BLE is initialized for the attack + BLEStateManager::initBLE("Bruce-Attack", ESP_PWR_LVL_P9); + String targetInfo = selectTargetFromScan(getScanTitle(attackIndex)); if (targetInfo.isEmpty()) return; @@ -5119,14 +5117,12 @@ void executeAttackWithTargetScan(int attackIndex) { showAttackProgress("Attack complete. Press any key to continue...", TFT_GREEN); while (!check(EscPress) && !check(SelPress) && !check(PrevPress) && !check(NextPress)) delay(50); - - // Clean up scan state but DON'T clear scannerData + if (g_pBLEScan) { g_pBLEScan->stop(); g_pBLEScan->clearResults(); g_bleScanActive = false; } - // Keep scannerData and g_selectedDevice for potential reuse delay(100); } @@ -5223,7 +5219,7 @@ int showSubMenu(const char *title, const char *options[], int optionCount) { } //============================================================================= -// Attack Submenus - Updated to use SelectedDevice +// Attack Submenus //============================================================================= void showFastPairSubMenu(NimBLEAddress target, SelectedDevice deviceInfo) { diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index d529eb3bc5..09e166f09f 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -15,7 +15,7 @@ #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" BLEScan *pBLEScan = nullptr; -int scanTime = SCANTIME; // In seconds +int scanTime = SCANTIME; bool bleNotifyRetry(NimBLECharacteristic *chr, const uint8_t *value, size_t length, uint8_t retries) { if (chr == nullptr) return false; @@ -80,10 +80,6 @@ void ble_info(const String &name, const String &address, const String &signal) { } } -//============================================================================= -// NimBLE Callbacks - Version-specific with proper lifetime management -//============================================================================= - #if NIMBLE_V2_PLUS class AdvertisedDeviceCallbacks : public NimBLEScanCallbacks {}; #else @@ -101,7 +97,6 @@ void stopBLEStack() { if (pBLEScan) { pBLEScan->stop(); pBLEScan->clearResults(); - // Don't delete pBLEScan - it's owned by BLEDevice pBLEScan = nullptr; } @@ -147,16 +142,15 @@ bool ble_scan_setup() { RAM_LOG("ble-scan pre-init"); - if (!is_ble_inited) { - if (!radioHasMemForBle()) { - displayError("Low RAM: free WiFi/SD first", true); - returnToMenu = true; - return false; - } - // Use a minimal name to save RAM - BLEDevice::init(""); - is_ble_inited = true; + // FIX: Always try to init - if already init'd, it's a no-op + if (!radioHasMemForBle()) { + displayError("Low RAM: free WiFi/SD first", true); + returnToMenu = true; + return false; } + + BLEDevice::init(""); + is_ble_inited = true; RAM_LOG("ble-scan post-init"); pBLEScan = BLEDevice::getScan(); @@ -197,7 +191,7 @@ void ble_scan() { options = {}; options.reserve(MAX_DISPLAY_DEVICES); - + bool bleWasActiveBefore = BLEConnected || (BLEDevice::getServer() != nullptr); #if !defined(LITE_VERSION) bleWasActiveBefore = bleWasActiveBefore || BLEStateManager::isBLEActive() || BLEStateManager::getActiveClientCount() > 0; @@ -208,24 +202,20 @@ void ble_scan() { return; } - // Clear previous results before scanning pBLEScan->clearResults(); - // Use a try-catch block to handle potential exceptions try { #if NIMBLE_V2_PLUS BLEScanResults foundDevices = pBLEScan->getResults(scanTime * 1000, false); #else - // NimBLE 1.x: start() returns results directly, time in seconds BLEScanResults foundDevices = pBLEScan->start(scanTime, false); #endif int deviceCount = foundDevices.getCount(); int processedCount = 0; - - // Cap the number of devices to prevent memory issues + int maxToProcess = min(deviceCount, MAX_DISPLAY_DEVICES); - + for (int i = 0; i < maxToProcess && processedCount < MAX_DISPLAY_DEVICES; i++) { #if NIMBLE_V2_PLUS const NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); @@ -233,7 +223,7 @@ void ble_scan() { NimBLEAdvertisedDevice *advertisedDevice = foundDevices.getDevice(i); #endif if (!advertisedDevice) continue; - + String bt_title; String bt_name; String bt_address; @@ -242,11 +232,11 @@ void ble_scan() { bt_name = advertisedDevice->getName().c_str(); bt_address = advertisedDevice->getAddress().toString().c_str(); bt_signal = String(advertisedDevice->getRSSI()); - + if (bt_name.isEmpty()) bt_name = ""; bt_title = bt_name; if (bt_title.isEmpty()) bt_title = bt_address; - + if (options.size() < MAX_DISPLAY_DEVICES) { options.emplace_back(bt_title.c_str(), [=]() { ble_info(bt_name, bt_address, bt_signal); @@ -255,7 +245,6 @@ void ble_scan() { } } - // Show "and more" if we hit the limit if (options.size() >= MAX_DISPLAY_DEVICES) { options.emplace_back("... and more devices", nullptr); } @@ -264,14 +253,11 @@ void ble_scan() { pBLEScan->clearResults(); return; } - - // Stop scan + if (pBLEScan) { pBLEScan->stop(); - // Don't clear results here - we need them for display } - - // Only stop BLE if it wasn't active before and we're done with it + if (!bleWasActiveBefore) { #if !defined(LITE_VERSION) if (!BLEStateManager::isBLEActive()) { @@ -281,7 +267,7 @@ void ble_scan() { stopBLEStack(); #endif } - + if (!options.empty()) { addOptionToMainMenu(); loopOptions(options); @@ -300,7 +286,7 @@ bool initBLEServer() { BLEDevice::init(blename.c_str()); is_ble_inited = true; } - + pServer = BLEDevice::createServer(); if (!pServer) { displayError("Failed to create BLE server"); @@ -313,7 +299,7 @@ bool initBLEServer() { displayError("Failed to create BLE service"); return false; } - + pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_RX_UUID, NIMBLE_PROPERTY::NOTIFY); if (!pTxCharacteristic) { displayError("Failed to create TX characteristic"); @@ -331,10 +317,7 @@ bool initBLEServer() { pRxCharacteristic->setCallbacks(new MyCallbacks()); #if NIMBLE_V2_PLUS - // NimBLE 2.x: Services start automatically when server starts - // No need to call pService->start() #else - // NimBLE 1.x: Need to call pService->start() pService->start(); #endif diff --git a/src/modules/ble/ble_spam.cpp b/src/modules/ble/ble_spam.cpp index f8be410a19..ba292173ca 100644 --- a/src/modules/ble/ble_spam.cpp +++ b/src/modules/ble/ble_spam.cpp @@ -31,6 +31,9 @@ #include "ble_spam.h" #include "core/display.h" #include "core/mykeyboard.h" +#include "core/radio_mem.h" +#include "core/sd_functions.h" +#include "core/utils.h" #ifdef CONFIG_BT_NIMBLE_ENABLED #if __has_include() #define NIMBLE_V2_PLUS 1 @@ -363,11 +366,29 @@ BLEAdvertisementData GetUniversalAdvertisementData(EBLEPayloadType Type, const S return AdvData; } +// ============================================================================ +// iBeacon - FIXED: Proper button handling + stack init/deinit +// ============================================================================ + void ibeacon(const char *DeviceName, const char *BEACON_UUID, int ManufacturerId) { + // CRITICAL: Clear any pending button presses before starting + delay(50); + while (check(AnyKeyPress)) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + // Reset all button states + SelPress = false; + EscPress = false; + PrevPress = false; + NextPress = false; + AnyKeyPress = false; + uint8_t macAddr[6]; generateRandomMac(macAddr); esp_iface_mac_addr_set(macAddr, ESP_MAC_BT); + // FIX: Always init - if already init'd, it's a no-op + // This handles the case where another module deinit'd the stack BLEDevice::init(DeviceName); vTaskDelay(5 / portTICK_PERIOD_MS); esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, MAX_TX_POWER); @@ -391,22 +412,37 @@ void ibeacon(const char *DeviceName, const char *BEACON_UUID, int ManufacturerId padprintln(""); padprintln("Press Any key to STOP."); - while (!check(AnyKeyPress)) { + // Main loop - with proper button handling + bool running = true; + while (running) { pAdvertising->start(); - Serial.println("Advertizing started..."); - vTaskDelay(20 / portTICK_PERIOD_MS); + Serial.println("Advertising started..."); + + // Check for button press with debounce + for (int i = 0; i < 20; i++) { + vTaskDelay(1 / portTICK_PERIOD_MS); + if (check(AnyKeyPress) || check(EscPress) || check(SelPress)) { + running = false; + break; + } + } + pAdvertising->stop(); vTaskDelay(5 / portTICK_PERIOD_MS); - Serial.println("Advertizing stop"); + Serial.println("Advertising stop"); + + esp_task_wdt_reset(); } -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else + // Deinit the BLE stack - self-contained module BLEDevice::deinit(); -#endif + Serial.println("[iBeacon] BLE stack deinitialized"); } +// ============================================================================ +// BLE Spam Attack Types and Configuration +// ============================================================================ + enum BleSpamAttackType { BLE_SPAM_ATTACK_APPLE_PAIRING, BLE_SPAM_ATTACK_APPLE_ACTION, @@ -1417,6 +1453,10 @@ bleSpamSelectAdvertisement(BleSpamRunState &state, BleSpamAttackType attackType, return &state.working_advertisement; } +// ============================================================================ +// BLE Spam Advertiser Functions - FIXED: Always init/deinit +// ============================================================================ + static void bleSpamInitAdvertiser( BleSpamRunState &state, const BleSpamConfig &config, const uint8_t *mac, bool resetStats ) { @@ -1427,6 +1467,7 @@ static void bleSpamInitAdvertiser( state.mac_initialized = false; } + // FIX: Always init - if already init'd, it's a no-op BLEDevice::init(""); vTaskDelay(5 / portTICK_PERIOD_MS); @@ -1458,11 +1499,8 @@ static void bleSpamDeinitAdvertiser() { vTaskDelay(5 / portTICK_PERIOD_MS); pAdvertising = nullptr; } -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else + // FIX: Always deinit - self-contained module BLEDevice::deinit(); -#endif #ifdef CONFIG_BT_NIMBLE_ENABLED // NimBLEDevice::m_ownAddrType is a static class member that survives // deinit()/init() cycles. bleSpamRestartAdvertiserForMac() flips it to diff --git a/src/modules/ble_api/ble_api.cpp b/src/modules/ble_api/ble_api.cpp index 68dbd4ba2e..9d4c070dde 100644 --- a/src/modules/ble_api/ble_api.cpp +++ b/src/modules/ble_api/ble_api.cpp @@ -45,11 +45,7 @@ void BLE_API::update_mtu(uint16_t mtu) { void BLE_API::end() { battery_service.end(); serial_service.end(); -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else BLEDevice::deinit(); -#endif serialDevice = &USBserial; } #endif diff --git a/src/modules/gps/wardriving.cpp b/src/modules/gps/wardriving.cpp index 3bf602a08f..e8ba0fabbc 100644 --- a/src/modules/gps/wardriving.cpp +++ b/src/modules/gps/wardriving.cpp @@ -104,11 +104,7 @@ bool Wardriving::begin_gps() { void Wardriving::end() { if (scanWiFi) wifiDisconnect(); if (scanBLE) { -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else BLEDevice::deinit(true); -#endif pBLEScan = nullptr; bleInitialized = false; } diff --git a/src/modules/rfid/PN532KillerTools.cpp b/src/modules/rfid/PN532KillerTools.cpp index f149202f20..c5c30489fa 100644 --- a/src/modules/rfid/PN532KillerTools.cpp +++ b/src/modules/rfid/PN532KillerTools.cpp @@ -788,11 +788,7 @@ bool PN532KillerTools::disableBleDataTransfer() { if (pServer) { pServer->getAdvertising()->stop(); -#if defined(CONFIG_IDF_TARGET_ESP32C5) - esp_bt_controller_deinit(); -#else BLEDevice::deinit(); -#endif } pServer = nullptr;