diff --git a/src/core/menu_items/GpsMenu.cpp b/src/core/menu_items/GpsMenu.cpp index dccc55e84..33194dcfe 100644 --- a/src/core/menu_items/GpsMenu.cpp +++ b/src/core/menu_items/GpsMenu.cpp @@ -8,9 +8,9 @@ void GpsMenu::optionsMenu() { options = { - {"Wardriving", [=]() { Wardriving(); } }, - {"GPS Tracker", [=]() { GPSTracker(); } }, - {"Config", [this]() { configMenu(); }}, + {"Wardriving", [this]() { wardrivingMenu(); }}, + {"GPS Tracker", [=]() { GPSTracker(); } }, + {"Config", [this]() { configMenu(); } }, }; addOptionToMainMenu(); @@ -18,6 +18,16 @@ void GpsMenu::optionsMenu() { loopOptions(options, MENU_TYPE_SUBMENU, txt.c_str()); } +void GpsMenu::wardrivingMenu() { + options = { + {"Scan WiFi Networks", []() { Wardriving(true, false); }}, + {"Scan BLE Devices", []() { Wardriving(false, true); }}, + {"Scan Both", []() { Wardriving(true, true); } }, + {"Back", [this]() { optionsMenu(); } }, + }; + + loopOptions(options, MENU_TYPE_SUBMENU, "Wardriving"); +} void GpsMenu::configMenu() { options = { {"Baudrate", setGpsBaudrateMenu }, diff --git a/src/core/menu_items/GpsMenu.h b/src/core/menu_items/GpsMenu.h index 679d1e9df..590a58020 100644 --- a/src/core/menu_items/GpsMenu.h +++ b/src/core/menu_items/GpsMenu.h @@ -8,6 +8,7 @@ class GpsMenu : public MenuItemInterface { GpsMenu() : MenuItemInterface("GPS") {} void optionsMenu(void); + void wardrivingMenu(void); void drawIcon(float scale); void drawIconImg(); bool getTheme() { return bruceConfig.theme.gps; } diff --git a/src/modules/ble/ble_common.cpp b/src/modules/ble/ble_common.cpp index 31ebe129b..93d8dd52b 100644 --- a/src/modules/ble/ble_common.cpp +++ b/src/modules/ble/ble_common.cpp @@ -6,15 +6,13 @@ #define CHARACTERISTIC_RX_UUID "1bc68da0-f3e3-11e9-81b4-2a2ae2dbcce4" #define CHARACTERISTIC_TX_UUID "1bc68efe-f3e3-11e9-81b4-2a2ae2dbcce4" +BLEScan *pBLEScan = nullptr; +int scanTime = SCANTIME; // In seconds + #if __has_include() #define NIMBLE_V2_PLUS 1 #endif -#define SCANTIME 5 -#define SCANTYPE ACTIVE -#define SCAN_INT 100 -#define SCAN_WINDOW 99 - #define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) BLEServer *pServer = NULL; @@ -37,9 +35,6 @@ class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic) { data = pCharacteristic->getValue(); } }; -int scanTime = SCANTIME; // In seconds -BLEScan *pBLEScan; - uint8_t sta_mac[6]; char strID[18]; char strAddl[200]; diff --git a/src/modules/ble/ble_common.h b/src/modules/ble/ble_common.h index 523a83d5f..53319db35 100644 --- a/src/modules/ble/ble_common.h +++ b/src/modules/ble/ble_common.h @@ -1,20 +1,29 @@ #ifndef __BLE_COMMON_H__ #define __BLE_COMMON_H__ -//#include "BLE2902.h" +// #include "BLE2902.h" #include #include #include -#include #include #include +#include -#include #include "core/display.h" +#include + +#define SCANTIME 5 +#define SCANTYPE ACTIVE +#define SCAN_INT 100 +#define SCAN_WINDOW 99 + +extern BLEScan *pBLEScan; +extern int scanTime; void ble_test(); +void ble_scan_setup(); void ble_scan(); void disPlayBLESend(); diff --git a/src/modules/gps/wardriving.cpp b/src/modules/gps/wardriving.cpp index 39f000e5e..3feb7e363 100644 --- a/src/modules/gps/wardriving.cpp +++ b/src/modules/gps/wardriving.cpp @@ -12,10 +12,18 @@ #include "core/sd_functions.h" #include "core/wifi/wifi_common.h" #include "current_year.h" +#include "modules/ble/ble_common.h" #define MAX_WAIT 5000 -Wardriving::Wardriving() { setup(); } +#if __has_include() +#define NIMBLE_V2_PLUS 1 +#endif +Wardriving::Wardriving(bool scanWiFi, bool scanBLE) { + this->scanWiFi = scanWiFi; + this->scanBLE = scanBLE; + setup(); +} Wardriving::~Wardriving() { if (gpsConnected) end(); @@ -33,6 +41,7 @@ void Wardriving::setup() { display_banner(); padprintln("Initializing..."); + loadAlertMACs(); begin_wifi(); if (!begin_gps()) return; @@ -91,7 +100,7 @@ void Wardriving::loop() { if (gps.location.isUpdated()) { padprintln("GPS location updated"); set_position(); - scan_networks(); + scanWiFiBLE(); } else { padprintln("GPS location not updated"); dump_gps_data(); @@ -128,15 +137,16 @@ void Wardriving::set_position() { void Wardriving::display_banner() { drawMainBorderWithTitle("Wardriving"); - padprintln(""); - if (wifiNetworkCount > 0) { - padprintln("File: " + filename.substring(0, filename.length() - 4), 2); - padprintln("Unique Networks Found: " + String(wifiNetworkCount), 2); - padprintf(2, "Distance: %.2fkm\n", distance / 1000); - } + tft.println(""); + if (filename != "") tft.println("File: " + filename.substring(0, filename.length() - 4)); + tft.print("Found"); + if (scanWiFi) tft.print(" WiFi: " + String(wifiNetworkCount)); + if (scanBLE) tft.print(" BLE: " + String(bluetoothDeviceCount)); + if (foundMACAddressCount) tft.print(" Alert: " + String(foundMACAddressCount)); - padprintln(""); + tft.println(""); + tft.printf("Distance: %.2fkm\n", distance / 1000); } void Wardriving::dump_gps_data() { @@ -166,19 +176,132 @@ String Wardriving::auth_mode_to_string(wifi_auth_mode_t authMode) { } } -void Wardriving::scan_networks() { - wifiConnected = true; +void Wardriving::scanWiFiBLE() { + padprintf(2, "Coord: %.6f, %.6f\n", gps.location.lat(), gps.location.lng()); + int networksFound = scanWiFi ? scanWiFiNetworks() : 0; + int bleDevicesFound = scanBLE ? scanBLEDevices() : 0; + append_to_file(networksFound, bleDevicesFound); +} +int Wardriving::scanWiFiNetworks() { + tft.print("Scanning Wi-Fi..."); + wifiConnected = true; int network_amount = WiFi.scanNetworks(); if (network_amount == 0) { - padprintln("No Wi-Fi networks found", 2); - return; + tft.print(" Found: None"); + return 0; } + tft.print(" Found: " + String(network_amount)); + tft.println(""); - padprintf(2, "Coord: %.6f, %.6f\n", gps.location.lat(), gps.location.lng()); - padprintln("Networks Found: " + String(network_amount), 2); + return network_amount; +} + +int Wardriving::scanBLEDevices() { + tft.print("Scanning BLE...."); + ble_scan_setup(); + BLEScanResults foundDevices; + +#ifdef NIMBLE_V2_PLUS + foundDevices = pBLEScan->getResults(scanTime * 1000, false); +#else + foundDevices = pBLEScan->start(scanTime, false); +#endif + + int count = foundDevices.getCount(); + if (count == 0) { + tft.print(" Found None"); + pBLEScan->clearResults(); + return 0; + } + + // Extract device data immediately while scan results are valid + bleDevices.clear(); + for (int i = 0; i < count; i++) { + const NimBLEAdvertisedDevice *device = foundDevices.getDevice(i); + if (!device) continue; + + BLEDeviceData deviceData; + + // Extract data with error handling + try { + deviceData.address = device->getAddress().toString().c_str(); + deviceData.rssi = device->getRSSI(); + + deviceData.name = device->getName().c_str(); + + deviceData.manufacturerId = 0; + + try { + // Check if device has manufacturer data before accessing it + if (device->haveManufacturerData()) { + std::string mfgData = device->getManufacturerData(); + if (!mfgData.empty() && mfgData.length() >= 2) { + // Extract manufacturer ID from first 2 bytes (little endian) + deviceData.manufacturerId = (uint16_t(mfgData[1]) << 8) | uint16_t(mfgData[0]); + } + } + } catch (const std::exception &e) { + // Serial.printf( + // "Exception extracting manufacturer data for device %s: %s\n", + // deviceData.address.c_str(), + // e.what() + // ); + deviceData.manufacturerId = 0; + } catch (...) { + // Serial.printf( + // "Unknown error extracting manufacturer data for device %s\n", + // deviceData.address.c_str() + // ); + deviceData.manufacturerId = 0; + } - return append_to_file(network_amount); + bleDevices.push_back(deviceData); + } catch (...) { + // Serial.printf("Error extracting data for BLE device %d, skipping\n", i); + continue; + } + } + + pBLEScan->clearResults(); + tft.print(" Found: " + String(bleDevices.size())); + tft.println(""); + + return bleDevices.size(); +} + +void Wardriving::loadAlertMACs() { + FS *fs; + if (!getFsStorage(fs)) return; + + if (!(*fs).exists("/BruceWardriving")) (*fs).mkdir("/BruceWardriving"); + + if ((*fs).exists("/BruceWardriving/alert.txt")) { + File alertFile = (*fs).open("/BruceWardriving/alert.txt", FILE_READ); + if (alertFile) { + while (alertFile.available()) { + String line = alertFile.readStringUntil('\n'); + line.trim(); + if (line.length() > 0 && !line.startsWith("#")) { + // Convert to lowercase for consistent comparison + line.toLowerCase(); + alertMACs.insert(line); + } + } + alertFile.close(); + if (alertMACs.size() > 0) { padprintln("Loaded " + String(alertMACs.size()) + " alert MACs"); } + } + } else { + // Create sample alert file + File alertFile = (*fs).open("/BruceWardriving/alert.txt", FILE_WRITE); + if (alertFile) { + alertFile.println("# Alert MAC addresses - one per line"); + alertFile.println("# Lines starting with # are comments"); + alertFile.println("# Example:"); + alertFile.println("# aa:bb:cc:dd:ee:ff"); + alertFile.close(); + } + } } void Wardriving::create_filename() { @@ -196,7 +319,7 @@ void Wardriving::create_filename() { filename = String(timestamp) + "_wardriving.csv"; } -void Wardriving::append_to_file(int network_amount) { +void Wardriving::append_to_file(int network_amount, int bluetooth_amount) { FS *fs; if (!getFsStorage(fs)) { padprintln("Storage setup error"); @@ -230,11 +353,18 @@ void Wardriving::append_to_file(int network_amount) { ); } + // WiFi Rows + // [BSSID],[SSID],[Capabilities],[First timestamp seen],[Channel],[Frequency], + // [RSSI],[Latitude],[Longitude],[Altitude],[Accuracy],[RCOIs],[MfgrId],[Type] + // Example: 1a:9f:ee:5c:71:c6,Scampoodle,[WPA2-EAP-CCMP][ESS],2018-08-01 13:08:27,161,5805, + // -43,37.76578028,-123.45919439,67,3.2160000801086426,5A03BA0000 BAA2D00000 BAA2D02000,,WIFI + for (int i = 0; i < network_amount; i++) { String macAddress = WiFi.BSSIDstr(i); // Check if MAC was already found in this session if (registeredMACs.find(macAddress) == registeredMACs.end()) { + registeredMACs.insert(macAddress); // Adds MAC to file int32_t channel = WiFi.channel(i); @@ -262,10 +392,63 @@ void Wardriving::append_to_file(int network_amount) { ); file.print(buffer); + // Check for alert + checkForAlert(macAddress, "WiFi", WiFi.SSID(i)); + wifiNetworkCount++; } } + // Bluetooth Rows + // [BD_ADDR],[Device Name],[Capabilities],[First timestamp seen],[Channel],[Frequency], + // [RSSI],[Latitude],[Longitude],[Altitude],[Accuracy],[RCOIs],[MfgrId],[Type] + // Example: 63:56:ac:c4:d4:30,,Misc [LE],2018-08-03 18:14:12,0,, + // -67,37.76090571,-122.44877987,104,49.3120002746582,,72,BLE + + for (const auto &device : bleDevices) { + Serial.printf( + "Processing BLE device: %s, Name: %s, RSSI: %d\n", + device.address.c_str(), + device.name.c_str(), + device.rssi + ); + + // Check if MAC was already found in this session + if (registeredMACs.find(device.address) == registeredMACs.end()) { + registeredMACs.insert(device.address); // Adds MAC to file + + char buffer[512]; + char manufacturerIdStr[8] = ""; + if (device.manufacturerId != 0) { + snprintf(manufacturerIdStr, sizeof(manufacturerIdStr), "%04X", device.manufacturerId); + } + snprintf( + buffer, + sizeof(buffer), + "%s,\"%s\",Misc [BLE],%04d-%02d-%02d %02d:%02d:%02d,0,,%d,%f,%f,%f,%f,,%s,BLE\n", + device.address.c_str(), + device.name.c_str(), + gps.date.year(), + gps.date.month(), + gps.date.day(), + gps.time.hour(), + gps.time.minute(), + gps.time.second(), + device.rssi, + gps.location.lat(), + gps.location.lng(), + gps.altitude.meters(), + gps.hdop.hdop() * 1.0, + manufacturerIdStr + ); + file.print(buffer); + + // Check for alert + checkForAlert(device.address, "BLE", device.name); + + bluetoothDeviceCount++; + } + } file.close(); } @@ -285,6 +468,24 @@ void Wardriving::releasePins() { } } +void Wardriving::checkForAlert(const String &macAddress, const String &deviceType, const String &deviceName) { + String macLower = macAddress; + macLower.toLowerCase(); + + if (alertMACs.find(macLower) != alertMACs.end()) { + String alertMsg = "ALERT: " + deviceType + " found!"; + if (deviceName.length() > 0) { alertMsg += " Name: " + deviceName; } + alertMsg += " MAC: " + macAddress; + + foundMACAddressCount++; + + displayError(alertMsg.c_str()); + + // Brief delay to make alert visible + vTaskDelay(2000 / portTICK_PERIOD_MS); + } +} + void Wardriving::restorePins() { if (rxPinReleased) { if (bruceConfigPins.CC1101_bus.checkConflict(bruceConfigPins.gps_bus.rx) || diff --git a/src/modules/gps/wardriving.h b/src/modules/gps/wardriving.h index 95eef5ab0..78cb679fe 100644 --- a/src/modules/gps/wardriving.h +++ b/src/modules/gps/wardriving.h @@ -9,17 +9,19 @@ #ifndef __WAR_DRIVING_H__ #define __WAR_DRIVING_H__ +#include "modules/ble/ble_common.h" #include #include #include #include +#include class Wardriving { public: ///////////////////////////////////////////////////////////////////////////////////// // Constructor ///////////////////////////////////////////////////////////////////////////////////// - Wardriving(); + Wardriving(bool scanWiFi = false, bool scanBLE = false); ~Wardriving(); ///////////////////////////////////////////////////////////////////////////////////// @@ -38,7 +40,21 @@ class Wardriving { TinyGPSPlus gps; HardwareSerial GPSserial = HardwareSerial(2); // Uses UART2 for GPS std::set registeredMACs; // Store and track registered MAC + std::set alertMACs; // Store alert MAC addresses from file + bool scanWiFi = false; // Flag to scan WiFi networks + bool scanBLE = false; // Flag to scan Bluetooth devices int wifiNetworkCount = 0; // Counter fo wifi networks + int bluetoothDeviceCount = 0; // Counter for bluetooth devices + int foundMACAddressCount = 0; // Counter for found MAC addresses + + // Structure to safely store BLE device data + struct BLEDeviceData { + String address; + String name; + int rssi; + uint16_t manufacturerId; + }; + std::vector bleDevices; // Safe storage for BLE device data bool rxPinReleased = false; ///////////////////////////////////////////////////////////////////////////////////// @@ -60,9 +76,13 @@ class Wardriving { // Operations ///////////////////////////////////////////////////////////////////////////////////// void set_position(void); - void scan_networks(void); + void scanWiFiBLE(void); + int scanWiFiNetworks(void); + int scanBLEDevices(void); + void loadAlertMACs(void); + void checkForAlert(const String &macAddress, const String &deviceType, const String &deviceName = ""); String auth_mode_to_string(wifi_auth_mode_t authMode); - void append_to_file(int network_amount); + void append_to_file(int network_amount = 0, int bluetooth_amount = 0); void create_filename(void); };