Compare commits

..

23 Commits

Author SHA1 Message Date
Just Call Me Koko
a20b25a39c Merge pull request #1034 from justcallmekoko/develop
Fix eviction criteria and work mini displays
2026-01-06 16:35:03 -05:00
Just Call Me Koko
cb4a6cd51e Fix eviction criteria and work mini displays 2026-01-06 13:47:06 -05:00
Just Call Me Koko
a785a3b125 Merge pull request #1033 from justcallmekoko/develop
Add MAC tracking backend
2026-01-05 20:55:24 -05:00
Just Call Me Koko
7a7c01512b Fix non-display hardware 2026-01-05 20:29:42 -05:00
Just Call Me Koko
60f446245d Add location for MAC tracking 2026-01-05 19:02:02 -05:00
Just Call Me Koko
fb73afd359 Add backend for MAC tracking 2026-01-05 00:17:49 -05:00
Just Call Me Koko
337cf18dac Add menu item for mac monitor 2026-01-04 03:03:50 -05:00
Just Call Me Koko
380875d32f Merge pull request #1029 from justcallmekoko/develop
Update version number
2026-01-03 14:50:37 -05:00
Just Call Me Koko
ae79653628 Update version number 2026-01-03 14:44:51 -05:00
Just Call Me Koko
d03c079080 Merge pull request #1028 from justcallmekoko/develop
Fix wardriving function on CLI
2026-01-03 14:29:58 -05:00
Just Call Me Koko
70154f5074 Fix wardriving function on CLI 2026-01-03 14:19:36 -05:00
Just Call Me Koko
8ef5cf3b92 Merge pull request #1015 from justcallmekoko/develop
Faster display buffer
2025-12-13 14:48:34 -05:00
Just Call Me Koko
b3eade1e06 Faster display buffer 2025-12-13 14:43:08 -05:00
Just Call Me Koko
1dd5502c1d Merge pull request #1012 from justcallmekoko/develop
Fix crash during BT wardrive
2025-12-12 18:11:10 -05:00
Just Call Me Koko
1862a5b812 Fix crash during BT wardrive 2025-12-12 18:06:07 -05:00
Just Call Me Koko
f5375eedaf Merge pull request #1006 from justcallmekoko/develop
Wardriving is asynchronous now
2025-12-08 16:45:31 -05:00
Just Call Me Koko
2910addb8a Ensure async wardrive 2025-12-08 16:32:25 -05:00
Just Call Me Koko
be3971d081 Wardriving is async 2025-12-08 15:40:24 -05:00
Just Call Me Koko
86e2fb206a Merge pull request #1005 from justcallmekoko/develop
Add wifi to flock wardrive
2025-12-08 15:10:53 -05:00
Just Call Me Koko
431fa175a0 Update WiFiScan.cpp 2025-12-08 15:04:09 -05:00
Just Call Me Koko
976687980c Add wifi to flock wardrive 2025-12-08 14:53:55 -05:00
Just Call Me Koko
1d419605f7 Merge pull request #1001 from justcallmekoko/develop
Fix menu navigation for back button
2025-12-04 18:36:50 -05:00
Just Call Me Koko
1624d98183 Fix menu navigation for back button 2025-12-04 18:12:28 -05:00
11 changed files with 655 additions and 46 deletions

View File

@@ -571,7 +571,7 @@ void CommandLine::runCommand(String input) {
int sta_sw = this->argSearch(&cmd_args, "-s");
int flk_sw = this->argSearch(&cmd_args, "-f");
if (flk_sw == -1) {
if (flk_sw != -1) {
Serial.println("Starting Flock Wardrive. Stop with " + (String)STOPSCAN_CMD);
#ifdef HAS_SCREEN
display_obj.clearScreen();

View File

@@ -475,33 +475,37 @@ void Display::processAndPrintString(TFT_eSPI& tft, const String& originalString)
String new_string = originalString;
// Check for color macros at the start of the string
if (new_string.startsWith(RED_KEY)) {
text_color = TFT_RED;
new_string.remove(0, strlen(RED_KEY)); // Remove the macro
} else if (new_string.startsWith(GREEN_KEY)) {
text_color = TFT_GREEN;
new_string.remove(0, strlen(GREEN_KEY)); // Remove the macro
} else if (new_string.startsWith(CYAN_KEY)) {
text_color = TFT_CYAN;
new_string.remove(0, strlen(CYAN_KEY)); // Remove the macro
} else if (new_string.startsWith(WHITE_KEY)) {
text_color = TFT_WHITE;
new_string.remove(0, strlen(WHITE_KEY)); // Remove the macro
} else if (new_string.startsWith(MAGENTA_KEY)) {
text_color = TFT_MAGENTA;
new_string.remove(0, strlen(MAGENTA_KEY)); // Remove the macro
if (new_string.startsWith(";")) {
if (new_string.startsWith(RED_KEY)) {
text_color = TFT_RED;
new_string.remove(0, strlen(RED_KEY)); // Remove the macro
} else if (new_string.startsWith(GREEN_KEY)) {
text_color = TFT_GREEN;
new_string.remove(0, strlen(GREEN_KEY)); // Remove the macro
} else if (new_string.startsWith(CYAN_KEY)) {
text_color = TFT_CYAN;
new_string.remove(0, strlen(CYAN_KEY)); // Remove the macro
} else if (new_string.startsWith(WHITE_KEY)) {
text_color = TFT_WHITE;
new_string.remove(0, strlen(WHITE_KEY)); // Remove the macro
} else if (new_string.startsWith(MAGENTA_KEY)) {
text_color = TFT_MAGENTA;
new_string.remove(0, strlen(MAGENTA_KEY)); // Remove the macro
}
}
String spaces = String(' ', TFT_WIDTH / CHAR_WIDTH);
// Set text color and print the string
tft.setTextColor(text_color, background_color);
tft.print(new_string);
tft.print(new_string + spaces);
}
void Display::displayBuffer(bool do_clear)
{
if (this->display_buffer->size() > 0)
{
int print_count = 1;
int print_count = 10;
while ((display_buffer->size() > 0) && (print_count > 0))
{
@@ -530,9 +534,9 @@ void Display::displayBuffer(bool do_clear)
screen_buffer->add(display_buffer->shift());
for (int i = 0; i < this->screen_buffer->size(); i++) {
tft.setCursor(xPos, (i * 12) + (SCREEN_HEIGHT / 6));
String spaces = String(' ', TFT_WIDTH / CHAR_WIDTH);
tft.print(spaces);
//tft.setCursor(xPos, (i * 12) + (SCREEN_HEIGHT / 6));
//String spaces = String(' ', TFT_WIDTH / CHAR_WIDTH);
//tft.print(spaces);
tft.setCursor(xPos, (i * 12) + (SCREEN_HEIGHT / 6));
this->processAndPrintString(tft, this->screen_buffer->get(i));

View File

@@ -13,7 +13,7 @@
#include <LinkedList.h>
#include <SPI.h>
//#include <lvgl.h>
#include <Ticker.h>
//#include <Ticker.h>
#include "SPIFFS.h"
#include "Assets.h"

View File

@@ -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;
}

View File

@@ -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 = "";

View File

@@ -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
@@ -2039,6 +2046,8 @@ void MenuFunctions::RunSetup()
});
this->addNodes(&wifiGeneralMenu, "View AP Info", TFTCYAN, NULL, KEYBOARD_ICO, [this](){
wifiAPMenu.parentMenu = &wifiGeneralMenu;
// Add the back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
@@ -2074,6 +2083,8 @@ void MenuFunctions::RunSetup()
// Select Stations on Mini v2
this->addNodes(&wifiGeneralMenu, "Select Stations", TFTCYAN, NULL, KEYBOARD_ICO, [this](){
wifiAPMenu.parentMenu = &wifiGeneralMenu;
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
this->changeMenu(wifiAPMenu.parentMenu, true);
@@ -2140,6 +2151,9 @@ void MenuFunctions::RunSetup()
});
this->addNodes(&wifiGeneralMenu, "Join WiFi", TFTWHITE, NULL, KEYBOARD_ICO, [this](){
wifiAPMenu.parentMenu = &wifiGeneralMenu;
// Add the back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
@@ -2186,6 +2200,8 @@ void MenuFunctions::RunSetup()
this->changeMenu(&wifiGeneralMenu, true);
}
else {
wifiAPMenu.parentMenu = &wifiGeneralMenu;
// Add the back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
@@ -2303,6 +2319,8 @@ void MenuFunctions::RunSetup()
// Clone AP MAC to ESP32 for button folks
//#ifndef HAS_ILI9341
this->addNodes(&setMacMenu, "Clone AP MAC", TFTRED, NULL, CLEAR_ICO, [this](){
wifiAPMenu.parentMenu = &wifiGeneralMenu;
// Add the back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
@@ -2321,6 +2339,8 @@ void MenuFunctions::RunSetup()
});
this->addNodes(&setMacMenu, "Clone STA MAC", TFTMAGENTA, NULL, CLEAR_ICO, [this](){
wifiAPMenu.parentMenu = &wifiGeneralMenu;
// Add the back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFTLIGHTGREY, NULL, 0, [this]() {
@@ -2474,6 +2494,8 @@ void MenuFunctions::RunSetup()
#ifdef HAS_BT
// Select Airtag on Mini
this->addNodes(&bluetoothAttackMenu, "Spoof Airtag", TFTWHITE, NULL, ATTACKS, [this](){
wifiAPMenu.parentMenu = &bluetoothAttackMenu;
// Clear nodes and add back button
wifiAPMenu.list->clear();
this->addNodes(&wifiAPMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() {

View File

@@ -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;

View File

@@ -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;
@@ -432,7 +435,16 @@ extern "C" {
if (gps_obj.getGpsModuleStatus()) {
bool do_save = false;
if (buf >= 0)
{
{
unsigned char mac_char[6];
wifi_scan_obj.copyNimbleMac(advertisedDevice->getAddress(), mac_char);
if (wifi_scan_obj.currentScanMode != BT_SCAN_WAR_DRIVE_CONT) {
if (wifi_scan_obj.seen_mac(mac_char))
return;
}
Serial.print(F("Device: "));
if(advertisedDevice->getName().length() != 0)
{
@@ -476,6 +488,10 @@ extern "C" {
if (do_save)
buffer_obj.append(wardrive_line);
if (wifi_scan_obj.currentScanMode != BT_SCAN_WAR_DRIVE_CONT) {
wifi_scan_obj.save_mac(mac_char);
}
}
}
#endif
@@ -1153,6 +1169,14 @@ extern "C" {
bool do_save = false;
if (buf >= 0)
{
unsigned char mac_char[6];
wifi_scan_obj.copyNimbleMac(advertisedDevice->getAddress(), mac_char);
if (wifi_scan_obj.currentScanMode != BT_SCAN_WAR_DRIVE_CONT) {
if (wifi_scan_obj.seen_mac(mac_char))
return;
}
Serial.print(F("Device: "));
if(advertisedDevice->getName().length() != 0)
{
@@ -1196,6 +1220,10 @@ extern "C" {
if (do_save)
buffer_obj.append(wardrive_line);
if (wifi_scan_obj.currentScanMode != BT_SCAN_WAR_DRIVE_CONT) {
wifi_scan_obj.save_mac(mac_char);
}
}
}
#endif
@@ -1714,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"},
@@ -2074,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)
@@ -2430,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))
{
@@ -2499,6 +2533,7 @@ void WiFiScan::StopScan(uint8_t scan_mode)
#endif
this->shutdownBLE();
this->ble_scanning = false;
#endif
}
@@ -2582,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
@@ -2596,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;
@@ -2604,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;
@@ -2612,6 +2658,313 @@ 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], int8_t rssi) {
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, rssi);
} else {
insert_mac_entry(idx, mac, now_ms, rssi);
}
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++;
}
mac_entries[idx].rssi = rssi;
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, int8_t rssi) {
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_entries[idx].rssi = rssi;
mac_entry_state[idx] = VALID_ENTRY;
}
void WiFiScan::evict_and_insert(const uint8_t mac[6], uint32_t now_ms) {
const uint32_t EVICT_AGE_MS = TRACK_EVICT_SEC * 1000UL;
// 1) Prefer reusing a tombstone if any exist.
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;
}
}
// Candidate among "expired" (age > EVICT_AGE_MS): lowest frame_count, then oldest
int32_t victim_expired = -1;
uint16_t victim_expired_frames = 0xFFFF;
uint32_t victim_expired_age = 0;
// Fallback candidate among all VALID: lowest frame_count, then oldest
int32_t victim_any = -1;
uint16_t victim_any_frames = 0xFFFF;
uint32_t victim_any_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);
const uint16_t frames = mac_entries[i].frame_count;
// Fallback (any valid): lowest frames, then oldest
if (victim_any < 0 ||
frames < victim_any_frames ||
(frames == victim_any_frames && age > victim_any_age)) {
victim_any = (int32_t)i;
victim_any_frames = frames;
victim_any_age = age;
}
// Expired group: lowest frames, then oldest (only if age exceeds threshold)
if (age > EVICT_AGE_MS) {
if (victim_expired < 0 ||
frames < victim_expired_frames ||
(frames == victim_expired_frames && age > victim_expired_age)) {
victim_expired = (int32_t)i;
victim_expired_frames = frames;
victim_expired_age = age;
}
}
}
// Choose victim: prefer expired-group, else fallback group
const int32_t victim = (victim_expired >= 0) ? victim_expired : victim_any;
if (victim >= 0) {
// Save evicted MAC for logging
uint8_t evicted_mac[6];
memcpy(evicted_mac, mac_entries[victim].mac, 6);
// Overwrite victim with new entry
insert_mac_entry((uint32_t)victim, mac, now_ms);
Serial.println(macToString(evicted_mac) + " expired");
return;
}
// If table is somehow inconsistent, 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) {
@@ -2767,7 +3120,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)
@@ -3361,7 +3715,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);
@@ -3369,7 +3724,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) {
@@ -4414,19 +4770,37 @@ void WiFiScan::executeWarDrive() {
bool do_save;
String display_string;
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) {
/*while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) {
Serial.println(F("Scan running..."));
delay(500);
}*/
int scan_status = WiFi.scanComplete();
if (scan_status == WIFI_SCAN_RUNNING) {
delay(1);
return;
}
else if (scan_status == WIFI_SCAN_FAILED) {
Serial.println("WiFi scan failed to start. Restarting...");
this->wifi_initialized = true;
this->shutdownWiFi();
this->startWardriverWiFi();
this->wifi_initialized = true;
delay(100);
}
#ifndef HAS_DUAL_BAND
/*#ifndef HAS_DUAL_BAND
int n = WiFi.scanNetworks(false, true, false, 110, this->set_channel);
#else
int n = WiFi.scanNetworks(false, true, false, 110);
#endif
#endif*/
if (n > 0) {
for (int i = 0; i < n; i++) {
bool do_continue = false;
if (scan_status > 0) {
for (int i = 0; i < scan_status; i++) {
do_continue = true;
display_string = "";
do_save = false;
uint8_t *this_bssid_raw = WiFi.BSSID(i);
@@ -4439,6 +4813,24 @@ void WiFiScan::executeWarDrive() {
this->save_mac(this_bssid_raw);
String ssid = WiFi.SSID(i);
//Serial.println(ssid);
if (this->currentScanMode == BT_SCAN_FLOCK_WARDRIVE) {
for (int x = 0; x < sizeof(flock_ssid)/sizeof(this->flock_ssid[0]); x++) {
//Serial.print("Comparing ");
//Serial.print(ssid);
//Serial.print(" to ");
//Serial.println(this->flock_ssid[x]);
if (strcasestr(ssid.c_str(), this->flock_ssid[x])) {
do_continue = false;
break;
}
}
if (do_continue)
continue;
}
ssid.replace(",","_");
if (ssid != "") {
@@ -4476,11 +4868,17 @@ void WiFiScan::executeWarDrive() {
buffer_obj.append(wardrive_line);
}
}
}
this->channelHop();
// Free up that memory, you sexy devil
WiFi.scanDelete();
// Free up that memory, you sexy devil
WiFi.scanDelete();
}
/*#ifndef HAS_DUAL_BAND
this->channelHop();
#endif*/
if (!this->ble_scanning)
WiFi.scanNetworks(true, true, false, 80);
}
#endif
}
@@ -4802,8 +5200,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);
@@ -4941,6 +5341,8 @@ 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_WAR_DRIVE) ||
(scan_mode == BT_SCAN_WAR_DRIVE_CONT) ||
(scan_mode == BT_SCAN_AIRTAG) ||
(scan_mode == BT_SCAN_AIRTAG_MON) ||
(scan_mode == BT_SCAN_FLIPPER) ||
@@ -4949,6 +5351,28 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color)
(scan_mode == BT_SCAN_SIMPLE) ||
(scan_mode == BT_SCAN_SIMPLE_TWO))
{
#ifdef HAS_GPS
if (gps_obj.getGpsModuleStatus()) {
if (scan_mode == BT_SCAN_WAR_DRIVE) {
startLog("bt_wardrive");
}
else if (scan_mode == BT_SCAN_WAR_DRIVE_CONT) {
startLog("bt_wardrive_cont");
}
else if (scan_mode == BT_SCAN_FLOCK_WARDRIVE) {
startLog("flock_wardrive");
this->startWardriverWiFi();
this->wifi_initialized = true;
}
String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n";
buffer_obj.append(header_line);
} else {
return;
}
#else
return;
#endif
#ifdef HAS_SCREEN
display_obj.TOP_FIXED_AREA_2 = 48;
display_obj.tteBar = true;
@@ -4973,6 +5397,10 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color)
display_obj.tft.drawCentreString("Simple Sniff", TFT_WIDTH / 2, 16, 2);
else if (scan_mode == BT_SCAN_SIMPLE_TWO)
display_obj.tft.drawCentreString("Simple Sniff 2", TFT_WIDTH / 2, 16, 2);
if (scan_mode == BT_SCAN_WAR_DRIVE)
display_obj.tft.drawCentreString("BT Wardrive",TFT_WIDTH / 2,16,2);
else if (scan_mode == BT_SCAN_WAR_DRIVE_CONT)
display_obj.tft.drawCentreString("BT Wardrive Continuous",TFT_WIDTH / 2,16,2);
#ifdef HAS_ILI9341
display_obj.touchToExit();
#endif
@@ -4987,6 +5415,8 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color)
pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false);
#endif
else if ((scan_mode == BT_SCAN_FLIPPER) ||
(scan_mode == BT_SCAN_WAR_DRIVE) ||
(scan_mode == BT_SCAN_WAR_DRIVE_CONT) ||
(scan_mode == BT_SCAN_FLOCK) ||
(scan_mode == BT_SCAN_FLOCK_WARDRIVE) ||
(scan_mode == BT_SCAN_SIMPLE) ||
@@ -5016,6 +5446,8 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color)
}
else if (scan_mode == BT_SCAN_FLOCK_WARDRIVE) {
startLog("flock_wardrive");
this->startWardriverWiFi();
this->wifi_initialized = true;
}
String header_line = "WigleWifi-1.4,appRelease=" + (String)MARAUDER_VERSION + ",model=ESP32 Marauder,release=" + (String)MARAUDER_VERSION + ",device=ESP32 Marauder,display=SPI TFT,board=ESP32 Marauder,brand=JustCallMeKoko\nMAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type\n";
buffer_obj.append(header_line);
@@ -5058,8 +5490,7 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color)
#endif
}
else if (scan_mode == BT_SCAN_SKIMMERS)
{
else if (scan_mode == BT_SCAN_SKIMMERS) {
#ifdef HAS_SCREEN
display_obj.TOP_FIXED_AREA_2 = 160;
display_obj.tteBar = true;
@@ -7172,6 +7603,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, snifferPacket->rx_ctrl.rssi);
/*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) {
@@ -10067,6 +10526,49 @@ 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
#ifndef HAS_MINI_SCREEN
display_obj.tft.println((String)ui_list[i].rssi + " " + 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);
#else
String mac_str = macToString(ui_list[i].mac);
display_obj.tft.println(mac_str.substring(mac_str.length() / 2) + " Tx: " + (String)ui_list[i].frame_count + " " + (String)((millis() - ui_list[i].last_seen_ms) / 1000) + "s ");
#endif
#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)
@@ -10090,6 +10592,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) ||
@@ -10105,13 +10618,21 @@ void WiFiScan::main(uint32_t currentTime)
this->ble_scanning = false;
}
else {
pBLEScan->start(0, scanCompleteCB, false);
this->ble_scanning = true;
return;
if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) {
pBLEScan->start(0, scanCompleteCB, false);
this->ble_scanning = true;
return;
}
}
#endif
if (currentScanMode == BT_SCAN_FLOCK)
channelHop();
else if (currentScanMode == BT_SCAN_FLOCK_WARDRIVE) {
#ifdef HAS_GPS
if (gps_obj.getGpsModuleStatus())
this->executeWarDrive();
#endif
}
}
}
else if (currentScanMode == WIFI_PING_SCAN) {

View File

@@ -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,25 @@ 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;
int8_t rssi;
};
#pragma pack(pop)
struct AirTag {
String mac; // MAC address of the AirTag
std::vector<uint8_t> payload; // Payload data
@@ -237,6 +257,11 @@ struct Flipper {
extern struct mac_addr* mac_history;
#endif
enum class MacSortMode : uint8_t {
MOST_RECENT,
MOST_FRAMES
};
class WiFiScan
{
private:
@@ -281,6 +306,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 +551,7 @@ class WiFiScan
NimBLEAdvertisementData GetUniversalAdvertisementData(EBLEPayloadType type);
#endif
void updateTrackerUI();
void showNetworkInfo();
void setNetworkInfo();
void fullARP();
@@ -540,6 +567,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 +638,9 @@ class WiFiScan
//LinkedList<ssid>* 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 +761,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], int8_t rssi = 0);
inline void insert_mac_entry(uint32_t idx, const uint8_t mac[6], uint32_t now_ms, int8_t rssi = 0);
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 +847,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

View File

@@ -32,10 +32,12 @@
//#define MARAUDER_V8
//// END BOARD TARGETS
#define MARAUDER_VERSION "v1.9.0"
#define MARAUDER_VERSION "v1.10.0"
#define GRAPH_REFRESH 100
#define TRACK_EVICT_SEC 90 // Seconds before marking tracked MAC as tombstone
#define DUAL_BAND_CHANNELS 51
//// HARDWARE NAMES

View File

@@ -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) {