diff --git a/esp32_marauder/Display.cpp b/esp32_marauder/Display.cpp index 0f14cb3..cb18eb4 100644 --- a/esp32_marauder/Display.cpp +++ b/esp32_marauder/Display.cpp @@ -590,7 +590,7 @@ void Display::displayBuffer(bool do_clear) } } -void Display::showCenterText(const char* text, int y, bool small_pp) +void Display::showCenterText(const char* text, int y, bool small_pp, uint8_t text_size) { if (!text) text = ""; @@ -598,7 +598,7 @@ void Display::showCenterText(const char* text, int y, bool small_pp) size_t len = strlen(text); if (!small_pp) - tft.setCursor((SCREEN_WIDTH - (len * (6 * BANNER_TEXT_SIZE))) / 2, y); + tft.setCursor((SCREEN_WIDTH - (len * (6 * text_size))) / 2, y); else tft.setCursor((SCREEN_WIDTH - (len * 6)) / 2, y); diff --git a/esp32_marauder/Display.h b/esp32_marauder/Display.h index f9eda4b..c017fd7 100644 --- a/esp32_marauder/Display.h +++ b/esp32_marauder/Display.h @@ -141,7 +141,7 @@ class Display void getTouchWhileFunction(bool pressed); void init(); void RunSetup(); - void showCenterText(const char* text, int y, bool small_pp = false); + void showCenterText(const char* text, int y, bool small_pp = false, uint8_t text_size = BANNER_TEXT_SIZE); void touchToExit(); void twoPartDisplay(String center_text); void updateBanner(String msg); diff --git a/esp32_marauder/MenuFunctions.cpp b/esp32_marauder/MenuFunctions.cpp index e7366b6..ab4c5c1 100644 --- a/esp32_marauder/MenuFunctions.cpp +++ b/esp32_marauder/MenuFunctions.cpp @@ -337,6 +337,7 @@ void MenuFunctions::main(uint32_t currentTime) (wifi_scan_obj.currentScanMode == WIFI_ATTACK_RICK_ROLL) || (wifi_scan_obj.currentScanMode == WIFI_ATTACK_BEACON_LIST) || (wifi_scan_obj.currentScanMode == BT_SCAN_ALL) || + (wifi_scan_obj.currentScanMode == BT_SCAN_FOX_HUNT) || (wifi_scan_obj.currentScanMode == BT_SCAN_RAYBAN) || (wifi_scan_obj.currentScanMode == BT_SCAN_AIRTAG) || (wifi_scan_obj.currentScanMode == BT_SCAN_AIRTAG_MON) || @@ -440,6 +441,7 @@ void MenuFunctions::main(uint32_t currentTime) (wifi_scan_obj.currentScanMode == WIFI_ATTACK_RICK_ROLL) || (wifi_scan_obj.currentScanMode == WIFI_ATTACK_BEACON_LIST) || (wifi_scan_obj.currentScanMode == BT_SCAN_ALL) || + (wifi_scan_obj.currentScanMode == BT_SCAN_FOX_HUNT) || (wifi_scan_obj.currentScanMode == BT_SCAN_RAYBAN) || (wifi_scan_obj.currentScanMode == BT_SCAN_AIRTAG) || (wifi_scan_obj.currentScanMode == BT_SCAN_AIRTAG_MON) || @@ -1500,6 +1502,7 @@ void MenuFunctions::RunSetup() extern LinkedList* ipList; extern LinkedList* probe_req_ssids; extern LinkedList* ssids; + extern LinkedList* ble_devices; this->disable_touch = false; @@ -1577,6 +1580,8 @@ void MenuFunctions::RunSetup() gpsPOIMenu.list = new LinkedList(); #endif + foxHuntMenu.list = new LinkedList(); + // Work menu names mainMenu.name = text_table1[6]; wifiMenu.name = text_table1[7]; @@ -1630,6 +1635,8 @@ void MenuFunctions::RunSetup() gpsPOIMenu.name = "GPS POI"; #endif + foxHuntMenu.name = "Fox Hunt"; + // Build Main Menu mainMenu.parentMenu = NULL; this->addNodes(&mainMenu, text_table1[7], TFTGREEN, WIFI, [this]() { @@ -2638,6 +2645,32 @@ void MenuFunctions::RunSetup() this->drawStatusBar(); wifi_scan_obj.StartScan(BT_SCAN_RAYBAN, TFT_CYAN); }); + this->addNodes(&bluetoothSnifferMenu, "Fox Hunt", TFTCYAN, SCANNERS, [this]() { + foxHuntMenu.list->clear(); + + // Bluetooth Fox Hunt Menu + foxHuntMenu.parentMenu = &bluetoothSnifferMenu; // Second Menu is third menu parent + this->addNodes(&foxHuntMenu, text09, TFTLIGHTGREY, 0, [this]() { + this->changeMenu(foxHuntMenu.parentMenu, true); + }); + + for (int i = 0; i < ble_devices->size(); i++) { + BleDevice ble_device = ble_devices->get(i); + ble_device.selected = false; + ble_devices->set(i, ble_device); + uint8_t node_color = rssiToMenuColor(ble_devices->get(i).rssi); + String node_name = String(ble_devices->get(i).rssi) + " " + ble_devices->get(i).name; + this->addNodes(&foxHuntMenu, node_name.c_str(), node_color, 255, [this, i](){ + BleDevice ble_device = ble_devices->get(i); + ble_device.selected = true; + ble_devices->set(i, ble_device); + display_obj.clearScreen(); + this->drawStatusBar(); + wifi_scan_obj.StartScan(BT_SCAN_FOX_HUNT, TFT_CYAN); + }); + } + this->changeMenu(&foxHuntMenu, true); + }); // Bluetooth Attack menu bluetoothAttackMenu.parentMenu = &bluetoothMenu; // Second Menu is third menu parent @@ -2679,6 +2712,7 @@ void MenuFunctions::RunSetup() this->drawStatusBar(); wifi_scan_obj.StartScan(BT_ATTACK_SPAM_ALL, TFT_MAGENTA); }); + #endif //#ifndef HAS_ILI9341 diff --git a/esp32_marauder/MenuFunctions.h b/esp32_marauder/MenuFunctions.h index 47d59de..b2bc00a 100644 --- a/esp32_marauder/MenuFunctions.h +++ b/esp32_marauder/MenuFunctions.h @@ -196,6 +196,8 @@ class MenuFunctions Menu evilPortalMenu; + Menu foxHuntMenu; + //static void lv_tick_handler(); // Menu icons diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index 04ad18c..1055367 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -34,6 +34,7 @@ LinkedList* airtags; LinkedList* flippers; LinkedList* ipList; LinkedList* probe_req_ssids; +LinkedList* ble_devices; extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32_t arg3){ if (arg == 31337) @@ -1062,9 +1063,36 @@ extern "C" { #endif } } - else if (wifi_scan_obj.currentScanMode == BT_SCAN_ALL) { + else if ((wifi_scan_obj.currentScanMode == BT_SCAN_ALL) || + (wifi_scan_obj.currentScanMode == BT_SCAN_FOX_HUNT)) { if (buf >= 0) { + BleDevice ble_device; + if (name_length > 0) + ble_device.name = name; + else + ble_device.name = mac; + + ble_device.rssi = rssi; + + memcpy(ble_device.mac, mac_char, sizeof(mac_char)); + + int device_match_check = wifi_scan_obj.seenBLEDevice(ble_device); + + if (device_match_check >= 0) { + ble_device.selected = ble_devices->get(device_match_check).selected; + ble_device.name = ble_devices->get(device_match_check).name; + memcpy(ble_device.mac, ble_devices->get(device_match_check).mac, sizeof(mac_char)); + ble_devices->set(device_match_check, ble_device); + //Serial.println(ble_devices->get(device_match_check).name + " RSSI updated: " + String(ble_devices->get(device_match_check).rssi)); + return; + } + + if (wifi_scan_obj.currentScanMode == BT_SCAN_FOX_HUNT) + return; + + ble_devices->add(ble_device); + #ifndef HAS_MINI_SCREEN display_string.concat(text_table4[0]); #endif @@ -1102,6 +1130,64 @@ extern "C" { display_obj.loading = false; } #endif + + /*Serial.println("\n========== BLE Advertisement =========="); + + Serial.printf("Address: %s\n", advertisedDevice->getAddress().toString().c_str()); + Serial.printf("Address Type: %u\n", advertisedDevice->getAddressType()); + Serial.printf("RSSI: %d dBm\n", advertisedDevice->getRSSI()); + Serial.printf("Adv Type: %u\n", advertisedDevice->getAdvType()); + Serial.printf("Adv Length: %u\n", advertisedDevice->getAdvLength()); + + Serial.printf("Connectable: %s\n", advertisedDevice->isConnectable() ? "yes" : "no"); + Serial.printf("Scannable: %s\n", advertisedDevice->isScannable() ? "yes" : "no"); + Serial.printf("Legacy Adv: %s\n", advertisedDevice->isLegacyAdvertisement() ? "yes" : "no"); + + if (advertisedDevice->haveName()) { + Serial.printf("Name: %s\n", advertisedDevice->getName().c_str()); + } + + if (advertisedDevice->haveTXPower()) { + Serial.printf("TX Power: %d dBm\n", advertisedDevice->getTXPower()); + } + + if (advertisedDevice->haveAppearance()) { + Serial.printf("Appearance: 0x%04X\n", advertisedDevice->getAppearance()); + } + + if (advertisedDevice->haveAdvInterval()) { + Serial.printf("Adv Interval: %u units\n", advertisedDevice->getAdvInterval()); + } + + if (advertisedDevice->haveURI()) { + Serial.printf("URI: %s\n", advertisedDevice->getURI().c_str()); + } + + uint8_t svcCount = advertisedDevice->getServiceUUIDCount(); + Serial.printf("Service UUIDs: %u\n", svcCount); + for (uint8_t i = 0; i < svcCount; i++) { + Serial.printf(" [%u] %s\n", i, advertisedDevice->getServiceUUID(i).toString().c_str()); + } + + uint8_t svcDataCount = advertisedDevice->getServiceDataCount(); + Serial.printf("Service Data: %u\n", svcDataCount); + for (uint8_t i = 0; i < svcDataCount; i++) { + Serial.printf(" UUID [%u]: %s\n", i, advertisedDevice->getServiceDataUUID(i).toString().c_str()); + printStringData(" Data", advertisedDevice->getServiceData(i)); + } + + uint8_t mfgCount = advertisedDevice->getManufacturerDataCount(); + Serial.printf("Mfg Data Sets: %u\n", mfgCount); + for (uint8_t i = 0; i < mfgCount; i++) { + printStringData(" Mfg", advertisedDevice->getManufacturerData(i)); + } + + const std::vector &payload = advertisedDevice->getPayload(); + Serial.printf("Raw Payload [%u]: ", (unsigned)payload.size()); + printHex(payload.data(), payload.size()); + Serial.println(); + + Serial.println("=======================================");*/ } } else if (wifi_scan_obj.currentScanMode == WIFI_SCAN_WAR_DRIVE) { @@ -1382,6 +1468,24 @@ extern "C" { #endif #endif +int WiFiScan::seenBLEDevice(BleDevice ble_device) { + for (int i = 0; i < ble_devices->size(); i++) { + //Serial.println("Comparing names " + ble_devices->get(i).name + " | " + ble_device.name); + if ((ble_devices->get(i).name == ble_device.name) || (ble_device.name == "")) { + //Serial.print("Comparing MACs "); + //Serial.print(macToString(ble_devices->get(i).mac)); + //Serial.print(" | "); + //Serial.println(macToString(ble_device.mac)); + //for (int x = 0; x < 6; x++) { + // if (ble_devices->get(i).mac[x] != ble_device.mac[x]) + // return -1; + //} + return i; + } + } + return -1; +} + bool WiFiScan::isFlockCamera(const uint8_t* payload, size_t len, const String& name, String* serial_out) { if (payload == nullptr || len < 4) { return false; @@ -1511,6 +1615,7 @@ void WiFiScan::RunSetup() { stations = new LinkedList(); airtags = new LinkedList(); flippers = new LinkedList(); + ble_devices = new LinkedList(); ipList = new LinkedList(); probe_req_ssids = new LinkedList; // for Pinescan @@ -1631,6 +1736,12 @@ int WiFiScan::clearList(uint8_t list_type) { airtags->remove(0); return num_cleared; } + else if (list_type == CLEAR_BLE) { + num_cleared = ble_devices->size(); + while (ble_devices->size() > 0) + ble_devices->remove(0); + return num_cleared; + } else if (list_type == CLEAR_FLIP) { num_cleared = flippers->size(); while (flippers->size() > 0) @@ -1983,6 +2094,7 @@ void WiFiScan::StartScan(uint8_t scan_mode, uint16_t color) { else if (scan_mode == WIFI_ATTACK_AP_SPAM) this->startWiFiAttacks(scan_mode, color, " AP Beacon Spam "); else if ((scan_mode == BT_SCAN_ALL) || + (scan_mode == BT_SCAN_FOX_HUNT) || (scan_mode == BT_SCAN_RAYBAN) || (scan_mode == BT_SCAN_AIRTAG) || (scan_mode == BT_SCAN_AIRTAG_MON) || @@ -2355,6 +2467,7 @@ void WiFiScan::StopScan(uint8_t scan_mode) { if ((currentScanMode == BT_SCAN_ALL) || + (currentScanMode == BT_SCAN_FOX_HUNT) || (currentScanMode == BT_SCAN_RAYBAN) || (currentScanMode == BT_SCAN_AIRTAG) || (currentScanMode == BT_SCAN_AIRTAG_MON) || @@ -2384,6 +2497,14 @@ void WiFiScan::StopScan(uint8_t scan_mode) { this->analyzer_name_update = true; #endif + for (int i = 0; i < ble_devices->size(); i++) { + if (ble_devices->get(i).selected) { + BleDevice ble_device = ble_devices->get(i); + ble_device.selected = false; + ble_devices->set(i, ble_device); + } + } + this->shutdownBLE(); this->ble_scanning = false; #endif @@ -5365,6 +5486,7 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) { NimBLEDevice::init(""); pBLEScan = NimBLEDevice::getScan(); //create new scan if ((scan_mode == BT_SCAN_ALL) || + (scan_mode == BT_SCAN_FOX_HUNT) || (scan_mode == BT_SCAN_RAYBAN) || (scan_mode == BT_SCAN_AIRTAG) || (scan_mode == BT_SCAN_AIRTAG_MON) || @@ -5380,6 +5502,8 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) { display_obj.tft.fillRect(0,16,TFT_WIDTH,16, color); if (scan_mode == BT_SCAN_ALL) display_obj.tft.drawCentreString(text_table4[41],TFT_WIDTH / 2,16,2); + else if (scan_mode == BT_SCAN_FOX_HUNT) + display_obj.tft.drawCentreString("Fox Hunt",TFT_WIDTH / 2,16,2); else if (scan_mode == BT_SCAN_AIRTAG) display_obj.tft.drawCentreString("Airtag Sniff",TFT_WIDTH / 2,16,2); else if (scan_mode == BT_SCAN_AIRTAG_MON) @@ -5401,12 +5525,21 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) { #endif display_obj.tft.setTextColor(TFT_CYAN, TFT_BLACK); #endif - if (scan_mode == BT_SCAN_ALL) + if (scan_mode == BT_SCAN_ALL) { + this->clearList(CLEAR_BLE); #ifndef HAS_NIMBLE_2 - pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), true); #else - pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), true); #endif + } + else if (scan_mode == BT_SCAN_FOX_HUNT) { + #ifndef HAS_NIMBLE_2 + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), true); + #else + pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), true); + #endif + } else if ((scan_mode == BT_SCAN_FLIPPER) || (scan_mode == BT_SCAN_RAYBAN) || (scan_mode == BT_SCAN_FLOCK) || @@ -9867,281 +10000,335 @@ uint16_t WiFiScan::rssiToColor(int8_t rssi) { return TFT_RED; } -// Upload one log file to WDG Wars. -// Mirrors backendUpload() but uses X-API-Key auth and wdgwars.pl endpoint. -/*bool WiFiScan::wdgwarsUpload(String filePath) { - //display.clearScreen(); - //display.drawCenteredText("WDG Upload...", true); - delay(100); - - if (!SD.exists(filePath)) { - //display.drawCenteredText(filePath + " not found", true); - Serial.println("[WDG] File not found: " + filePath); - return false; - } - - String apiKey = settings_obj.loadSetting(WDG_KEY_NAME); - if (apiKey.isEmpty()) { +#ifdef HAS_DIRECT_UPLOAD + // Upload one log file to WDG Wars. + // Mirrors backendUpload() but uses X-API-Key auth and wdgwars.pl endpoint. + bool WiFiScan::wdgwarsUpload(String filePath) { //display.clearScreen(); - //display.drawCenteredText("No WDG API key", true); - Serial.println("[WDG] No WDG Wars API key configured"); - return false; - } + //display.drawCenteredText("WDG Upload...", true); + delay(100); - File fileToUpload = SD.open(filePath); - if (!fileToUpload) { - //display.clearScreen(); - //display.drawCenteredText("Could not open file", true); - Serial.println("[WDG] Could not open: " + filePath); - return false; - } - - // Build multipart body - String boundary = "----ESP32BOUNDARY"; - String part1 = "--" + boundary + "\r\n"; - part1 += "Content-Disposition: form-data; name=\"file\"; filename=\"" + - filePath + "\"\r\n"; - part1 += "Content-Type: application/octet-stream\r\n\r\n"; - String part2 = "\r\n--" + boundary + "--\r\n"; - int totalLength = part1.length() + fileToUpload.size() + part2.length(); - - Serial.println("[WDG] File size: " + String(fileToUpload.size())); - Serial.println("[WDG] Total length: " + String(totalLength)); - - client->setInsecure(); - if (!client->connect("wdgwars.pl", 443)) { - fileToUpload.close(); - client->stop(); - //display.clearScreen(); - //display.drawCenteredText("WDG connect fail", true); - Serial.println("[WDG] Failed to connect to wdgwars.pl"); - return false; - } - - // HTTP request - client->println("POST /api/v2/upload-csv HTTP/1.1"); - client->println("Host: wdgwars.pl"); - client->println("User-Agent: ESP32Uploader/1.0"); - client->println("Accept: application/json"); - client->println("X-API-Key: " + apiKey); - client->println("Content-Type: multipart/form-data; boundary=" + boundary); - client->print("Content-Length: "); - client->println(totalLength); - client->println(); - - // Send body - client->print(part1); - - const size_t CHUNK = 4096; - uint8_t buf[CHUNK]; - size_t totalSent = 0; - uint8_t pct = 0; - String pctStr; - - while (fileToUpload.available()) { - size_t n = fileToUpload.read(buf, CHUNK); - totalSent += n; - client->write(buf, n); - pct = (totalSent * 100) / fileToUpload.size(); - //display.tft->drawRect(0, (TFT_HEIGHT / 3) * 2, TFT_WIDTH, TFT_HEIGHT - (TFT_HEIGHT / 3) * 2, ST77XX_BLACK); - //display.tft->setCursor(0, (TFT_HEIGHT / 3) * 2); - pctStr = String(pct) + "%"; - //display.drawCenteredText(pctStr, false); - } - - client->print(part2); - fileToUpload.close(); - - Serial.println("[WDG] Bytes sent: " + String(totalSent)); - - // Read response - String response; - unsigned long t = millis(); - while (millis() - t < 5000) { - while (client->available()) { - char c = client->read(); - response += c; + if (!SD.exists(filePath)) { + //display.drawCenteredText(filePath + " not found", true); + Serial.println("[WDG] File not found: " + filePath); + return false; } - if (!client->connected() && !client->available()) - break; - } - client->stop(); - // Capture first 200 chars of response for log viewer - String respTrunc = response.length() > 200 ? response.substring(0, 200) : response; - Serial.println("[WDG] Response: " + respTrunc); - - // WDG Wars returns 200 on success - bool ok = response.indexOf("202 Accepted") >= 0 || - response.indexOf("\"ok\":true") >= 0; - - //display.clearScreen(); - //display.drawCenteredText(ok ? "WDG OK" : "WDG Failed", true); - delay(1000); - - return ok; -}*/ - -// Upload one log file to Wigle -/*bool WiFiScan::wigleUpload(String filePath) { - //server.begin(); - - //display.clearScreen(); - //display.drawCenteredText("Wigle Upload...", true); - - delay(100); - - if (!SD.exists(filePath)) { - //display.clearScreen(); - //display.drawCenteredText(filePath + " not found", true); - Serial.println("File does not exist: " + filePath); - return false; - } - - File fileToUpload = SD.open(filePath); - if (!fileToUpload) { - //display.clearScreen(); - //display.drawCenteredText("Could not open file", true); - Serial.println("Could not open file: " + filePath); - return false; - } - - // Load credentials - String username = settings_obj.loadSetting("wu"); - String token = settings_obj.loadSetting("wt"); - if (username.isEmpty() || token.isEmpty()) { - fileToUpload.close(); - //display.clearScreen(); - //display.drawCenteredText("No wigle creds", true); - Serial.println("Missing wigle credentials"); - return false; - } - - Serial.println("Username: " + username); - Serial.println("Token: " + token); - - String boundary = "----ESP32BOUNDARY"; - String contentType = "multipart/form-data; boundary=" + boundary; - - // Build parts - String part1 = "--" + boundary + "\r\n"; - part1 += "Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath + "\"\r\n"; - part1 += "Content-Type: application/octet-stream\r\n\r\n"; - - String part2 = "\r\n--" + boundary + "\r\n"; - part2 += "Content-Disposition: form-data; name=\"donate\"\r\n\r\non\r\n"; - - String part3 = "--" + boundary + "--\r\n"; - - int totalLength = part1.length() + fileToUpload.size() + part2.length() + part3.length(); - - Serial.println("part1.length(): " + String(part1.length())); - Serial.println("fileToUpload.size(): " + String(fileToUpload.size())); - Serial.println("part2.length(): " + String(part2.length())); - Serial.println("part3.length(): " + String(part3.length())); - Serial.println("Total Content-Length: " + String(totalLength)); - - Serial.print("File size: "); - Serial.println(fileToUpload.size()); - - // Connect manually via WiFiClientSecure - //WiFiClientSecure *client = new WiFiClientSecure(); - //client.stop(); - client->setInsecure(); - - if (!client->connect("api.wigle.net", 443)) { - fileToUpload.close(); - //delete client; - client->stop(); - //display.clearScreen(); - //display.drawCenteredText("Could not connect", true); - Serial.println("Failed to connected to api.wigle.net"); - return false; - } - - Serial.println("Connected"); - - // Compose headers - String auth = base64Encode(username + ":" + token); - - Serial.println("Finished encoding"); - - client->println("POST /api/v2/file/upload HTTP/1.1"); - client->println("Host: api.wigle.net"); - client->println("User-Agent: ESP32Uploader/1.0"); - client->println("Accept: application/json"); - client->println("Authorization: Basic " + auth); - client->println("Content-Type: " + contentType); - client->print("Content-Length: "); - client->println(totalLength); - client->println(); - delay(100); - - Serial.println("Finished sending header"); - - // Send body - client->print(part1); - const size_t BUFFER_SIZE = 4096; // 1KB at a time - uint8_t buffer[BUFFER_SIZE]; - - Serial.println("Finished sending part1"); - - uint8_t percent_sent = 0; - - String display_percent = ""; - - size_t totalBytesSent = 0; - while (fileToUpload.available()) { - size_t bytesRead = fileToUpload.read(buffer, BUFFER_SIZE); - totalBytesSent += bytesRead; - Serial.print("Writing "); - Serial.print(totalBytesSent); - Serial.println(" bytes..."); - percent_sent = (totalBytesSent * 100) / fileToUpload.size(); - //display.tft->drawRect(0, (TFT_HEIGHT / 3) * 2, TFT_WIDTH, TFT_HEIGHT, ST77XX_BLACK); - //display.tft->setCursor(0, (TFT_HEIGHT / 3) * 2); - display_percent = (String)percent_sent + "%"; - //display.drawCenteredText(display_percent, false); - client->write(buffer, bytesRead); - } - - Serial.println("Uploaded file bytes: " + String(totalBytesSent)); - - client->print(part2); - client->print(part3); - - Serial.println("Finished sending part2 and part3"); - - fileToUpload.close(); - - - // Read response - String response; - unsigned long timeout = millis(); - while (millis() - timeout < 5000) { - while (client->available()) { - char c = client->read(); - response += c; + String apiKey = settings_obj.loadSetting(WDG_KEY_NAME); + if (apiKey.isEmpty()) { + //display.clearScreen(); + //display.drawCenteredText("No WDG API key", true); + Serial.println("[WDG] No WDG Wars API key configured"); + return false; } + + File fileToUpload = SD.open(filePath); + if (!fileToUpload) { + //display.clearScreen(); + //display.drawCenteredText("Could not open file", true); + Serial.println("[WDG] Could not open: " + filePath); + return false; + } + + // Build multipart body + String boundary = "----ESP32BOUNDARY"; + String part1 = "--" + boundary + "\r\n"; + part1 += "Content-Disposition: form-data; name=\"file\"; filename=\"" + + filePath + "\"\r\n"; + part1 += "Content-Type: application/octet-stream\r\n\r\n"; + String part2 = "\r\n--" + boundary + "--\r\n"; + int totalLength = part1.length() + fileToUpload.size() + part2.length(); + + Serial.println("[WDG] File size: " + String(fileToUpload.size())); + Serial.println("[WDG] Total length: " + String(totalLength)); + + client->setInsecure(); + if (!client->connect("wdgwars.pl", 443)) { + fileToUpload.close(); + client->stop(); + //display.clearScreen(); + //display.drawCenteredText("WDG connect fail", true); + Serial.println("[WDG] Failed to connect to wdgwars.pl"); + return false; + } + + // HTTP request + client->println("POST /api/v2/upload-csv HTTP/1.1"); + client->println("Host: wdgwars.pl"); + client->println("User-Agent: ESP32Uploader/1.0"); + client->println("Accept: application/json"); + client->println("X-API-Key: " + apiKey); + client->println("Content-Type: multipart/form-data; boundary=" + boundary); + client->print("Content-Length: "); + client->println(totalLength); + client->println(); + + // Send body + client->print(part1); + + const size_t CHUNK = 4096; + uint8_t buf[CHUNK]; + size_t totalSent = 0; + uint8_t pct = 0; + String pctStr; + + while (fileToUpload.available()) { + size_t n = fileToUpload.read(buf, CHUNK); + totalSent += n; + client->write(buf, n); + pct = (totalSent * 100) / fileToUpload.size(); + //display.tft->drawRect(0, (TFT_HEIGHT / 3) * 2, TFT_WIDTH, TFT_HEIGHT - (TFT_HEIGHT / 3) * 2, ST77XX_BLACK); + //display.tft->setCursor(0, (TFT_HEIGHT / 3) * 2); + pctStr = String(pct) + "%"; + //display.drawCenteredText(pctStr, false); + } + + client->print(part2); + fileToUpload.close(); + + Serial.println("[WDG] Bytes sent: " + String(totalSent)); + + // Read response + String response; + unsigned long t = millis(); + while (millis() - t < 5000) { + while (client->available()) { + char c = client->read(); + response += c; + } + if (!client->connected() && !client->available()) + break; + } + client->stop(); + + // Capture first 200 chars of response for log viewer + String respTrunc = response.length() > 200 ? response.substring(0, 200) : response; + Serial.println("[WDG] Response: " + respTrunc); + + // WDG Wars returns 200 on success + bool ok = response.indexOf("202 Accepted") >= 0 || + response.indexOf("\"ok\":true") >= 0; + + //display.clearScreen(); + //display.drawCenteredText(ok ? "WDG OK" : "WDG Failed", true); + delay(1000); + + return ok; } - if (millis() - timeout > 5000) - Serial.println("Timeout reached"); - if (!client->connected()) - Serial.println("Client disconnected"); - - client->stop(); + // Upload one log file to Wigle + bool WiFiScan::wigleUpload(String filePath) { + //server.begin(); - String respTrunc = response.length() > 200 ? response.substring(0, 200) : response; - Serial.println("[WIGLE] Response: " + respTrunc); + //display.clearScreen(); + //display.drawCenteredText("Wigle Upload...", true); - bool ok = response.indexOf("200 OK") >= 0; + delay(100); - //display.clearScreen(); - //display.drawCenteredText(ok ? "WIGLE OK" : "WIGLE Failed", true); + if (!SD.exists(filePath)) { + //display.clearScreen(); + //display.drawCenteredText(filePath + " not found", true); + Serial.println("File does not exist: " + filePath); + return false; + } - return ok; -}*/ + File fileToUpload = SD.open(filePath); + if (!fileToUpload) { + //display.clearScreen(); + //display.drawCenteredText("Could not open file", true); + Serial.println("Could not open file: " + filePath); + return false; + } + + // Load credentials + String username = settings_obj.loadSetting("wu"); + String token = settings_obj.loadSetting("wt"); + if (username.isEmpty() || token.isEmpty()) { + fileToUpload.close(); + //display.clearScreen(); + //display.drawCenteredText("No wigle creds", true); + Serial.println("Missing wigle credentials"); + return false; + } + + Serial.println("Username: " + username); + Serial.println("Token: " + token); + + String boundary = "----ESP32BOUNDARY"; + String contentType = "multipart/form-data; boundary=" + boundary; + + // Build parts + String part1 = "--" + boundary + "\r\n"; + part1 += "Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath + "\"\r\n"; + part1 += "Content-Type: application/octet-stream\r\n\r\n"; + + String part2 = "\r\n--" + boundary + "\r\n"; + part2 += "Content-Disposition: form-data; name=\"donate\"\r\n\r\non\r\n"; + + String part3 = "--" + boundary + "--\r\n"; + + int totalLength = part1.length() + fileToUpload.size() + part2.length() + part3.length(); + + Serial.println("part1.length(): " + String(part1.length())); + Serial.println("fileToUpload.size(): " + String(fileToUpload.size())); + Serial.println("part2.length(): " + String(part2.length())); + Serial.println("part3.length(): " + String(part3.length())); + Serial.println("Total Content-Length: " + String(totalLength)); + + Serial.print("File size: "); + Serial.println(fileToUpload.size()); + + // Connect manually via WiFiClientSecure + //WiFiClientSecure *client = new WiFiClientSecure(); + //client.stop(); + client->setInsecure(); + + if (!client->connect("api.wigle.net", 443)) { + fileToUpload.close(); + //delete client; + client->stop(); + //display.clearScreen(); + //display.drawCenteredText("Could not connect", true); + Serial.println("Failed to connected to api.wigle.net"); + return false; + } + + Serial.println("Connected"); + + // Compose headers + String auth = base64Encode(username + ":" + token); + + Serial.println("Finished encoding"); + + client->println("POST /api/v2/file/upload HTTP/1.1"); + client->println("Host: api.wigle.net"); + client->println("User-Agent: ESP32Uploader/1.0"); + client->println("Accept: application/json"); + client->println("Authorization: Basic " + auth); + client->println("Content-Type: " + contentType); + client->print("Content-Length: "); + client->println(totalLength); + client->println(); + delay(100); + + Serial.println("Finished sending header"); + + // Send body + client->print(part1); + const size_t BUFFER_SIZE = 4096; // 1KB at a time + uint8_t buffer[BUFFER_SIZE]; + + Serial.println("Finished sending part1"); + + uint8_t percent_sent = 0; + + String display_percent = ""; + + size_t totalBytesSent = 0; + while (fileToUpload.available()) { + size_t bytesRead = fileToUpload.read(buffer, BUFFER_SIZE); + totalBytesSent += bytesRead; + Serial.print("Writing "); + Serial.print(totalBytesSent); + Serial.println(" bytes..."); + percent_sent = (totalBytesSent * 100) / fileToUpload.size(); + //display.tft->drawRect(0, (TFT_HEIGHT / 3) * 2, TFT_WIDTH, TFT_HEIGHT, ST77XX_BLACK); + //display.tft->setCursor(0, (TFT_HEIGHT / 3) * 2); + display_percent = (String)percent_sent + "%"; + //display.drawCenteredText(display_percent, false); + client->write(buffer, bytesRead); + } + + Serial.println("Uploaded file bytes: " + String(totalBytesSent)); + + client->print(part2); + client->print(part3); + + Serial.println("Finished sending part2 and part3"); + + fileToUpload.close(); + + + // Read response + String response; + unsigned long timeout = millis(); + while (millis() - timeout < 5000) { + while (client->available()) { + char c = client->read(); + response += c; + } + } + + if (millis() - timeout > 5000) + Serial.println("Timeout reached"); + if (!client->connected()) + Serial.println("Client disconnected"); + + client->stop(); + + String respTrunc = response.length() > 200 ? response.substring(0, 200) : response; + Serial.println("[WIGLE] Response: " + respTrunc); + + bool ok = response.indexOf("200 OK") >= 0; + + //display.clearScreen(); + //display.drawCenteredText(ok ? "WIGLE OK" : "WIGLE Failed", true); + + return ok; + } +#endif + +void WiFiScan::runFoxHunt(uint32_t currentTime) { + #ifdef HAS_SCREEN + if (currentTime - this->last_ui_update >= 100) + this->last_ui_update = millis(); + else + return; + + display_obj.tft.fillRect(0, (TFT_HEIGHT / 3), TFT_WIDTH, TFT_HEIGHT / 3, TFT_BLACK); + + #ifdef HAS_BT + if (currentScanMode == BT_SCAN_FOX_HUNT) { + for (int i = 0; i < ble_devices->size(); i++) { + if (ble_devices->get(i).selected) { + int targ_rssi = ble_devices->get(i).rssi; + + #ifdef HAS_MINI_SCREEN + display_obj.tft.setTextSize(1); + #else + display_obj.tft.setTextSize(2); + #endif + + display_obj.tft.setTextColor(TFT_CYAN, TFT_BLACK); + + #ifdef HAS_MINI_SCREEN + display_obj.showCenterText(ble_devices->get(i).name.c_str(), (TFT_HEIGHT / 4), true); + #else + display_obj.showCenterText(ble_devices->get(i).name.c_str(), (TFT_HEIGHT / 4), false, 2); + #endif + + #ifdef HAS_MINI_SCREEN + display_obj.tft.setTextSize(2); + #else + display_obj.tft.setTextSize(3); + #endif + + display_obj.tft.setTextColor(rssiToColorScaled(targ_rssi), TFT_BLACK); + + #ifdef HAS_MINI_SCREEN + display_obj.showCenterText(String(targ_rssi).c_str(), (TFT_HEIGHT / 3), false, 2); + #else + display_obj.showCenterText(String(targ_rssi).c_str(), (TFT_HEIGHT / 3), false, 3); + #endif + + display_obj.tft.fillRect(0, (TFT_HEIGHT / 4) * 3, rssiToBarWidth(targ_rssi), 20, rssiToColorScaled(targ_rssi)); + display_obj.tft.fillRect(rssiToBarWidth(targ_rssi), (TFT_HEIGHT / 4) * 3, TFT_WIDTH, 20, TFT_BLACK); + } + } + } + #endif + #endif +} // Function for updating scan status void WiFiScan::main(uint32_t currentTime) @@ -10175,6 +10362,9 @@ void WiFiScan::main(uint32_t currentTime) this->channelHop(true); } } + else if (currentScanMode == BT_SCAN_FOX_HUNT) { + this->runFoxHunt(currentTime); + } else if (currentScanMode == WIFI_SCAN_DETECT_FOLLOW) { if (currentTime - initTime >= this->channel_hop_delay * HOP_DELAY) { initTime = millis(); diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index f60bcfb..eba3aef 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -14,12 +14,12 @@ #include // 1.3.8, 2.3.2 #endif -#ifdef HAS_IDF_3 +/*#ifdef HAS_IDF_3 extern "C" { #include "esp_netif.h" #include "esp_netif_net_stack.h" } -#endif +#endif*/ //#include #include @@ -68,8 +68,10 @@ #include "LedInterface.h" #endif -//#include -//#include "mbedtls/sha256.h" +#ifdef HAS_DIRECT_UPLOAD + #include + #include "mbedtls/sha256.h" +#endif #define bad_list_length 3 @@ -160,6 +162,7 @@ #define BT_SCAN_RAYBAN 81 #define BT_ATTACK_APPLE_JUICE 82 #define WIFI_SCAN_DISPLAY_AP_INFO 83 +#define BT_SCAN_FOX_HUNT 84 #define WIFI_ATTACK_FUNNY_BEACON 99 @@ -211,6 +214,7 @@ #define CLEAR_PINE 5 #define CLEAR_MULTI 6 #define CLEAR_SSID 7 +#define CLEAR_BLE 8 extern EvilPortal evil_portal_obj; @@ -274,6 +278,13 @@ struct Flipper { String name; }; +struct BleDevice { + uint8_t mac[6]; + String name; + bool selected = false; + int rssi = -128; +}; + #ifdef HAS_PSRAM extern struct mac_addr* mac_history; #endif @@ -304,7 +315,9 @@ class WiFiScan uint mac_history_cursor = 0; uint8_t channel_hop_delay = 1; - //WiFiClientSecure *client = new WiFiClientSecure(); + #ifdef HAS_DIRECT_UPLOAD + WiFiClientSecure *client = new WiFiClientSecure(); + #endif int x_pos; //position along the graph x axis float y_pos_x; //current graph y axis position of X value @@ -593,6 +606,7 @@ class WiFiScan bool wigleUpload(String filePath); bool wdgwarsUpload(String filePath); + void runFoxHunt(uint32_t currentTime); void throwThatShitInACircle(); void displayTargetFilter(); void displayTransmitRate(); @@ -869,6 +883,7 @@ class WiFiScan bool checkFlockOUI(const uint8_t mac[6]); bool startWiFi(String ssid, String password, bool gui = true); bool isFlockCamera(const uint8_t* payload, size_t len, const String& name, String* serial_out); + int seenBLEDevice(BleDevice ble_device); uint16_t rssiToColor(int8_t rssi); bool isMetaIdentifier(uint16_t id); bool isBlockedIdentifier(uint16_t id); diff --git a/esp32_marauder/configs.h b/esp32_marauder/configs.h index 1a4aed6..2a64bc1 100644 --- a/esp32_marauder/configs.h +++ b/esp32_marauder/configs.h @@ -538,6 +538,7 @@ #define HAS_NIMBLE_2 #define HAS_IDF_3 //#define HAS_SIMPLEX_DISPLAY + //#define HAS_DIRECT_UPLOAD #endif #if defined(MARAUDER_M5_NANO_C6) @@ -2430,6 +2431,9 @@ #define BANNER_TIME GRAPH_REFRESH + #define TFT_WIDTH 0 + #define TFT_HEIGHT 0 + #define TFT_BLACK 0 #define TFT_WHITE 0 #define TFT_CYAN 0 diff --git a/esp32_marauder/utils.h b/esp32_marauder/utils.h index 9265b41..252db4c 100644 --- a/esp32_marauder/utils.h +++ b/esp32_marauder/utils.h @@ -257,7 +257,7 @@ inline uint16_t getNextPort(uint16_t port) { return port + 1; } -String base64Encode(const String& input) { +inline String base64Encode(const String& input) { size_t outputLen; unsigned char output[256]; mbedtls_base64_encode(output, sizeof(output), &outputLen, @@ -265,4 +265,51 @@ String base64Encode(const String& input) { return String((char*)output).substring(0, outputLen); } +inline static void printHex(const uint8_t *data, size_t len) { + for (size_t i = 0; i < len; i++) { + if (data[i] < 0x10) Serial.print('0'); + Serial.print(data[i], HEX); + if (i + 1 < len) Serial.print(' '); + } +} + +inline static void printStringData(const char *label, const std::string &data) { + Serial.printf("%s [%u]: ", label, (unsigned)data.length()); + printHex((const uint8_t *)data.data(), data.length()); + Serial.println(); +} + +inline static uint8_t rssiToMenuColor(int rssi) { + if (rssi >= -60) { + return TFTGREEN; + } else if (rssi >= -70) { + return TFTYELLOW; + } else if (rssi >= -80) { + return TFTORANGE; + } else { + return TFTRED; + } +} + +inline static uint16_t rssiToColorScaled(int rssi) { + // Clamp to expected BLE RSSI range + rssi = constrain(rssi, -100, -40); + + // Map RSSI to 0-255 + uint8_t green = map(rssi, -100, -40, 0, 255); + uint8_t red = 255 - green; + + // RGB888 -> RGB565 + return ((red & 0xF8) << 8) | + ((green & 0xFC) << 3); +} + +inline static int rssiToBarWidth(int rssi) { + // Clamp to the full possible RSSI range + rssi = constrain(rssi, -127, 0); + + // Map RSSI to a width between 0 and TFT_WIDTH + return map(rssi, -127, 0, 0, TFT_WIDTH); +} + #endif \ No newline at end of file