Flipper Zero/Rogue AP Detector/analyzer.c
From charlesreid1
Main article: Flipper Zero/Rogue AP Detector
Requirements
- Create analysis results that are quantitative (score 0-100) but also human-readable
- A Levenshtein distance algorithm to detect typosquatting in SSIDs (but not run frequently, as it is computationally intensive)
- An embedded OUI database to identify the manufacturer of an AP's network card
analyzer.h
#ifndef ANALYZER_H #define ANALYZER_H #include "scanner.h" #include "baseline.h" // Threshold for SSID similarity. A distance of 1 or 2 is highly suspicious. #define LEVENSHTEIN_EVIL_TWIN_THRESHOLD 2 // How far outside the baseline RSSI an AP can be before it's considered anomalous. #define RSSI_ANOMALY_THRESHOLD_DBM 10 /** * @brief Enumeration of potential threat types identified by the analyzer. */ typedef enum { THREAT_NONE, // AP is known and operating within normal parameters. THREAT_EVIL_TWIN, // An unknown AP with a very similar SSID to a known one. THREAT_SIGNAL_ANOMALY, // A known AP is suddenly much stronger or weaker. THREAT_UNEXPECTED_CONFIG, // A known AP has changed its SSID or encryption. THREAT_UNKNOWN_DEVICE, // An unknown AP that doesn't match any evil twin patterns. } ThreatType; /** * @brief Structure to hold the complete result of an AP analysis. */ typedef struct { ap_info_t scanned_ap; // A copy of the AP that was analyzed. int suspicion_score; // Final score from 0 (safe) to 100 (highly suspicious). ThreatType threat_type; // The classification of the threat. const char* reason; // A human-readable explanation for the score. const char* manufacturer; // The manufacturer of the AP, based on its OUI. } AnalysisResult; /** * @brief The core analysis function. * It analyzes a single scanned AP against the entire baseline of legitimate APs. * * @param manager Pointer to the BaselineManager containing known APs. * @param scanned_ap Pointer to the AP information from a recent scan. * @return An AnalysisResult struct containing the verdict. */ AnalysisResult analyze_ap(BaselineManager* manager, const ap_info_t* scanned_ap); /** * @brief A helper function to get a string representation of a threat type. * @param threat The ThreatType enum. * @return A constant string describing the threat. */ const char* threat_type_to_string(ThreatType threat); #endif // ANALYZER_H
analyzer.c
#include "analyzer.h" #include <string.h> #include <stdlib.h> // For MIN/MAX macros #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif // --- OUI (Organizationally Unique Identifier) Database --- // This small, embedded database helps identify the manufacturer of an AP. typedef struct { uint8_t oui[3]; const char* manufacturer; } OuiEntry; // A selection of common WiFi hardware vendors. This can be expanded. static const OuiEntry OUI_DATABASE[] = { {{0x00, 0x0F, 0xAC}, "TP-Link"}, {{0x00, 0x1A, 0x70}, "Cisco"}, {{0x00, 0x0B, 0x86}, "Aruba Networks"}, {{0x00, 0x90, 0x4C}, "NETGEAR"}, {{0x08, 0x00, 0x28}, "Dell"}, {{0x18, 0x64, 0x72}, "ASUS"}, {{0x20, 0xCF, 0x30}, "Belkin"}, {{0x44, 0xD9, 0xE7}, "Ubiquiti Networks"}, {{0x68, 0x72, 0x51}, "Linksys"}, {{0x88, 0xDC, 0x96}, "D-Link"}, {{0xB0, 0x39, 0x56}, "Google"}, {{0xE0, 0x4F, 0x43}, "Hewlett Packard"}, {{0xF8, 0xE4, 0xFB}, "Amazon"}, }; static const size_t OUI_DATABASE_SIZE = sizeof(OUI_DATABASE) / sizeof(OuiEntry); // --- Forward Declarations for internal functions --- static int levenshtein_distance(const char* s1, const char* s2); static const char* find_manufacturer_by_oui(const uint8_t bssid[6]); // --- Public Function Implementations --- const char* threat_type_to_string(ThreatType threat) { switch(threat) { case THREAT_NONE: return "Known AP"; case THREAT_EVIL_TWIN: return "Evil Twin"; case THREAT_SIGNAL_ANOMALY: return "Signal Anomaly"; case THREAT_UNEXPECTED_CONFIG: return "Config Change"; case THREAT_UNKNOWN_DEVICE: return "Unknown Device"; default: return "N/A"; } } AnalysisResult analyze_ap(BaselineManager* manager, const ap_info_t* scanned_ap) { AnalysisResult result = {0}; memcpy(&result.scanned_ap, scanned_ap, sizeof(ap_info_t)); result.threat_type = THREAT_NONE; result.suspicion_score = 0; result.reason = "Known AP, operating normally."; result.manufacturer = find_manufacturer_by_oui(scanned_ap->bssid); if(furi_mutex_acquire(manager->mutex, FuriWaitForever) != FuriStatusOk) { result.reason = "Error: Could not access baseline."; return result; } baseline_ap_t* matched_ap = NULL; for(uint16_t i = 0; i < manager->count; i++) { if(memcmp(manager->baseline_aps[i].bssid, scanned_ap->bssid, 6) == 0) { matched_ap = &manager->baseline_aps[i]; break; } } // CASE 1: The AP's BSSID is in our baseline (it's a known device) if(matched_ap) { // Check for signal strength anomaly if(scanned_ap->rssi < (matched_ap->rssi_min - RSSI_ANOMALY_THRESHOLD_DBM) || scanned_ap->rssi > (matched_ap->rssi_max + RSSI_ANOMALY_THRESHOLD_DBM)) { result.threat_type = THREAT_SIGNAL_ANOMALY; result.suspicion_score = 40; result.reason = "Known AP has unusual signal strength."; } // Check for unexpected changes to SSID or encryption else if(strcmp(matched_ap->ssid, scanned_ap->ssid) != 0 || matched_ap->encryption != scanned_ap->encryption) { result.threat_type = THREAT_UNEXPECTED_CONFIG; result.suspicion_score = 75; result.reason = "Known AP has changed its configuration."; } } // CASE 2: The AP's BSSID is NOT in our baseline (it's an unknown device) else { result.threat_type = THREAT_UNKNOWN_DEVICE; result.suspicion_score = 25; // Base score for any unknown device result.reason = "Unknown AP detected in the area."; // Check for SSID similarity (potential Evil Twin) for(uint16_t i = 0; i < manager->count; i++) { int distance = levenshtein_distance(scanned_ap->ssid, manager->baseline_aps[i].ssid); if(distance > 0 && distance <= LEVENSHTEIN_EVIL_TWIN_THRESHOLD) { result.threat_type = THREAT_EVIL_TWIN; result.suspicion_score = 95; result.reason = "SSID is deceptively similar to a known AP."; break; // Found a likely evil twin, no need to check further } } } furi_mutex_release(manager->mutex); return result; } // --- Internal (Static) Function Implementations --- /** * @brief Finds the manufacturer name for a given BSSID. * @return Manufacturer name or "Unknown". */ static const char* find_manufacturer_by_oui(const uint8_t bssid[6]) { for(size_t i = 0; i < OUI_DATABASE_SIZE; i++) { if(memcmp(OUI_DATABASE[i].oui, bssid, 3) == 0) { return OUI_DATABASE[i].manufacturer; } } return "Unknown"; } /** * @brief Calculates the Levenshtein distance between two strings. * This is a measure of how different two strings are. * @return The number of edits (insertions, deletions, substitutions) to change s1 to s2. */ static int levenshtein_distance(const char* s1, const char* s2) { int s1_len = strlen(s1); int s2_len = strlen(s2); // Use heap for the matrix to avoid stack overflow with long SSIDs int* d = malloc((s2_len + 1) * sizeof(int)); if(!d) return 1000; // Return a large number on allocation failure for(int i = 0; i <= s2_len; i++) { d[i] = i; } for(int i = 0; i < s1_len; i++) { int old = d[0]; d[0] = i + 1; for(int j = 0; j < s2_len; j++) { int temp = d[j+1]; int cost = (s1[i] == s2[j]) ? 0 : 1; d[j+1] = MIN(MIN(d[j] + 1, d[j+1] + 1), old + cost); old = temp; } } int result = d[s2_len]; free(d); return result; }