diff --git a/README.md b/README.md index c7084deb64..af3f1396e0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Bruce is a versatile ESP32 firmware packed with offensive-security tools, built to make Red Team operations fast and portable. -It also supports [M5Stack](https://shop.m5stack.com), [LILYGO](https://lilygo.cc) and [Elecrow](https://www.elecrow.com) products, and works great with the Cardputer, Sticks, M5Cores, T-Decks and T-Embeds. +It also supports [M5Stack](https://shop.m5stack.com), [LILYGO](https://lilygo.cc) , [RockBase IoT](https://www.rockbaseiot.com) and [Elecrow](https://www.elecrow.com) products, and works great with the Cardputer, Sticks, M5Cores, T-Decks and T-Embeds. ## :zap: Get Our Official DevKit! @@ -245,6 +245,7 @@ Also, [read our FAQ](https://wiki.bruce.computer/faq/) | [Bruce RF Reaper](https://www.elecrow.com/bruce-pcb-rf-reaper.html) | :ok: | :ok: | :x: | :ok: but w/ ST25R3916 | :x: | :ok: | :ok: | :x: | :ok: | :x: | | [Elecrow 24B](https://www.elecrow.com/2-4inch-esp32-miner-lcd-display-2pcs-cryptocurrency-solo-miner-with-1000kh-s-hashrate.html) | :ok: | :ok: | :ok: | :ok: | :x: | :ok:¹ | :x: | :x: | :x: | :x:² | | [Elecrow 3.5"](https://www.elecrow.com/esp-terminal-with-esp32-3-5-inch-parallel-480x320-tft-capacitive-touch-display-rgb-by-chip-ili9488.html) | :ok: | :ok: | :ok: | :ok: | :x: | :ok:¹ | :x: | :x: | :x: | :x:² | +| [NM-CYD-C5 + RF HAT](https://rockbase.shop/products/nm-cyd-c5-colorful) | :ok: | :ok: | :x: | :ok: | :x: | :ok: | :ok: | :x: | :ok: | :x: | ² CYD have a LITE_VERSION version for Launcher Compatibility ¹ Core, CYD and StickCs Bad-USB: [here](https://wiki.bruce.computer/features/others/#badusb) @@ -258,6 +259,7 @@ Bruce stems from a keen observation within the community focused on devices like ![Bruce on M5Core](./media/pictures/core.png) ![Bruce on Stick](./media/pictures/stick.png) ![Bruce on CYD](./media/pictures/cyd.png) +![Bruce on CYD with NM-RF-HAT](./media/pictures/bruce-cyd.png) Other media can be [found here](./media/). diff --git a/boards/CYD-2432S028/CYD-2432S028.ini b/boards/CYD-2432S028/CYD-2432S028.ini index b7f5d2ccbf..45a581574e 100644 --- a/boards/CYD-2432S028/CYD-2432S028.ini +++ b/boards/CYD-2432S028/CYD-2432S028.ini @@ -58,8 +58,8 @@ build_flags = -DBAD_RX=GROVE_SCL ; SERIAL (GPS) dedicated pins - -DSERIAL_TX=1 - -DSERIAL_RX=3 + -DSERIAL_TX=3 + -DSERIAL_RX=1 -DGPS_SERIAL_TX=SERIAL_TX -DGPS_SERIAL_RX=SERIAL_RX @@ -75,14 +75,14 @@ build_flags = ;Infrared Led default pin and state -DIR_TX_PINS='{{"Pin 22", 22}, {"Pin 27", 27}}' - -DIR_RX_PINS='{{"Pin 22", 22}, {"Pin 27", 27}, {"Pin 35", 35}}' + -DIR_RX_PINS='{{"Pin 27", 27}, {"Pin 22", 22}, {"Pin 35", 35}}' -DTXLED=22 ;NEED TO SET SOMETHING HERE, at least -1 -DLED_ON=HIGH -DLED_OFF=LOW ;Radio Frequency (one pin modules) pin setting -DRF_TX_PINS='{{"Pin 22", 22}, {"Pin 27", 27}}' - -DRF_RX_PINS='{{"Pin 22", 22}, {"Pin 27", 27}, {"Pin 35", 35}}' + -DRF_RX_PINS='{{"Pin 27", 27}, {"Pin 22", 22}, {"Pin 35", 35}}' ;CC1101 SPI connection pins ; best connection pins for higher speed https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/spi_master.html#gpio-matrix-and-io-mux @@ -116,7 +116,7 @@ build_flags = ;Screen Setup -DHAS_SCREEN=1 - -DROTATION=1 + -DROTATION=3 -DBACKLIGHT=21 -DMINBRIGHT=160 @@ -339,7 +339,7 @@ build_flags = extends=env:CYD-2432S028 build_flags = ${env:CYD-2432S028.build_flags} - -DTFT_INVERSION_ON + ;-DTFT_INVERSION_ON -DDEVICE_NAME='"CYD-2USB"' [env:LAUNCHER_CYD-2USB] diff --git a/boards/CYD-2432S028/interface.cpp b/boards/CYD-2432S028/interface.cpp index 1280adca96..17b9ac5e76 100644 --- a/boards/CYD-2432S028/interface.cpp +++ b/boards/CYD-2432S028/interface.cpp @@ -112,6 +112,40 @@ void _post_setup_gpio() { pinMode(TFT_BL, OUTPUT); ledcAttach(TFT_BL, TFT_BRIGHT_FREQ, TFT_BRIGHT_Bits); ledcWrite(TFT_BL, 255); + + // Force sync color inversion to prevent bruceConf.json from overriding + // the value set in _setup_gpio(). For CYD variants with TFT_INVERSION_ON, + // the init() sequence sends INVON; we send INVOFF here to ensure normal colors. +#ifdef TFT_INVERSION_ON + bruceConfig.colorInverted = 0; + tft.invertDisplay(0); +#else + bruceConfig.colorInverted = 1; + tft.invertDisplay(1); +#endif + + bruceConfigPins.gps_bus.rx = (gpio_num_t)GPS_SERIAL_RX; + bruceConfigPins.gps_bus.tx = (gpio_num_t)GPS_SERIAL_TX; + bruceConfigPins.gpsBaudrate = 9600; + + bool pinsChanged = false; + if (bruceConfigPins.rfTx != 22) { + bruceConfigPins.rfTx = 22; + pinsChanged = true; + } + if (bruceConfigPins.rfRx != 27) { + bruceConfigPins.rfRx = 27; + pinsChanged = true; + } + if (bruceConfigPins.irTx != 22) { + bruceConfigPins.irTx = 22; + pinsChanged = true; + } + if (bruceConfigPins.irRx != 27) { + bruceConfigPins.irRx = 27; + pinsChanged = true; + } + if (pinsChanged) bruceConfigPins.saveFile(); } /********************************************************************* diff --git a/boards/CYD-2432S028/pins_arduino.h b/boards/CYD-2432S028/pins_arduino.h index 823f440f6f..f190e835a8 100644 --- a/boards/CYD-2432S028/pins_arduino.h +++ b/boards/CYD-2432S028/pins_arduino.h @@ -45,6 +45,14 @@ static const uint8_t T9 = 32; static const uint8_t DAC1 = 25; static const uint8_t DAC2 = 26; +// I2C +#define GROVE_SDA 27 +#define GROVE_SCL 22 + +// InfraRed +#define RXLED 27 +#define TXLED 22 + // Deepsleep #define DEEPSLEEP_WAKEUP_PIN 36 // Touch interrupt #define DEEPSLEEP_PIN_ACT LOW diff --git a/boards/nm-cyd-c5/interface.cpp b/boards/nm-cyd-c5/interface.cpp index 46a6144fc9..ea903b882f 100644 --- a/boards/nm-cyd-c5/interface.cpp +++ b/boards/nm-cyd-c5/interface.cpp @@ -37,12 +37,6 @@ void _setup_gpio() { digitalWrite(SDCARD_CS, HIGH); digitalWrite(W5500_SS_PIN, HIGH); digitalWrite(TFT_CS, HIGH); -#ifdef ST7789_DRIVER - bruceConfig.colorInverted = 0; -#endif -#ifdef ILI9341_DRIVER - bruceConfig.colorInverted = 0; -#endif } /*************************************************************************************** ** Function name: _post_setup_gpio() @@ -60,6 +54,24 @@ void _post_setup_gpio() { bruceConfigPins.gpsBaudrate = 9600; bruceConfigPins.rfTx = 8; bruceConfigPins.rfRx = 9; + + // Force disable color inversion for ST7789/ILI9341 on nm-cyd-c5. + // Must be done here because begin_storage() loads bruceConf.json which may + // override the value set in _setup_gpio(). +#ifdef ST7789_DRIVER + bruceConfig.colorInverted = 0; + tft.invertDisplay(0); +#endif +#ifdef ILI9341_DRIVER + bruceConfig.colorInverted = 0; + tft.invertDisplay(0); +#endif + + // Force set I2C bus pins for nm-cyd-c5. + // brucePins.conf may have stale/wrong i2c_bus values, so override them + // to ensure PN532 and other I2C devices use the correct GPIO9(SDA)/GPIO8(SCL). + bruceConfigPins.i2c_bus.sda = (gpio_num_t)GROVE_SDA; + bruceConfigPins.i2c_bus.scl = (gpio_num_t)GROVE_SCL; } /*************************************************************************************** diff --git a/boards/nm-cyd-c5/nm-cyd-c5.ini b/boards/nm-cyd-c5/nm-cyd-c5.ini index 7bdd0c80e1..f4872a17ff 100644 --- a/boards/nm-cyd-c5/nm-cyd-c5.ini +++ b/boards/nm-cyd-c5/nm-cyd-c5.ini @@ -28,8 +28,8 @@ build_flags = -DGPS_SERIAL_RX=4 ; grove pins ; defaults from https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h - -DGROVE_SDA=8 ; default RF TX pin - -DGROVE_SCL=9 ; default IR/RF RX pin + -DGROVE_SDA=9 ; default IR/RF RX pin + -DGROVE_SCL=8 ; default IR/RF TX pin ;-DALLOW_ALL_GPIO_FOR_IR_RF=1 ; Set this option to make use of all GPIOs, from 1 to 44 to be chosen, except TFT and SD pins ; ir led pin diff --git a/boards/nm-cyd-c5/pins_arduino.h b/boards/nm-cyd-c5/pins_arduino.h index 53ffb73a81..4491477502 100644 --- a/boards/nm-cyd-c5/pins_arduino.h +++ b/boards/nm-cyd-c5/pins_arduino.h @@ -57,9 +57,11 @@ static const uint8_t LP_TX = 11; // UART #define SERIAL_TX 11 #define SERIAL_RX 12 + // I2C -#define GROVE_SDA 8 -#define GROVE_SCL 9 +#define GROVE_SDA 9 +#define GROVE_SCL 8 + // SPI #define SPI_SCK_PIN 6 #define SPI_MOSI_PIN 7 diff --git a/media/pictures/bruce-cyd.png b/media/pictures/bruce-cyd.png new file mode 100644 index 0000000000..e1857b49c5 Binary files /dev/null and b/media/pictures/bruce-cyd.png differ diff --git a/platformio.ini b/platformio.ini index 3a343da8eb..49d7584766 100644 --- a/platformio.ini +++ b/platformio.ini @@ -281,6 +281,7 @@ build_flags = -DCONFIG_ESP32_JTAG_SUPPORT_DISABLE=1 # Restrict IR protocols to save Flash on 4mb devices -D_IR_ENABLE_DEFAULT_=false + -DDECODE_HASH=true -DDECODE_NEC=true -DSEND_NEC=true -DDECODE_SAMSUNG=true diff --git a/src/core/menu_items/WifiMenu.cpp b/src/core/menu_items/WifiMenu.cpp index f0c283a3ae..2cf05c4fb6 100644 --- a/src/core/menu_items/WifiMenu.cpp +++ b/src/core/menu_items/WifiMenu.cpp @@ -20,6 +20,9 @@ #ifndef LITE_VERSION #include "modules/pwnagotchi/pwnagotchi.h" #include "modules/wifi/channel_analyzer.h" +#if defined(NM_CYD_ESP32C5) +#include "modules/wifi/dual_band_analyzer.h" +#endif #include "modules/wifi/jam_detect.h" #include "modules/wifi/wifi_recover.h" #endif @@ -74,6 +77,9 @@ void WifiMenu::optionsMenu() { options.push_back({"SSH", lambdaHelper(ssh_setup, String(""))}); options.push_back({"Sniffer", sniffer_setup}); options.push_back({"Channel Analyzer", channel_analyzer_setup}); +#if defined(NM_CYD_ESP32C5) + options.push_back({"Dual Band Analyzer", dual_band_analyzer_setup}); +#endif options.push_back({"Jam Detect", jam_detect_setup}); options.push_back({"Scan Hosts", [=]() { bool doScan = true; diff --git a/src/modules/wifi/dual_band_analyzer.cpp b/src/modules/wifi/dual_band_analyzer.cpp new file mode 100644 index 0000000000..c3d40fe3c5 --- /dev/null +++ b/src/modules/wifi/dual_band_analyzer.cpp @@ -0,0 +1,255 @@ +#if !defined(LITE_VERSION) && defined(NM_CYD_ESP32C5) + +#include "dual_band_analyzer.h" + +#include "core/display.h" +#include "core/mykeyboard.h" +#include "core/wifi/webInterface.h" +#include +#include +#include + +namespace { + +constexpr int32_t RSSI_CEILING = -30; +constexpr int32_t RSSI_SHOW_SSID = -70; +constexpr int32_t RSSI_FLOOR = -100; +constexpr uint8_t CHANNEL_COUNT = 9; +constexpr uint8_t DISPLAY_AP_COUNT = 8; +// non-DFS (Safe) Channel +constexpr uint8_t CHANNELS[CHANNEL_COUNT] = {36, 40, 44, 48, 149, 153, 157, 161, 165}; +// DFS(Dynamic Frequency Selection) channels (Optional) +// constexpr uint8_t DFS_CHANNELS[8] = {52,56,60,64,100,116,132,140}; +constexpr uint16_t CHANNEL_COLORS[CHANNEL_COUNT] = { + TFT_RED, TFT_ORANGE, TFT_YELLOW, TFT_GREEN, TFT_CYAN, TFT_BLUE, TFT_MAGENTA, TFT_PINK, TFT_WHITE +}; + +struct ChannelStats { + uint8_t apCount; + int32_t peak; + String peakName; +}; + +struct DisplayAp { + int channel; + int32_t rssi; + String name; +}; + +uint16_t channelIdx(int channel) { + if (channel <= 14) return channel - 1; + if (channel <= 68) return 14 + ((channel - 32) / 2); + if (channel <= 144) return 41 + ((channel - 96) / 2); + if (channel <= 177) return 67 + ((channel - 149) / 2); + return 82; +} + +int graphIndex(int channel) { + static constexpr uint16_t referenceIndexes[CHANNEL_COUNT] = {16, 18, 20, 22, 67, 69, 71, 73, 75}; + uint16_t index = channelIdx(channel); + for (uint8_t i = 0; i < CHANNEL_COUNT; ++i) { + if (index == referenceIndexes[i] && channel == CHANNELS[i]) return i; + } + return -1; +} + +bool matchBssidPrefix(const uint8_t *first, const uint8_t *second) { + for (uint8_t i = 0; i < 5; ++i) { + if (first[i] != second[i]) return false; + } + return true; +} + +void resetStats(ChannelStats *stats) { + for (uint8_t i = 0; i < CHANNEL_COUNT; ++i) { + stats[i].apCount = 0; + stats[i].peak = RSSI_FLOOR; + stats[i].peakName = ""; + } +} + +void resetDisplayAps(DisplayAp *displayAps) { + for (uint8_t i = 0; i < DISPLAY_AP_COUNT; ++i) { + displayAps[i].channel = 0; + displayAps[i].rssi = RSSI_FLOOR; + displayAps[i].name = ""; + } +} + +void addDisplayAp(DisplayAp *displayAps, int channel, int32_t rssi, const String &name) { + int insertAt = -1; + for (uint8_t i = 0; i < DISPLAY_AP_COUNT; ++i) { + if (rssi > displayAps[i].rssi) { + insertAt = i; + break; + } + } + if (insertAt < 0) return; + + for (int i = DISPLAY_AP_COUNT - 1; i > insertAt; --i) displayAps[i] = displayAps[i - 1]; + displayAps[insertAt].channel = channel; + displayAps[insertAt].rssi = rssi; + displayAps[insertAt].name = name.length() > 0 ? name : ""; +} + +void drawAnalyzer( + const ChannelStats *stats, const DisplayAp *displayAps, uint16_t totalAp, uint16_t scanNumber +) { + const int headerHeight = 20; + const int statsHeight = 52; + const int spectrumHeight = tftHeight - headerHeight - statsHeight; + const int spectrumBottom = headerHeight + spectrumHeight; + const int baseline = spectrumBottom - 20; + const int graphTop = headerHeight + 12; + const int graphHeight = baseline - graphTop; + const int left = 8; + const int slotWidth = (tftWidth - left * 2) / CHANNEL_COUNT; + const int barWidth = max(3, slotWidth - 4); + + tft.fillRect(0, 0, tftWidth, headerHeight, bruceConfig.bgColor); + tft.fillRect(0, headerHeight, tftWidth, spectrumHeight, bruceConfig.bgColor); + tft.fillRect(0, spectrumBottom, tftWidth, statsHeight, bruceConfig.bgColor); + + tft.setTextSize(FP); + tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); + tft.drawString("< BACK", left, 2, 1); + tft.drawString("5G WiFi Analyzer", left + 42, 2, 1); + tft.drawRightString("Scan " + String(scanNumber), tftWidth - left, 2, 1); + + tft.drawFastHLine(left, baseline, tftWidth - left * 2, bruceConfig.priColor); + + int strongestIndex = -1; + for (uint8_t i = 0; i < CHANNEL_COUNT; ++i) { + const int x = left + i * slotWidth + 2; + const int signalHeight = + constrain(map(stats[i].peak, RSSI_FLOOR, RSSI_CEILING, 1, graphHeight), 1, graphHeight); + + if (stats[i].apCount > 0) { + tft.fillRect(x, baseline - signalHeight, barWidth, signalHeight, CHANNEL_COLORS[i]); + tft.setTextColor(CHANNEL_COLORS[i], bruceConfig.bgColor); + tft.drawCentreString(String(stats[i].peak), x + barWidth / 2, baseline - signalHeight - 9, 1); + if (strongestIndex < 0 || stats[i].peak > stats[strongestIndex].peak) strongestIndex = i; + } + + tft.setTextColor(CHANNEL_COLORS[i], bruceConfig.bgColor); + tft.drawCentreString(String(CHANNELS[i]), x + barWidth / 2, baseline + 2, 1); + tft.setTextColor(TFT_LIGHTGREY, bruceConfig.bgColor); + tft.drawCentreString(String(stats[i].apCount), x + barWidth / 2, baseline + 10, 1); + } + + tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); + tft.drawString("AP " + String(totalAp) + " 5 GHz", left, spectrumBottom + 2, 1); + if (strongestIndex >= 0) { + String peakText = "Ch" + String(CHANNELS[strongestIndex]) + " " + String(stats[strongestIndex].peak) + + " " + stats[strongestIndex].peakName; + if (peakText.length() > 31) peakText = peakText.substring(0, 31); + tft.drawString(peakText, left, spectrumBottom + 12, 1); + } else { + tft.drawString("Scanning 36-165", left, spectrumBottom + 12, 1); + } + + const int listTop = spectrumBottom + 24; + const int columnWidth = tftWidth / 2; + for (uint8_t i = 0; i < DISPLAY_AP_COUNT; ++i) { + if (displayAps[i].channel == 0) break; + const int column = i % 2; + const int row = i / 2; + String apText = String(i + 1) + ". Ch" + String(displayAps[i].channel) + " " + + String(displayAps[i].rssi) + " " + displayAps[i].name; + const int maxChars = columnWidth / (LW * 1) - 1; + if (apText.length() > maxChars) apText = apText.substring(0, maxChars); + tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); + tft.drawString(apText, column * columnWidth + left, listTop + row * LH, 1); + } +} + +void collectStats(ChannelStats *stats, DisplayAp *displayAps, int networks, uint16_t &totalAp) { + for (int i = 0; i < networks; ++i) { + int channel = WiFi.channel(i); + int index = graphIndex(channel); + if (index < 0) continue; + + int32_t rssi = WiFi.RSSI(i); + String ssid = WiFi.SSID(i); + if (ssid.length() > 0 && stats[index].peak < rssi) { + stats[index].peak = rssi; + stats[index].peakName = ssid; + } + + bool duplicate = false; + const uint8_t *bssid = WiFi.BSSID(i); + for (int j = 0; j < i; ++j) { + if (WiFi.channel(j) == channel && matchBssidPrefix(WiFi.BSSID(j), bssid)) { + duplicate = true; + break; + } + } + if (!duplicate) { + ++stats[index].apCount; + ++totalAp; + addDisplayAp(displayAps, channel, rssi, ssid.length() > 0 ? ssid : WiFi.BSSIDstr(i)); + } + } +} + +} // namespace + +void dual_band_analyzer_setup() { + returnToMenu = false; + cleanlyStopWebUiForWiFiFeature(); + WiFi.mode(WIFI_STA); + WiFi.setBandMode(WIFI_BAND_MODE_5G_ONLY); + + ChannelStats stats[CHANNEL_COUNT]; + DisplayAp displayAps[DISPLAY_AP_COUNT]; + uint16_t scanNumber = 0; + uint16_t totalAp = 0; + bool scanInProgress = false; + tft.fillScreen(bruceConfig.bgColor); + + while (!returnToMenu) { + if (check(EscPress)) { + returnToMenu = true; + break; + } + + if (!scanInProgress) { + resetStats(stats); + resetDisplayAps(displayAps); + totalAp = 0; + WiFi.scanNetworks(true, true, false, scanNumber < 2 ? 30 : 300); + scanInProgress = true; + } + + int networks = WiFi.scanComplete(); + if (networks == WIFI_SCAN_RUNNING) { + vTaskDelay(pdMS_TO_TICKS(20)); + continue; + } + + scanInProgress = false; + if (networks < 0) { + WiFi.scanDelete(); + vTaskDelay(pdMS_TO_TICKS(20)); + continue; + } + if (networks > 0) collectStats(stats, displayAps, networks, totalAp); + ++scanNumber; + drawAnalyzer(stats, displayAps, totalAp, scanNumber); + WiFi.scanDelete(); + + uint32_t pauseStarted = millis(); + while (millis() - pauseStarted < 250) { + if (check(EscPress)) { + returnToMenu = true; + break; + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + } + + WiFi.scanDelete(); + WiFi.setBandMode(WIFI_BAND_MODE_AUTO); +} + +#endif diff --git a/src/modules/wifi/dual_band_analyzer.h b/src/modules/wifi/dual_band_analyzer.h new file mode 100644 index 0000000000..40fca24668 --- /dev/null +++ b/src/modules/wifi/dual_band_analyzer.h @@ -0,0 +1,7 @@ +#pragma once + +#if !defined(LITE_VERSION) && defined(NM_CYD_ESP32C5) + +void dual_band_analyzer_setup(); + +#endif \ No newline at end of file