diff --git a/esp32_marauder/Display.h b/esp32_marauder/Display.h index 5f03705..75b2ea7 100644 --- a/esp32_marauder/Display.h +++ b/esp32_marauder/Display.h @@ -13,7 +13,7 @@ #include #include //#include -#include +//#include #include "SPIFFS.h" #include "Assets.h" diff --git a/esp32_marauder/GpsInterface.cpp b/esp32_marauder/GpsInterface.cpp index 546d114..131786e 100644 --- a/esp32_marauder/GpsInterface.cpp +++ b/esp32_marauder/GpsInterface.cpp @@ -518,6 +518,9 @@ void GpsInterface::setGPSInfo() { this->datetime = this->dt_string_from_gps(); + this->lat_int = nmea.getLatitude(); + this->lon_int = nmea.getLongitude(); + this->lat = String((float)nmea.getLatitude()/1000000, 7); this->lon = String((float)nmea.getLongitude()/1000000, 7); long alt = 0; @@ -543,6 +546,14 @@ String GpsInterface::getLon() { return this->lon; } +int32_t GpsInterface::getLatInt() { + return this->lat_int; +} + +int32_t GpsInterface::getLonInt() { + return this->lon_int; +} + float GpsInterface::getAlt() { return this->altf; } diff --git a/esp32_marauder/GpsInterface.h b/esp32_marauder/GpsInterface.h index 27640ce..faae978 100644 --- a/esp32_marauder/GpsInterface.h +++ b/esp32_marauder/GpsInterface.h @@ -46,6 +46,8 @@ class GpsInterface { bool getGpsModuleStatus(); String getLat(); String getLon(); + int32_t getLatInt(); + int32_t getLonInt(); float getAlt(); float getAccuracy(); String getDatetime(); @@ -93,6 +95,8 @@ class GpsInterface { String notparsed_nmea_sentence = ""; String lat = ""; String lon = ""; + int32_t lat_int = 0; + int32_t lon_int = 0; float altf = 0.0; float accuracy = 0.0; String datetime = ""; diff --git a/esp32_marauder/MenuFunctions.cpp b/esp32_marauder/MenuFunctions.cpp index 40cd69a..708c5fd 100644 --- a/esp32_marauder/MenuFunctions.cpp +++ b/esp32_marauder/MenuFunctions.cpp @@ -203,6 +203,7 @@ void MenuFunctions::main(uint32_t currentTime) { // Stop the current scan if ((wifi_scan_obj.currentScanMode == WIFI_SCAN_PROBE) || + (wifi_scan_obj.currentScanMode == WIFI_SCAN_DETECT_FOLLOW) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_STATION) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_AP) || @@ -300,6 +301,7 @@ void MenuFunctions::main(uint32_t currentTime) { // Stop the current scan if ((wifi_scan_obj.currentScanMode == WIFI_SCAN_PROBE) || + (wifi_scan_obj.currentScanMode == WIFI_SCAN_DETECT_FOLLOW) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_STATION_WAR_DRIVE) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_RAW_CAPTURE) || (wifi_scan_obj.currentScanMode == WIFI_SCAN_STATION) || @@ -1669,6 +1671,11 @@ void MenuFunctions::RunSetup() this->drawStatusBar(); wifi_scan_obj.StartScan(WIFI_SCAN_SIG_STREN, TFT_CYAN); }); + this->addNodes(&wifiSnifferMenu, "MAC Monitor", TFTMAGENTA, NULL, SCANNERS, [this]() { + display_obj.clearScreen(); + this->drawStatusBar(); + wifi_scan_obj.StartScan(WIFI_SCAN_DETECT_FOLLOW, TFT_MAGENTA); + }); // Build Wardriving menu #ifdef HAS_GPS diff --git a/esp32_marauder/MenuFunctions.h b/esp32_marauder/MenuFunctions.h index dfc502a..beb6679 100644 --- a/esp32_marauder/MenuFunctions.h +++ b/esp32_marauder/MenuFunctions.h @@ -193,7 +193,7 @@ class MenuFunctions Menu evilPortalMenu; - static void lv_tick_handler(); + //static void lv_tick_handler(); // Menu icons @@ -260,7 +260,7 @@ class MenuFunctions Menu infoMenu; Menu apInfoMenu; - Ticker tick; + //Ticker tick; uint16_t x = -1, y = -1; boolean pressed = false; diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index d7a2639..59e7081 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -6,6 +6,9 @@ struct mac_addr* mac_history = nullptr; #endif +MacEntry WiFiScan::mac_entries[mac_history_len]; +uint8_t WiFiScan::mac_entry_state[mac_history_len]; + int num_beacon = 0; int num_deauth = 0; int num_probe = 0; @@ -1739,6 +1742,9 @@ void WiFiScan::RunSetup() { mac_history = (struct mac_addr*) ps_malloc(mac_history_len * sizeof(struct mac_addr)); #endif + for (int i = 0; i < mac_history_len; i++) + mac_entry_state[i] = 0; + #ifdef HAS_BT watch_models = new WatchModel[26] { {0x1A, "Fallback Watch"}, @@ -2099,6 +2105,8 @@ void WiFiScan::StartScan(uint8_t scan_mode, uint16_t color) StopScan(scan_mode); else if (scan_mode == WIFI_SCAN_PROBE) RunProbeScan(scan_mode, color); + else if (scan_mode == WIFI_SCAN_DETECT_FOLLOW) + RunProbeScan(scan_mode, color); else if (scan_mode == WIFI_SCAN_STATION_WAR_DRIVE) RunProbeScan(scan_mode, color); else if (scan_mode == WIFI_SCAN_EVIL_PORTAL) @@ -2455,6 +2463,7 @@ void WiFiScan::StopScan(uint8_t scan_mode) (currentScanMode == WIFI_CONNECTED) || (currentScanMode == BT_SCAN_FLOCK) || (currentScanMode == BT_SCAN_FLOCK_WARDRIVE) || + (currentScanMode == WIFI_SCAN_DETECT_FOLLOW) || (currentScanMode == LV_JOIN_WIFI) || (this->wifi_initialized)) { @@ -2608,6 +2617,16 @@ bool WiFiScan::mac_cmp(struct mac_addr addr1, struct mac_addr addr2) { return true; } +bool WiFiScan::mac_cmp(uint8_t addr1[6], uint8_t addr2[6]) { + //Return true if 2 mac_addr structs are equal. + for (int y = 0; y < 6 ; y++) { + if (addr1[y] != addr2[y]) { + return false; + } + } + return true; +} + #ifdef HAS_BT void WiFiScan::copyNimbleMac(const BLEAddress &addr, unsigned char out[6]) { #ifndef HAS_DUAL_BAND @@ -2622,7 +2641,7 @@ bool WiFiScan::mac_cmp(struct mac_addr addr1, struct mac_addr addr2) { } #endif -bool WiFiScan::seen_mac(unsigned char* mac) { +bool WiFiScan::seen_mac(unsigned char* mac, bool simple) { //Return true if this MAC address is in the recently seen array. struct mac_addr tmp; @@ -2630,6 +2649,7 @@ bool WiFiScan::seen_mac(unsigned char* mac) { tmp.bytes[x] = mac[x]; } + for (int x = 0; x < mac_history_len; x++) { if (this->mac_cmp(tmp, mac_history[x])) { return true; @@ -2638,6 +2658,303 @@ bool WiFiScan::seen_mac(unsigned char* mac) { return false; } +int16_t WiFiScan::seen_mac_int(unsigned char* mac, bool simple) { + //Return true if this MAC address is in the recently seen array. + + uint8_t tmp[6]; + for (int x = 0; x < 6 ; x++) { + tmp[x] = mac[x]; + } + + for (int x = 0; x < mac_history_len; x++) { + if (this->mac_cmp(tmp, mac_entries[x].mac)) { + return x; + } + } + return -1; +} + +inline uint32_t WiFiScan::hash_mac(const uint8_t mac[6]) { + uint32_t hash = 2166136261u; // FNV offset basis + + for (int i = 0; i < 6; i++) { + hash ^= mac[i]; + hash *= 16777619u; // FNV prime + } + + return hash; +} + +int WiFiScan::update_mac_entry(const uint8_t mac[6]) { + const uint32_t now_ms = millis(); + const uint32_t start_idx = hash_mac(mac) & (mac_history_len - 1); + + int32_t first_tombstone = -1; + + for (uint32_t probe = 0; probe < mac_history_len; probe++) { + const uint32_t idx = (start_idx + probe) & (mac_history_len - 1); + + switch (mac_entry_state[idx]) { + + case EMPTY_ENTRY: + // Insert new entry (prefer earlier tombstone if found) + if (first_tombstone >= 0) { + insert_mac_entry(first_tombstone, mac, now_ms); + } else { + insert_mac_entry(idx, mac, now_ms); + } + return EMPTY_ENTRY; + + case TOMBSTONE_ENTRY: + // Remember first tombstone for possible reuse + if (first_tombstone < 0) { + first_tombstone = idx; + } + break; + + case VALID_ENTRY: + // Check for MAC match + if (memcmp(mac_entries[idx].mac, mac, 6) == 0) { + mac_entries[idx].last_seen_ms = now_ms; + + #ifdef HAS_GPS + mac_entries[idx].last_lat_e6 = gps_obj.getLatInt(); + mac_entries[idx].last_lon_e6 = gps_obj.getLonInt(); + #endif + + if (mac_entries[idx].frame_count < UINT16_MAX) { + mac_entries[idx].frame_count++; + } + + return idx; + } + break; + } + } + + // Table full: evict something (simple policy: overwrite first tombstone or oldest) + evict_and_insert(mac, now_ms); + + return TOMBSTONE_ENTRY; +} + +inline void WiFiScan::insert_mac_entry(uint32_t idx, const uint8_t mac[6], uint32_t now_ms) { + memcpy(mac_entries[idx].mac, mac, 6); + mac_entries[idx].last_seen_ms = now_ms; + mac_entries[idx].frame_count = 1; + #ifdef HAS_GPS + mac_entries[idx].first_lat_e6 = gps_obj.getLatInt(); + mac_entries[idx].first_lon_e6 = gps_obj.getLonInt(); + mac_entries[idx].last_lat_e6 = gps_obj.getLatInt(); + mac_entries[idx].last_lon_e6 = gps_obj.getLonInt(); + #else + mac_entries[idx].first_lat_e6 = 0; + mac_entries[idx].first_lon_e6 = 0; + mac_entries[idx].last_lat_e6 = 0; + mac_entries[idx].last_lon_e6 = 0; + #endif + mac_entries[idx].following = false; + mac_entries[idx].dloc = 0; + mac_entry_state[idx] = VALID_ENTRY; +} + +void WiFiScan::evict_and_insert(const uint8_t mac[6], uint32_t now_ms) { + constexpr uint32_t EVICT_AGE_MS = TRACK_EVICT_SEC * 1000UL; + + // 1) Safety: if any tombstone exists, reuse it. + for (uint32_t i = 0; i < mac_history_len; i++) { + if (mac_entry_state[i] == TOMBSTONE_ENTRY) { + insert_mac_entry(i, mac, now_ms); + return; + } + } + + // 2) Find a VALID entry older than EVICT_AGE_MS (prefer oldest among those). + int32_t best_old_idx = -1; + uint32_t best_old_age = 0; + + // 3) Fallback: track absolute oldest VALID entry in case none exceed TRACK_EVICT_SEC. + int32_t oldest_idx = -1; + uint32_t oldest_age = 0; + + for (uint32_t i = 0; i < mac_history_len; i++) { + if (mac_entry_state[i] != VALID_ENTRY) { + continue; + } + + const uint32_t age = (uint32_t)(now_ms - mac_entries[i].last_seen_ms); + + // Track absolute oldest + if (oldest_idx < 0 || age > oldest_age) { + oldest_idx = (int32_t)i; + oldest_age = age; + } + + // Track best eviction candidate over threshold + if (age > EVICT_AGE_MS) { + if (best_old_idx < 0 || age > best_old_age) { + best_old_idx = (int32_t)i; + best_old_age = age; + } + } + } + + // Choose eviction target + int32_t victim = (best_old_idx >= 0) ? best_old_idx : oldest_idx; + + if (victim >= 0) { + // Optional: mark tombstone first (not strictly required if overwriting, + // but keeps state transitions explicit). + mac_entry_state[victim] = TOMBSTONE_ENTRY; + + // Overwrite with new entry + insert_mac_entry((uint32_t)victim, mac, now_ms); + Serial.println(macToString(mac_entries[victim].mac) + " expired"); + return; + } + + // If we got here, something is inconsistent (no valid entries but "full" insert path). + // Just insert at 0. + insert_mac_entry(0, mac, now_ms); +} + +static inline uint32_t age_ms(uint32_t now_ms, uint32_t last_seen_ms) { + return (uint32_t)(now_ms - last_seen_ms); // wrap-safe +} + +static inline int32_t iabs32(int32_t v) { + return (v < 0) ? -v : v; +} + +// Uses e6 degrees. No meters conversion at runtime. +// Writes computed location delta (e6 degrees) into out_dloc if provided. +static inline bool is_following_candidate_light( + const MacEntry& e, + uint32_t now_ms, + int32_t* out_dloc = nullptr +) { + const bool has_first = !(e.first_lat_e6 == 0 && e.first_lon_e6 == 0); + const bool has_last = !(e.last_lat_e6 == 0 && e.last_lon_e6 == 0); + if (!has_first || !has_last) { + if (out_dloc) + *out_dloc = 0; + + return false; + } + + // Optional freshness limit (avoid super old "following" marks) + const uint32_t MAX_AGE_MS = 10UL * 60UL * 1000UL; // 10 minutes + if (age_ms(now_ms, e.last_seen_ms) > MAX_AGE_MS) { + if (out_dloc) + *out_dloc = 0; + + return false; + } + + // Movement threshold: + // meters are e6 degrees = meters * 9 + const int32_t THRESH_E6 = (int32_t)(75 * 9); // ~75 m to-do: needs tuning + + int32_t dlat = iabs32(e.last_lat_e6 - e.first_lat_e6); + int32_t dlon = iabs32(e.last_lon_e6 - e.first_lon_e6); + + // Rough longitude scaling for mid-latitudes (~0.75) + dlon = (dlon * 3) / 4; + + // Cheap distance proxy + int32_t d = (dlat > dlon) ? dlat : dlon; + + if (out_dloc) *out_dloc = d; + + return d >= THRESH_E6; +} + +// Returns how many entries were written to out_top10 (0..10) +uint8_t WiFiScan::build_top10_for_ui(MacEntry* out_top10, MacSortMode mode) { + if (!out_top10) return 0; + + const uint32_t now_ms = millis(); + + int32_t top_idx[10]; + uint8_t top_count = 0; + + for (int i = 0; i < 10; i++) + top_idx[i] = -1; + + auto better = [&](uint32_t a_idx, uint32_t b_idx) -> bool { + const MacEntry& A = mac_entries[a_idx]; + const MacEntry& B = mac_entries[b_idx]; + + const bool A_follow = is_following_candidate_light(A, now_ms); + const bool B_follow = is_following_candidate_light(B, now_ms); + + // Following entries always rank ahead of non-following + if (A_follow != B_follow) + return A_follow && !B_follow; + + // Original sort rules + if (mode == MacSortMode::MOST_FRAMES) { + if (A.frame_count != B.frame_count) + return A.frame_count > B.frame_count; + + return age_ms(now_ms, A.last_seen_ms) < age_ms(now_ms, B.last_seen_ms); + } else { + const uint32_t ageA = age_ms(now_ms, A.last_seen_ms); + const uint32_t ageB = age_ms(now_ms, B.last_seen_ms); + if (ageA != ageB) + return ageA < ageB; + + return A.frame_count > B.frame_count; + } + }; + + for (uint32_t i = 0; i < mac_history_len; i++) { + if (mac_entry_state[i] != VALID_ENTRY) + continue; + + if (top_count < 10) { + int pos = (int)top_count; + while (pos > 0 && top_idx[pos - 1] >= 0 && better(i, (uint32_t)top_idx[pos - 1])) { + top_idx[pos] = top_idx[pos - 1]; + pos--; + } + top_idx[pos] = (int32_t)i; + top_count++; + continue; + } + + const int32_t worst_idx = top_idx[9]; + if (worst_idx < 0) + continue; + + if (better(i, (uint32_t)worst_idx)) { + int pos = 9; + while (pos > 0 && top_idx[pos - 1] >= 0 && better(i, (uint32_t)top_idx[pos - 1])) { + top_idx[pos] = top_idx[pos - 1]; + pos--; + } + top_idx[pos] = (int32_t)i; + } + } + + for (uint8_t k = 0; k < top_count; k++) { + const int32_t src = top_idx[k]; + if (src >= 0) { + int32_t dloc = 0; + out_top10[k] = mac_entries[(uint32_t)src]; + out_top10[k].following = is_following_candidate_light(out_top10[k], now_ms, &dloc); + out_top10[k].dloc = dloc; + } + } + + for (uint8_t k = top_count; k < 10; k++) { + memset(&out_top10[k], 0, sizeof(MacEntry)); + } + + return top_count; +} + + void WiFiScan::save_mac(unsigned char* mac) { //Save a MAC address into the recently seen array. if (this->mac_history_cursor >= mac_history_len) { @@ -2793,7 +3110,8 @@ void WiFiScan::RunPingScan(uint8_t scan_mode, uint16_t color) display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif this->current_scan_ip = this->gateway; - Serial.println("Cleared IPs: " + (String)this->clearIPs()); + Serial.print(F("Cleared IPs: ")); + Serial.println((String)this->clearIPs()); if (scan_mode == WIFI_PING_SCAN) Serial.println(F("Starting Ping Scan with...")); else if (scan_mode == WIFI_ARP_SCAN) @@ -3387,7 +3705,8 @@ void WiFiScan::setMac() { ((currentWiFiMode == WIFI_MODE_AP) || (currentWiFiMode == WIFI_MODE_APSTA) || (currentWiFiMode == WIFI_MODE_NULL))) Serial.printf("Failed to set AP MAC: %s | 0x%X\n", macToString(this->ap_mac), result); else if ((currentWiFiMode == WIFI_MODE_AP) || (currentWiFiMode == WIFI_MODE_APSTA) || (currentWiFiMode == WIFI_MODE_NULL)) - Serial.println("Successfully set AP MAC: " + macToString(this->ap_mac)); + Serial.print(F("Successfully set AP MAC: ")); + Serial.println(macToString(this->ap_mac)); // Do the station result = esp_wifi_set_mac(WIFI_IF_STA, this->sta_mac); @@ -3395,7 +3714,8 @@ void WiFiScan::setMac() { ((currentWiFiMode == WIFI_MODE_STA) || (currentWiFiMode == WIFI_MODE_APSTA))) Serial.printf("Failed to set STA MAC: %s | 0x%X\n", macToString(this->sta_mac), result); else if ((currentWiFiMode == WIFI_MODE_STA) || (currentWiFiMode == WIFI_MODE_APSTA)) - Serial.println("Successfully set STA MAC: " + macToString(this->sta_mac)); + Serial.print(F("Successfully set STA MAC: ")); + Serial.println(macToString(this->sta_mac)); } void WiFiScan::RunSetMac(uint8_t * mac, bool ap) { @@ -4870,8 +5190,10 @@ void WiFiScan::RunProbeScan(uint8_t scan_mode, uint16_t color) display_obj.tft.setTextColor(TFT_BLACK, color); #ifdef HAS_FULL_SCREEN display_obj.tft.fillRect(0,16,TFT_WIDTH,16, color); - if (scan_mode != BT_SCAN_FLOCK) + if (scan_mode == WIFI_SCAN_PROBE) display_obj.tft.drawCentreString(text_table4[40],TFT_WIDTH / 2,16,2); + else if (scan_mode == WIFI_SCAN_DETECT_FOLLOW) + display_obj.tft.drawCentreString("MAC Monitor",TFT_WIDTH / 2,16,2); else { Serial.println(F("Starting WiFi sniff for Flock...")); display_obj.tft.drawCentreString("Flock Sniff",TFT_WIDTH / 2,16,2); @@ -7271,6 +7593,34 @@ void WiFiScan::beaconSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type } } } + else if (wifi_scan_obj.currentScanMode == WIFI_SCAN_DETECT_FOLLOW) { + + // Skip if is a management frame that isn't probe request + /*if (type == WIFI_PKT_MGMT) { + if (snifferPacket->payload[0] != 0x40) + return; + }*/ + + char addr[] = "00:00:00:00:00:00"; + getMAC(addr, snifferPacket->payload, 10); + + int frame_check = wifi_scan_obj.update_mac_entry(src_addr); + + /*Serial.print(addr); + + if (frame_check == EMPTY_ENTRY) { + Serial.println(" Added to table."); + } + else if (frame_check == VALID_ENTRY) { + Serial.println(" Updated in table"); + } + else if (frame_check == TOMBSTONE_ENTRY) { + Serial.println(" Evicted"); + } + else { + Serial.println(" Frames: " + (String)mac_entries[frame_check].frame_count + " Last Seen: " + (String)(millis() - mac_entries[frame_check].last_seen_ms)); + }*/ + } } void WiFiScan::stationSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type) { @@ -10166,6 +10516,44 @@ void WiFiScan::portScan(uint8_t scan_mode, uint16_t targ_port) { } } +void WiFiScan::updateTrackerUI() { + MacEntry ui_list[10]; + uint8_t n = this->build_top10_for_ui(ui_list, MacSortMode::MOST_FRAMES); + + #ifdef HAS_SCREEN + display_obj.tft.fillRect(0, + (STATUS_BAR_WIDTH * 2) + 1 + EXT_BUTTON_WIDTH, + TFT_WIDTH, + TFT_HEIGHT - STATUS_BAR_WIDTH + 1, + TFT_BLACK); + display_obj.tft.setCursor(0, (STATUS_BAR_WIDTH * 2) + CHAR_WIDTH + EXT_BUTTON_WIDTH); + display_obj.tft.setTextSize(1); + #endif + + Serial.println(F("---------------")); + + for (int i = 0; i < n; i++) { + if (ui_list[i].following) { + #ifdef HAS_SCREEN + display_obj.tft.setTextColor(TFT_RED, TFT_BLACK); + #endif + Serial.print(F("FOLLOWING ")); + } + else { + #ifdef HAS_SCREEN + display_obj.tft.setTextColor(TFT_WHITE, TFT_BLACK); + #endif + } + + #ifdef HAS_SCREEN + display_obj.tft.println(macToString(ui_list[i].mac) + " Tx: " + (String)ui_list[i].frame_count + " " + (String)((millis() - ui_list[i].last_seen_ms) / 1000) + "s " + (String)ui_list[i].dloc); + #endif + + Serial.print(macToString(ui_list[i].mac)); + Serial.println(" Frames: " + (String)ui_list[i].frame_count + " Last Seen: " + (String)((millis() - ui_list[i].last_seen_ms) / 1000) + "s"); + } +} + // Function for updating scan status void WiFiScan::main(uint32_t currentTime) @@ -10189,6 +10577,17 @@ void WiFiScan::main(uint32_t currentTime) channelHop(); } } + else if (currentScanMode == WIFI_SCAN_DETECT_FOLLOW) { + if (currentTime - initTime >= this->channel_hop_delay * HOP_DELAY) { + initTime = millis(); + channelHop(); + } + + if (currentTime - this->last_ui_update >= 1000) { + this->last_ui_update = millis(); + this->updateTrackerUI(); + } + } else if ((currentScanMode == BT_SCAN_FLOCK) || (currentScanMode == BT_SCAN_FLOCK_WARDRIVE) || (currentScanMode == BT_SCAN_WAR_DRIVE) || diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index 209e469..fe3fdf7 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -144,6 +144,7 @@ #define BT_SCAN_SIMPLE 73 #define BT_SCAN_SIMPLE_TWO 74 #define BT_SCAN_FLOCK_WARDRIVE 75 +#define WIFI_SCAN_DETECT_FOLLOW 76 #define WIFI_ATTACK_FUNNY_BEACON 99 @@ -219,6 +220,24 @@ esp_err_t esp_wifi_80211_tx(wifi_interface_t ifx, const void *buffer, int len, b esp_err_t esp_base_mac_addr_set(uint8_t *Mac); #endif +#define EMPTY_ENTRY 0 +#define VALID_ENTRY 1 +#define TOMBSTONE_ENTRY 2 + +#pragma pack(push, 1) +struct MacEntry { + uint8_t mac[6]; + uint32_t last_seen_ms; + uint16_t frame_count; + int32_t first_lat_e6; + int32_t first_lon_e6; + int32_t last_lat_e6; + int32_t last_lon_e6; + bool following; + int32_t dloc; +}; +#pragma pack(pop) + struct AirTag { String mac; // MAC address of the AirTag std::vector payload; // Payload data @@ -237,6 +256,11 @@ struct Flipper { extern struct mac_addr* mac_history; #endif +enum class MacSortMode : uint8_t { + MOST_RECENT, + MOST_FRAMES +}; + class WiFiScan { private: @@ -281,6 +305,7 @@ class WiFiScan //int num_deauth = 0; // RED uint32_t initTime = 0; + uint32_t last_ui_update = 0; bool run_setup = true; void initWiFi(uint8_t scan_mode); uint8_t bluetoothScanTime = 5; @@ -525,6 +550,7 @@ class WiFiScan NimBLEAdvertisementData GetUniversalAdvertisementData(EBLEPayloadType type); #endif + void updateTrackerUI(); void showNetworkInfo(); void setNetworkInfo(); void fullARP(); @@ -540,6 +566,7 @@ class WiFiScan uint8_t getSecurityType(const uint8_t* beacon, uint16_t len); void addAnalyzerValue(int16_t value, int rssi_avg, int16_t target_array[], int array_size); bool mac_cmp(struct mac_addr addr1, struct mac_addr addr2); + bool mac_cmp(uint8_t addr1[6], uint8_t addr2[6]); void clearMacHistory(); void executeWarDrive(); void executeSourApple(); @@ -610,6 +637,9 @@ class WiFiScan //LinkedList* ssids; + static MacEntry mac_entries[mac_history_len]; + static uint8_t mac_entry_state[mac_history_len]; + // Stuff for RAW stats uint32_t mgmt_frames = 0; uint32_t data_frames = 0; @@ -730,7 +760,12 @@ class WiFiScan #ifdef HAS_SCREEN int8_t checkAnalyzerButtons(uint32_t currentTime); #endif - bool seen_mac(unsigned char* mac); + bool seen_mac(unsigned char* mac, bool simple = true); + int16_t seen_mac_int(unsigned char* mac, bool simple = true); + int update_mac_entry(const uint8_t mac[6]); + inline void insert_mac_entry(uint32_t idx, const uint8_t mac[6], uint32_t now_ms); + void evict_and_insert(const uint8_t mac[6], uint32_t now_ms); + uint8_t build_top10_for_ui(MacEntry* out_top10, MacSortMode mode); void save_mac(unsigned char* mac); #ifdef HAS_BT void copyNimbleMac(const BLEAddress &addr, unsigned char out[6]); @@ -811,5 +846,6 @@ class WiFiScan static void pineScanSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); // Pineapple static int extractPineScanChannel(const uint8_t* payload, int len); // Pineapple static void multiSSIDSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); // MultiSSID + static inline uint32_t hash_mac(const uint8_t mac[6]); }; #endif diff --git a/esp32_marauder/configs.h b/esp32_marauder/configs.h index 243e550..3427fb9 100644 --- a/esp32_marauder/configs.h +++ b/esp32_marauder/configs.h @@ -32,10 +32,12 @@ //#define MARAUDER_V8 //// END BOARD TARGETS - #define MARAUDER_VERSION "v1.9.1" + #define MARAUDER_VERSION "v1.10.0" #define GRAPH_REFRESH 100 + #define TRACK_EVICT_SEC 120 // Seconds before marking tracked MAC as tombstone + #define DUAL_BAND_CHANNELS 51 //// HARDWARE NAMES diff --git a/esp32_marauder/utils.h b/esp32_marauder/utils.h index 8785636..3bc6157 100644 --- a/esp32_marauder/utils.h +++ b/esp32_marauder/utils.h @@ -156,6 +156,14 @@ String macToString(uint8_t macAddr[6]) { return String(macStr); } +String macToString(const uint8_t macAddr[6]) { + char macStr[18]; // 17 characters for "XX:XX:XX:XX:XX:XX" + 1 null terminator + snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", + macAddr[0], macAddr[1], macAddr[2], + macAddr[3], macAddr[4], macAddr[5]); + return String(macStr); +} + void convertMacStringToUint8(const String& macStr, uint8_t macAddr[6]) { // Ensure the input string is in the format "XX:XX:XX:XX:XX:XX" if (macStr.length() != 17) {