diff --git a/src/core/main_menu.cpp b/src/core/main_menu.cpp index 1fb02b376..e6e3d409a 100644 --- a/src/core/main_menu.cpp +++ b/src/core/main_menu.cpp @@ -6,6 +6,7 @@ MainMenu::MainMenu() { _menuItems = { &wifiMenu, + &bw16Menu, &bleMenu, #if !defined(LITE_VERSION) ðernetMenu, diff --git a/src/core/main_menu.h b/src/core/main_menu.h index 705e967c5..76f0666cf 100644 --- a/src/core/main_menu.h +++ b/src/core/main_menu.h @@ -4,6 +4,7 @@ #include #include "menu_items/BleMenu.h" +#include "menu_items/Bw16Menu.h" #include "menu_items/ClockMenu.h" #include "menu_items/ConfigMenu.h" #include "menu_items/ConnectMenu.h" @@ -35,6 +36,7 @@ class MainMenu { RFMenu rfMenu; ScriptsMenu scriptsMenu; WifiMenu wifiMenu; + Bw16Menu bw16Menu; #if !defined(LITE_VERSION) LoRaMenu loraMenu; EthernetMenu ethernetMenu; diff --git a/src/core/menu_items/Bw16Menu.cpp b/src/core/menu_items/Bw16Menu.cpp new file mode 100644 index 000000000..600eb972d --- /dev/null +++ b/src/core/menu_items/Bw16Menu.cpp @@ -0,0 +1,135 @@ +#include "Bw16Menu.h" +#include "core/display.h" +#include "core/settings.h" +#include "core/utils.h" + +BW16Tool Bw16Menu::tool; + +void Bw16Menu::optionsMenu() { + tool.setup(); + + options = { + {"Bad Ble", [&]() { tool.attckbleMenu(); }}, + {"Scan WiFi", [&]() { tool.scanWifi(); } }, + {"Select WiFi", [&]() { tool.selectWifi(); } }, + {"Attacks", [&]() { attackMenu(); } }, + {"Config", [this]() { configMenu(); } } + }; + + addOptionToMainMenu(); + String txt = "BW16 |TX:" + String(bruceConfigPins.gps_bus.tx) + + " |RX:" + String(bruceConfigPins.gps_bus.rx) + " | (" + String(bruceConfigPins.gpsBaudrate) + + " bps)"; + loopOptions(options, MENU_TYPE_SUBMENU, txt.c_str()); +} + +void Bw16Menu::attackMenu() { + options = { + {"Deauth Selected", [&]() { tool.attackSelected(); }}, + {"Deauth ALL", [&]() { tool.attackAll(); } }, + {"Beacon Spam", [&]() { tool.beaconSpam(); } }, + {"Beacon List", [&]() { tool.beaconList(); } }, + {"Beacon & Deauth", [&]() { tool.beaconDeauth(); } }, + {"Evil Portal", [&]() { tool.evilPortal(); } }, + {"Back", [this]() { optionsMenu(); } } + }; + loopOptions(options, MENU_TYPE_SUBMENU, "BW16 Attacks"); +} + +void Bw16Menu::configMenu() { + options = { + {"Baudrate", setGpsBaudrateMenu }, + {"UART Pins", [=]() { setUARTPinsMenu(bruceConfigPins.gps_bus); }}, + {"Back", [this]() { optionsMenu(); } } + }; + loopOptions(options, MENU_TYPE_SUBMENU, "BW16 Config"); +} + +void Bw16Menu::drawIcon(float scale) { + clearIconArea(); + uint16_t color = bruceConfig.priColor; + uint16_t bg = bruceConfig.bgColor; + + int minDim = min(tftWidth, tftHeight); + + int basePx; + if (minDim <= 135) { + basePx = 4; + } else if (minDim <= 240) { + basePx = 5; + } else if (minDim <= 320) { + basePx = 6; + } else { + basePx = 7; + } + + float adjustedScale = scale * (minDim / 240.0f); + adjustedScale = constrain(adjustedScale, 0.8f, 1.2f); + + int px = max(2, (int)(basePx * adjustedScale)); + int totalW = 24 * px; + int totalH = 20 * px; + int x = iconCenterX - totalW / 2; + int y = iconCenterY - totalH / 2; + + tft.fillRect(x + 2 * px, y, totalW - 4 * px, px, color); + tft.fillRect(x + 2 * px, y + totalH - px, totalW - 4 * px, px, color); + tft.fillRect(x, y + 2 * px, px, totalH - 4 * px, color); + tft.fillRect(x + totalW - px, y + 2 * px, px, totalH - 4 * px, color); + + tft.fillRect(x + px, y + px, px, px, color); + tft.fillRect(x + totalW - 2 * px, y + px, px, px, color); + tft.fillRect(x + px, y + totalH - 2 * px, px, px, color); + tft.fillRect(x + totalW - 2 * px, y + totalH - 2 * px, px, px, color); + + int wy = y + 3 * px; + int wx = x + 2 * px; + + tft.fillRect(wx, wy, px, 5 * px, color); + tft.fillRect(wx + 2 * px, wy + 2 * px, px, 3 * px, color); + tft.fillRect(wx + 4 * px, wy + 2 * px, px, 3 * px, color); + tft.fillRect(wx + 6 * px, wy, px, 5 * px, color); + tft.fillRect(wx + 1 * px, wy + 5 * px, px, px, color); + tft.fillRect(wx + 3 * px, wy + 4 * px, px, px, color); + tft.fillRect(wx + 5 * px, wy + 5 * px, px, px, color); + + int ix = wx + 8 * px; + tft.fillRect(ix, wy, 3 * px, px, color); + tft.fillRect(ix + px, wy + px, px, 3 * px, color); + tft.fillRect(ix, wy + 4 * px, 3 * px, px, color); + + int fx = ix + 4 * px; + tft.fillRect(fx, wy, 4 * px, px, color); + tft.fillRect(fx + px, wy + px, px, 4 * px, color); + tft.fillRect(fx, wy + 4 * px, px, px, color); + tft.fillRect(fx + 2 * px, wy + 2 * px, 2 * px, px, color); + tft.drawPixel(fx + px, wy + 2 * px, bg); + + int ix2 = fx + 5 * px; + tft.fillRect(ix2, wy, 3 * px, px, color); + tft.fillRect(ix2 + px, wy + px, px, 3 * px, color); + tft.fillRect(ix2, wy + 4 * px, 3 * px, px, color); + + int by = y + 11 * px; + int x5 = x + 5 * px; + + tft.fillRect(x5, by, 7 * px, px, color); + tft.fillRect(x5, by + px, 2 * px, 2 * px, color); + tft.fillRect(x5, by + 3 * px, 6 * px, px, color); + tft.fillRect(x5 + 5 * px, by + 4 * px, 2 * px, 2 * px, color); + tft.fillRect(x5 + px, by + 6 * px, 5 * px, px, color); + tft.fillRect(x5, by + 5 * px, px, px, color); + tft.drawPixel(x5 + 6 * px, by + 3 * px, bg); + tft.drawPixel(x5 + 6 * px, by + 6 * px, bg); + + int xG = x5 + 8 * px; + tft.fillRect(xG + px, by, 5 * px, px, color); + tft.fillRect(xG, by + px, 2 * px, 5 * px, color); + tft.fillRect(xG + px, by + 6 * px, 5 * px, px, color); + tft.fillRect(xG + 5 * px, by + 3 * px, 2 * px, 3 * px, color); + tft.fillRect(xG + 3 * px, by + 3 * px, 2 * px, px, color); + tft.fillRect(xG + 6 * px, by + px, px, px, color); + tft.drawPixel(xG, by, bg); + tft.drawPixel(xG, by + 6 * px, bg); + tft.drawPixel(xG + 6 * px, by + 6 * px, bg); +} diff --git a/src/core/menu_items/Bw16Menu.h b/src/core/menu_items/Bw16Menu.h new file mode 100644 index 000000000..181bef0e7 --- /dev/null +++ b/src/core/menu_items/Bw16Menu.h @@ -0,0 +1,22 @@ +#ifndef __BW16_MENU_H__ +#define __BW16_MENU_H__ + +#include "MenuItemInterface.h" +#include "modules/bw16/bw16.h" + +class Bw16Menu : public MenuItemInterface { +public: + Bw16Menu() : MenuItemInterface("BW16") {} + + void optionsMenu(void); + void attackMenu(); + void configMenu(void); + void drawIcon(float scale); + bool hasTheme() { return bruceConfig.theme.bw16; } + String themePath() { return bruceConfig.theme.paths.bw16; } + +private: + static BW16Tool tool; +}; + +#endif diff --git a/src/core/menu_items/RFMenu.cpp b/src/core/menu_items/RFMenu.cpp index 23b3416f6..76b7a964c 100644 --- a/src/core/menu_items/RFMenu.cpp +++ b/src/core/menu_items/RFMenu.cpp @@ -4,6 +4,7 @@ #include "core/utils.h" #include "modules/rf/record.h" #include "modules/rf/rf_bruteforce.h" +#include "modules/rf/rf_chat.h" // ← RF Chat #include "modules/rf/rf_jammer.h" #include "modules/rf/rf_listen.h" #include "modules/rf/rf_scan.h" @@ -27,6 +28,7 @@ void RFMenu::optionsMenu() { {"Listen", rf_listen }, // dev_eclipse #endif {"Bruteforce", rf_bruteforce }, // dev_eclipse + {"RF Chat", rf_chat }, // CC1101 p2p chat {"Jammer Itmt", [=]() { RFJammer(false); }}, #endif {"Jammer Full", [=]() { RFJammer(true); } }, @@ -36,7 +38,7 @@ void RFMenu::optionsMenu() { delay(200); String txt = "Radio Frequency"; - if (bruceConfigPins.rfModule == CC1101_SPI_MODULE) txt += " (CC1101)"; // Indicates if CC1101 is connected + if (bruceConfigPins.rfModule == CC1101_SPI_MODULE) txt += " (CC1101)"; else txt += " Tx: " + String(bruceConfigPins.rfTx) + " Rx: " + String(bruceConfigPins.rfRx); loopOptions(options, MENU_TYPE_SUBMENU, txt.c_str()); @@ -44,11 +46,13 @@ void RFMenu::optionsMenu() { void RFMenu::configMenu() { options = { - {"RF TX Pin", lambdaHelper(gsetRfTxPin, true)}, - {"RF RX Pin", lambdaHelper(gsetRfRxPin, true)}, - {"RF Module", setRFModuleMenu}, - {"RF Frequency", setRFFreqMenu}, - {"Back", [this]() { optionsMenu(); }}, + {"RF TX Pin", lambdaHelper(gsetRfTxPin, true)}, + {"RF RX Pin", lambdaHelper(gsetRfRxPin, true)}, + {"RF Module", setRFModuleMenu }, + {"RF Frequency", setRFFreqMenu }, + {"Chat Username", rf_chat_change_username }, // RF Chat nickname + {"Chat Frequency", rf_chat_change_freq }, // RF Chat frequency + {"Back", [this]() { optionsMenu(); } }, }; loopOptions(options, MENU_TYPE_SUBMENU, "RF Config"); diff --git a/src/core/theme.cpp b/src/core/theme.cpp index 68d6f416c..d08694444 100644 --- a/src/core/theme.cpp +++ b/src/core/theme.cpp @@ -42,6 +42,7 @@ bool BruceTheme::openThemeFile(FS *fs, String filepath, bool overwriteConfigSett ThemeEntry entries[] = { {"wifi", &theme.wifi, theme.paths.wifi }, + {"bw16", &theme.bw16, theme.paths.bw16 }, {"ble", &theme.ble, theme.paths.ble }, {"ethernet", &theme.ethernet, theme.paths.ethernet }, {"rf", &theme.rf, theme.paths.rf }, diff --git a/src/core/theme.h b/src/core/theme.h index d05faea77..2e214ca48 100644 --- a/src/core/theme.h +++ b/src/core/theme.h @@ -8,6 +8,7 @@ struct themeFiles { String wifi = ""; + String bw16 = ""; String ble = ""; String ethernet = ""; String rf = ""; @@ -34,6 +35,7 @@ struct themeInfo { bool border = true; bool label = true; bool wifi = false; + bool bw16 = false; bool ble = false; bool ethernet = false; bool rf = false; diff --git a/src/modules/BW16/bw16.h b/src/modules/BW16/bw16.h new file mode 100644 index 000000000..1efccab76 --- /dev/null +++ b/src/modules/BW16/bw16.h @@ -0,0 +1,63 @@ +#ifndef __BW16_TOOL_H__ +#define __BW16_TOOL_H__ + +#include +#include +#include +#include + +struct BW16Network { + int index; + String ssid; + String bssid; + int channel; + int rssi; +}; + +struct BW16Group { + String ssid; + std::vector aps; + int maxRssi = -127; + bool has24 = false; + bool has5 = false; +}; + +class BW16Tool { +public: + BW16Tool(); + ~BW16Tool(); + + void setup(); + void scanWifi(); + void selectWifi(); + void attackWifiMenu(); + + void attackSelected(); + void attackAll(); + void beaconSpam(); + void beaconList(); + void beaconDeauth(); + void evilPortal(); + void attckbleMenu(); + void send_at_ble(String type); + String readSerialLine(); + +private: + bool isBW16Active = false; + bool rxPinReleased = false; + std::vector all_networks; + std::vector grouped_networks; + std::vector selected_bssids; + + bool begin_bw16(); + void end(); + void releasePins(); + void restorePins(); + void display_banner(); + void send_attack_command(String type); + void printCustomLog(String msg); + void centerString(String text); + void save_captured_creds(const std::vector &creds); +}; + +#endif diff --git a/src/modules/rf/rf_chat.cpp b/src/modules/rf/rf_chat.cpp new file mode 100644 index 000000000..2532eb4a1 --- /dev/null +++ b/src/modules/rf/rf_chat.cpp @@ -0,0 +1,428 @@ +#if !defined(LITE_VERSION) +#include "rf_chat.h" +#include "core/config.h" +#include "core/configPins.h" +#include "core/display.h" +#include "core/mykeyboard.h" +#include "core/utils.h" +#include "globals.h" +#include +#include +#include +#include +#include +#include + +// ───────────────────────────────────────────── +// Module state +// ───────────────────────────────────────────── +extern BruceConfigPins bruceConfigPins; + +static bool rfchat_ready = false; +static bool rfchat_update = false; +static volatile bool rfchat_pktReceived = false; +static volatile bool rfchat_irqEnabled = true; + +static String rfchat_displayName; +static String rfchat_outMsg; + +static std::vector rfchat_messages; +static int rfchat_scrollOffset = 0; +static const int rfchat_maxMessages = 19; + +// Layout constants (mirrors LoRaRF) +static const int rfchat_yStart = 35; +static const int rfchat_ySpacing = 10; + +// RadioLib objects +static Module *rfchat_module = nullptr; +static CC1101 *rfchat_radio = nullptr; + +// ───────────────────────────────────────────── +// ISR +// ───────────────────────────────────────────── +IRAM_ATTR void onRFChatPacket() { + if (!rfchat_irqEnabled) return; + rfchat_pktReceived = true; +} + +// ───────────────────────────────────────────── +// Init / teardown +// ───────────────────────────────────────────── +static void rfchat_clearRadio() { + if (rfchat_radio) { delete rfchat_radio; rfchat_radio = nullptr; } + if (rfchat_module) { delete rfchat_module; rfchat_module = nullptr; } +} + +/** + * Start the CC1101 on the configured SPI bus. + * Returns true on success. + */ +static bool rfchat_startRadio(float freqMHz) { + rfchat_ready = false; + rfchat_pktReceived = false; + rfchat_irqEnabled = true; + + // ── Pin validation ────────────────────────────────────────────────── + int csPin = bruceConfigPins.CC1101_bus.cs; + int mosiPin = bruceConfigPins.CC1101_bus.mosi; + int misoPin = bruceConfigPins.CC1101_bus.miso; + int sckPin = bruceConfigPins.CC1101_bus.sck; + int gdo0Pin = bruceConfigPins.CC1101_bus.io0; // GDO0 → packet-ready IRQ + + if (csPin == GPIO_NUM_NC || mosiPin == GPIO_NUM_NC || + misoPin == GPIO_NUM_NC || sckPin == GPIO_NUM_NC) { + displayError("CC1101 pins not set!", true); + return false; + } + if (gdo0Pin == GPIO_NUM_NC) { + displayError("CC1101 GDO0 not set!", true); + return false; + } + + // ── SPI bus selection (mirrors LoRaRF pattern) ────────────────────── + SPIClass *selectedSPI = &CC_NRF_SPI; + CC_NRF_SPI.begin((int8_t)sckPin, (int8_t)misoPin, (int8_t)mosiPin); + + rfchat_clearRadio(); + rfchat_module = new Module(csPin, gdo0Pin, RADIOLIB_NC, RADIOLIB_NC, *selectedSPI); + rfchat_radio = new CC1101(rfchat_module); + + // ── Radio parameters ───────────────────────────────────────────────── + // 4.8 kbps GFSK – good balance of range / throughput for short messages + int state = rfchat_radio->begin(freqMHz, // carrier frequency (MHz) + 4.8, // bit rate (kbps) + 5.0, // freq deviation (kHz) + 58.5, // RX bandwidth (kHz) + 10, // TX power (dBm) + 16); // preamble length (bytes) + + if (state == RADIOLIB_ERR_NONE) { + rfchat_radio->setGdo0Action(onRFChatPacket, RISING); + state = rfchat_radio->startReceive(); + } + + if (state != RADIOLIB_ERR_NONE) { + Serial.printf("[RF Chat] CC1101 init failed: %d\n", state); + displayError("CC1101 Init Failed", true); + rfchat_clearRadio(); + return false; + } + + rfchat_ready = true; + Serial.printf("[RF Chat] CC1101 ready @ %.3f MHz\n", freqMHz); + return true; +} + +// ───────────────────────────────────────────── +// TX +// ───────────────────────────────────────────── +static bool rfchat_sendMessage(String payload) { + if (!rfchat_ready || !rfchat_radio) return false; + rfchat_irqEnabled = false; + + int state = rfchat_radio->transmit(payload); + rfchat_radio->startReceive(); + rfchat_irqEnabled = true; + + if (state != RADIOLIB_ERR_NONE) { + Serial.printf("[RF Chat] TX failed: %d\n", state); + displayError("RF send failed"); + return false; + } + return true; +} + +// ───────────────────────────────────────────── +// RX +// ───────────────────────────────────────────── +static void rfchat_receiveMessage() { + if (!rfchat_pktReceived || !rfchat_ready || !rfchat_radio) return; + rfchat_irqEnabled = false; + rfchat_pktReceived = false; + + String incoming; + int state = rfchat_radio->readData(incoming); + + if (state == RADIOLIB_ERR_NONE && incoming.length() > 0) { + Serial.println("[RF Chat] RX: " + incoming); + File f = LittleFS.open("/rf_chats.txt", "a"); + if (f) { f.println(incoming); f.close(); } + + rfchat_messages.push_back(incoming); + if ((int)rfchat_messages.size() > rfchat_maxMessages) + rfchat_scrollOffset = rfchat_messages.size() - rfchat_maxMessages; + rfchat_update = true; + } else { + Serial.printf("[RF Chat] RX error: %d\n", state); + } + + rfchat_radio->startReceive(); + rfchat_irqEnabled = true; +} + +// ───────────────────────────────────────────── +// Render +// ───────────────────────────────────────────── +static void rfchat_render() { + if (!rfchat_update) return; + tft.setTextSize(1); + tft.fillScreen(TFT_BLACK); + + // Header + tft.setTextColor(0x6DFC); // greenish – same as LoRa + if (!rfchat_ready) + tft.drawString("CC1101 not ready", 10, 13); + tft.drawString("Nick: " + rfchat_displayName, 10, 25); + + // Messages + int yPos = rfchat_yStart; + int endIdx = rfchat_scrollOffset + rfchat_maxMessages; + if (endIdx > (int)rfchat_messages.size()) endIdx = rfchat_messages.size(); + + for (int i = rfchat_scrollOffset; i < endIdx; i++) { + tft.setTextColor(bruceConfig.priColor); + tft.drawString(rfchat_messages[i], 10, yPos); + yPos += rfchat_ySpacing; + } + + // Footer hint + tft.setTextColor(TFT_DARKGREY); + tft.drawString("[SEL] Send [UP/DN] Scroll [ESC] Quit", 10, tftHeight - 12); + + rfchat_update = false; +} + +// ───────────────────────────────────────────── +// Load history from flash +// ───────────────────────────────────────────── +static void rfchat_loadMessages() { + rfchat_messages.clear(); + File f = LittleFS.open("/rf_chats.txt", "r"); + if (!f) return; + while (f.available()) { + String line = f.readStringUntil('\n'); + line.trim(); + if (line.length()) rfchat_messages.push_back(line); + } + f.close(); + if ((int)rfchat_messages.size() > rfchat_maxMessages) + rfchat_scrollOffset = rfchat_messages.size() - rfchat_maxMessages; + else + rfchat_scrollOffset = 0; +} + +// ───────────────────────────────────────────── +// Action helpers (button callbacks) +// ───────────────────────────────────────────── +static void rfchat_doSend() { + tft.fillScreen(TFT_BLACK); + + if (!rfchat_ready) { + tft.setTextColor(TFT_RED); + tft.setTextSize(2); + tft.drawCentreString("CC1101 not ready!", tftWidth / 2, tftHeight / 2, 2); + delay(1500); + rfchat_update = true; + return; + } + + String input = keyboard("", 256, "Message:"); + if (input.length() == 0) { + rfchat_update = true; + return; + } + + String full = rfchat_displayName + ": " + input; + Serial.println("[RF Chat] TX: " + full); + + if (rfchat_sendMessage(full)) { + File f = LittleFS.open("/rf_chats.txt", "a"); + if (f) { f.println(full); f.close(); } + rfchat_messages.push_back(full); + if ((int)rfchat_messages.size() > rfchat_maxMessages) + rfchat_scrollOffset = rfchat_messages.size() - rfchat_maxMessages; + } + + tft.fillScreen(TFT_BLACK); + rfchat_update = true; +} + +static void rfchat_scrollUp() { + if (rfchat_scrollOffset > 0) { rfchat_scrollOffset--; rfchat_update = true; } +} + +static void rfchat_scrollDown() { + if (rfchat_scrollOffset < (int)rfchat_messages.size() - rfchat_maxMessages) { + rfchat_scrollOffset++; + rfchat_update = true; + } +} + +// ───────────────────────────────────────────── +// Main loop +// ───────────────────────────────────────────── +static void rfchat_mainLoop() { + while (true) { + rfchat_render(); + rfchat_receiveMessage(); + +#ifdef HAS_3_BUTTONS + if (EscPress) { + long t0 = millis(); + while (EscPress) { + long elapsed = millis() - (t0 + 200); + if (elapsed > 0) { + int sweep = min((int)(360 * elapsed / 500), 360); + tft.drawArc(tftWidth/2, tftHeight/2, 25, 15, 0, sweep, + getColorVariation(bruceConfig.priColor), bruceConfig.bgColor); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } + tft.drawArc(tftWidth/2, tftHeight/2, 25, 15, 0, 360, + bruceConfig.bgColor, bruceConfig.bgColor); + if (millis() - t0 > 700) break; // long press → exit + check(EscPress); + rfchat_scrollUp(); + } + if (check(NextPress)) rfchat_scrollDown(); + if (check(SelPress)) rfchat_doSend(); +#else + if (check(EscPress)) break; + if (check(PrevPress)) rfchat_scrollUp(); + if (check(NextPress)) rfchat_scrollDown(); + if (check(SelPress)) rfchat_doSend(); +#endif + + delay(20); + } +} + +// ───────────────────────────────────────────── +// Settings helpers (public, used by menu) +// ───────────────────────────────────────────── +void rf_chat_change_username() { + tft.fillScreen(TFT_BLACK); + String username = keyboard("", 64, "Username:"); + if (username.length() == 0) return; + + File f = LittleFS.open("/rf_chat_settings.json", "r"); + JsonDocument doc; + if (f) { deserializeJson(doc, f); f.close(); } + doc["RF_Name"] = username; + f = LittleFS.open("/rf_chat_settings.json", "w"); + serializeJson(doc, f); + f.close(); +} + +/** + * Let the user pick a preset frequency or enter a custom one. + * Valid CC1101 / M5 RF bands: 315, 433, 868, 915 MHz. + */ +void rf_chat_change_freq() { + std::vector