mirror of
https://github.com/smittix/intercept.git
synced 2026-07-06 00:28:12 -07:00
96172ca593
First-time run of ruff-format via pre-commit hook normalises quote style, trailing commas, and whitespace across 188 Python files. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
569 lines
14 KiB
Python
569 lines
14 KiB
Python
"""
|
|
WiFi-specific constants for the unified scanner.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# =============================================================================
|
|
# SCANNER SETTINGS
|
|
# =============================================================================
|
|
|
|
# Default quick scan timeout in seconds
|
|
DEFAULT_QUICK_SCAN_TIMEOUT = 15
|
|
|
|
# Default deep scan channel dwell time (seconds)
|
|
DEFAULT_CHANNEL_DWELL_TIME = 2
|
|
|
|
# Maximum RSSI samples to keep per network
|
|
MAX_RSSI_SAMPLES = 300
|
|
|
|
# Network expiration time (seconds since last seen)
|
|
NETWORK_STALE_TIMEOUT = 300 # 5 minutes
|
|
|
|
# Client expiration time (seconds since last seen)
|
|
CLIENT_STALE_TIMEOUT = 180 # 3 minutes
|
|
|
|
# Probe request retention time (seconds)
|
|
PROBE_REQUEST_RETENTION = 600 # 10 minutes
|
|
|
|
# =============================================================================
|
|
# WIFI BANDS
|
|
# =============================================================================
|
|
|
|
BAND_2_4_GHZ = "2.4GHz"
|
|
BAND_5_GHZ = "5GHz"
|
|
BAND_6_GHZ = "6GHz"
|
|
BAND_UNKNOWN = "unknown"
|
|
|
|
# =============================================================================
|
|
# WIFI BAND CHANNEL MAPPINGS
|
|
# =============================================================================
|
|
|
|
# 2.4 GHz channels (1-14)
|
|
CHANNELS_2_4_GHZ = list(range(1, 15))
|
|
|
|
# 5 GHz channels (UNII-1, UNII-2A, UNII-2C, UNII-3)
|
|
CHANNELS_5_GHZ = [
|
|
# UNII-1 (5150-5250 MHz)
|
|
36,
|
|
40,
|
|
44,
|
|
48,
|
|
# UNII-2A (5250-5350 MHz) - DFS
|
|
52,
|
|
56,
|
|
60,
|
|
64,
|
|
# UNII-2C (5470-5725 MHz) - DFS
|
|
100,
|
|
104,
|
|
108,
|
|
112,
|
|
116,
|
|
120,
|
|
124,
|
|
128,
|
|
132,
|
|
136,
|
|
140,
|
|
144,
|
|
# UNII-3 (5725-5850 MHz)
|
|
149,
|
|
153,
|
|
157,
|
|
161,
|
|
165,
|
|
]
|
|
|
|
# 6 GHz channels (Wi-Fi 6E)
|
|
CHANNELS_6_GHZ = [
|
|
1,
|
|
5,
|
|
9,
|
|
13,
|
|
17,
|
|
21,
|
|
25,
|
|
29,
|
|
33,
|
|
37,
|
|
41,
|
|
45,
|
|
49,
|
|
53,
|
|
57,
|
|
61,
|
|
65,
|
|
69,
|
|
73,
|
|
77,
|
|
81,
|
|
85,
|
|
89,
|
|
93,
|
|
97,
|
|
101,
|
|
105,
|
|
109,
|
|
113,
|
|
117,
|
|
121,
|
|
125,
|
|
129,
|
|
133,
|
|
137,
|
|
141,
|
|
145,
|
|
149,
|
|
153,
|
|
157,
|
|
161,
|
|
165,
|
|
169,
|
|
173,
|
|
177,
|
|
181,
|
|
185,
|
|
189,
|
|
193,
|
|
197,
|
|
201,
|
|
205,
|
|
209,
|
|
213,
|
|
217,
|
|
221,
|
|
225,
|
|
229,
|
|
233,
|
|
]
|
|
|
|
# Non-overlapping channels for recommendations
|
|
NON_OVERLAPPING_2_4_GHZ = [1, 6, 11]
|
|
NON_OVERLAPPING_5_GHZ = [36, 40, 44, 48, 149, 153, 157, 161, 165] # Non-DFS
|
|
|
|
# Channel to frequency mappings (MHz)
|
|
CHANNEL_FREQUENCIES = {
|
|
# 2.4 GHz
|
|
1: 2412,
|
|
2: 2417,
|
|
3: 2422,
|
|
4: 2427,
|
|
5: 2432,
|
|
6: 2437,
|
|
7: 2442,
|
|
8: 2447,
|
|
9: 2452,
|
|
10: 2457,
|
|
11: 2462,
|
|
12: 2467,
|
|
13: 2472,
|
|
14: 2484,
|
|
# 5 GHz
|
|
36: 5180,
|
|
40: 5200,
|
|
44: 5220,
|
|
48: 5240,
|
|
52: 5260,
|
|
56: 5280,
|
|
60: 5300,
|
|
64: 5320,
|
|
100: 5500,
|
|
104: 5520,
|
|
108: 5540,
|
|
112: 5560,
|
|
116: 5580,
|
|
120: 5600,
|
|
124: 5620,
|
|
128: 5640,
|
|
132: 5660,
|
|
136: 5680,
|
|
140: 5700,
|
|
144: 5720,
|
|
149: 5745,
|
|
153: 5765,
|
|
157: 5785,
|
|
161: 5805,
|
|
165: 5825,
|
|
}
|
|
|
|
# Frequency to channel reverse mapping
|
|
FREQUENCY_CHANNELS = {v: k for k, v in CHANNEL_FREQUENCIES.items()}
|
|
|
|
|
|
def get_band_from_channel(channel: int) -> str:
|
|
"""Get WiFi band from channel number."""
|
|
if 1 <= channel <= 14:
|
|
return BAND_2_4_GHZ
|
|
elif channel in CHANNELS_5_GHZ:
|
|
return BAND_5_GHZ
|
|
elif channel in CHANNELS_6_GHZ:
|
|
return BAND_6_GHZ
|
|
return BAND_UNKNOWN
|
|
|
|
|
|
def get_band_from_frequency(frequency_mhz: int) -> str:
|
|
"""Get WiFi band from frequency in MHz."""
|
|
if 2400 <= frequency_mhz <= 2500:
|
|
return BAND_2_4_GHZ
|
|
elif 5150 <= frequency_mhz <= 5850:
|
|
return BAND_5_GHZ
|
|
elif 5925 <= frequency_mhz <= 7125:
|
|
return BAND_6_GHZ
|
|
return BAND_UNKNOWN
|
|
|
|
|
|
def get_channel_from_frequency(frequency_mhz: int) -> int | None:
|
|
"""Get channel number from frequency in MHz."""
|
|
return FREQUENCY_CHANNELS.get(frequency_mhz)
|
|
|
|
|
|
# =============================================================================
|
|
# SECURITY TYPES
|
|
# =============================================================================
|
|
|
|
SECURITY_OPEN = "Open"
|
|
SECURITY_WEP = "WEP"
|
|
SECURITY_WPA = "WPA"
|
|
SECURITY_WPA2 = "WPA2"
|
|
SECURITY_WPA3 = "WPA3"
|
|
SECURITY_WPA_WPA2 = "WPA/WPA2"
|
|
SECURITY_WPA2_WPA3 = "WPA2/WPA3"
|
|
SECURITY_ENTERPRISE = "Enterprise"
|
|
SECURITY_UNKNOWN = "Unknown"
|
|
|
|
# Security type priority (higher = more secure)
|
|
SECURITY_PRIORITY = {
|
|
SECURITY_OPEN: 0,
|
|
SECURITY_WEP: 1,
|
|
SECURITY_WPA: 2,
|
|
SECURITY_WPA_WPA2: 3,
|
|
SECURITY_WPA2: 4,
|
|
SECURITY_WPA2_WPA3: 5,
|
|
SECURITY_WPA3: 6,
|
|
SECURITY_ENTERPRISE: 7,
|
|
SECURITY_UNKNOWN: -1,
|
|
}
|
|
|
|
# =============================================================================
|
|
# CIPHER TYPES
|
|
# =============================================================================
|
|
|
|
CIPHER_NONE = "None"
|
|
CIPHER_WEP = "WEP"
|
|
CIPHER_TKIP = "TKIP"
|
|
CIPHER_CCMP = "CCMP"
|
|
CIPHER_GCMP = "GCMP"
|
|
CIPHER_UNKNOWN = "Unknown"
|
|
|
|
# =============================================================================
|
|
# AUTHENTICATION TYPES
|
|
# =============================================================================
|
|
|
|
AUTH_OPEN = "Open"
|
|
AUTH_PSK = "PSK"
|
|
AUTH_SAE = "SAE"
|
|
AUTH_EAP = "EAP"
|
|
AUTH_OWE = "OWE"
|
|
AUTH_UNKNOWN = "Unknown"
|
|
|
|
# =============================================================================
|
|
# CHANNEL WIDTH
|
|
# =============================================================================
|
|
|
|
WIDTH_20_MHZ = "20MHz"
|
|
WIDTH_40_MHZ = "40MHz"
|
|
WIDTH_80_MHZ = "80MHz"
|
|
WIDTH_160_MHZ = "160MHz"
|
|
WIDTH_320_MHZ = "320MHz"
|
|
WIDTH_UNKNOWN = "Unknown"
|
|
|
|
# =============================================================================
|
|
# SIGNAL STRENGTH BANDS (for proximity radar)
|
|
# =============================================================================
|
|
|
|
SIGNAL_STRONG = "strong" # >= -50 dBm
|
|
SIGNAL_MEDIUM = "medium" # -50 to -70 dBm
|
|
SIGNAL_WEAK = "weak" # -70 to -85 dBm
|
|
SIGNAL_VERY_WEAK = "very_weak" # < -85 dBm
|
|
SIGNAL_UNKNOWN = "unknown"
|
|
|
|
# RSSI thresholds for signal bands
|
|
RSSI_STRONG = -50
|
|
RSSI_MEDIUM = -70
|
|
RSSI_WEAK = -85
|
|
|
|
|
|
def get_signal_band(rssi: int | None) -> str:
|
|
"""Get signal band from RSSI value."""
|
|
if rssi is None:
|
|
return SIGNAL_UNKNOWN
|
|
if rssi >= RSSI_STRONG:
|
|
return SIGNAL_STRONG
|
|
elif rssi >= RSSI_MEDIUM:
|
|
return SIGNAL_MEDIUM
|
|
elif rssi >= RSSI_WEAK:
|
|
return SIGNAL_WEAK
|
|
return SIGNAL_VERY_WEAK
|
|
|
|
|
|
# =============================================================================
|
|
# PROXIMITY BANDS (consistent with Bluetooth)
|
|
# =============================================================================
|
|
|
|
PROXIMITY_IMMEDIATE = "immediate" # < 3m
|
|
PROXIMITY_NEAR = "near" # 3-10m
|
|
PROXIMITY_FAR = "far" # > 10m
|
|
PROXIMITY_UNKNOWN = "unknown"
|
|
|
|
# RSSI thresholds for proximity band classification
|
|
PROXIMITY_RSSI_IMMEDIATE = -55 # >= -55 dBm -> immediate
|
|
PROXIMITY_RSSI_NEAR = -70 # >= -70 dBm -> near
|
|
|
|
|
|
def get_proximity_band(rssi: int | None) -> str:
|
|
"""Get proximity band from RSSI value."""
|
|
if rssi is None:
|
|
return PROXIMITY_UNKNOWN
|
|
if rssi >= PROXIMITY_RSSI_IMMEDIATE:
|
|
return PROXIMITY_IMMEDIATE
|
|
elif rssi >= PROXIMITY_RSSI_NEAR:
|
|
return PROXIMITY_NEAR
|
|
return PROXIMITY_FAR
|
|
|
|
|
|
# =============================================================================
|
|
# DISTANCE ESTIMATION (WiFi-specific)
|
|
# =============================================================================
|
|
|
|
# Path-loss exponent for indoor WiFi (typically 2.5-4.0)
|
|
WIFI_PATH_LOSS_EXPONENT = 3.0
|
|
|
|
# Reference RSSI at 1 meter (typical WiFi AP)
|
|
WIFI_RSSI_AT_1M = -40
|
|
|
|
# EMA smoothing alpha for RSSI
|
|
WIFI_EMA_ALPHA = 0.3
|
|
|
|
# =============================================================================
|
|
# SCAN MODES
|
|
# =============================================================================
|
|
|
|
SCAN_MODE_QUICK = "quick" # Uses system tools (no monitor mode)
|
|
SCAN_MODE_DEEP = "deep" # Uses airodump-ng (monitor mode required)
|
|
|
|
# =============================================================================
|
|
# TOOL DETECTION
|
|
# =============================================================================
|
|
|
|
# Quick scan tools (by platform priority)
|
|
QUICK_SCAN_TOOLS_LINUX = ["nmcli", "iw", "iwlist"]
|
|
QUICK_SCAN_TOOLS_DARWIN = ["airport"]
|
|
|
|
# Deep scan tools
|
|
DEEP_SCAN_TOOLS = ["airodump-ng"]
|
|
|
|
# Monitor mode tools
|
|
MONITOR_MODE_TOOLS = ["airmon-ng", "iw"]
|
|
|
|
# Tool command timeouts (seconds)
|
|
TOOL_TIMEOUT_QUICK = 30.0
|
|
TOOL_TIMEOUT_DETECT = 5.0
|
|
|
|
# =============================================================================
|
|
# AIRODUMP-NG SETTINGS
|
|
# =============================================================================
|
|
|
|
AIRODUMP_OUTPUT_PREFIX = "airodump_wifi"
|
|
AIRODUMP_POLL_INTERVAL = 1.0 # seconds between CSV reads
|
|
|
|
# =============================================================================
|
|
# HEURISTIC FLAGS
|
|
# =============================================================================
|
|
|
|
HEURISTIC_HIDDEN = "hidden"
|
|
HEURISTIC_ROGUE_AP = "rogue_ap"
|
|
HEURISTIC_EVIL_TWIN = "evil_twin"
|
|
HEURISTIC_BEACON_FLOOD = "beacon_flood"
|
|
HEURISTIC_WEAK_SECURITY = "weak_security"
|
|
HEURISTIC_DEAUTH_DETECTED = "deauth_detected"
|
|
HEURISTIC_NEW = "new"
|
|
HEURISTIC_PERSISTENT = "persistent"
|
|
HEURISTIC_STRONG_STABLE = "strong_stable"
|
|
|
|
# Thresholds
|
|
BEACON_FLOOD_THRESHOLD = 50 # Same BSSID seen > 50 times/minute
|
|
PERSISTENT_MIN_SEEN = 10
|
|
PERSISTENT_WINDOW_SECONDS = 300
|
|
STRONG_RSSI_THRESHOLD = -50
|
|
STABLE_VARIANCE_THRESHOLD = 5.0
|
|
|
|
# =============================================================================
|
|
# COMMON VENDOR OUI PREFIXES (first 3 bytes of MAC)
|
|
# =============================================================================
|
|
|
|
VENDOR_OUIS = {
|
|
"00:00:5E": "IANA",
|
|
"00:03:93": "Apple",
|
|
"00:0A:95": "Apple",
|
|
"00:0D:93": "Apple",
|
|
"00:11:24": "Apple",
|
|
"00:14:51": "Apple",
|
|
"00:16:CB": "Apple",
|
|
"00:17:F2": "Apple",
|
|
"00:19:E3": "Apple",
|
|
"00:1B:63": "Apple",
|
|
"00:1C:B3": "Apple",
|
|
"00:1D:4F": "Apple",
|
|
"00:1E:52": "Apple",
|
|
"00:1E:C2": "Apple",
|
|
"00:1F:5B": "Apple",
|
|
"00:1F:F3": "Apple",
|
|
"00:21:E9": "Apple",
|
|
"00:22:41": "Apple",
|
|
"00:23:12": "Apple",
|
|
"00:23:32": "Apple",
|
|
"00:23:6C": "Apple",
|
|
"00:23:DF": "Apple",
|
|
"00:24:36": "Apple",
|
|
"00:25:00": "Apple",
|
|
"00:25:4B": "Apple",
|
|
"00:25:BC": "Apple",
|
|
"00:26:08": "Apple",
|
|
"00:26:4A": "Apple",
|
|
"00:26:B0": "Apple",
|
|
"00:26:BB": "Apple",
|
|
"00:50:F2": "Microsoft",
|
|
"00:15:5D": "Microsoft",
|
|
"00:17:FA": "Microsoft",
|
|
"00:1D:D8": "Microsoft",
|
|
"00:50:56": "VMware",
|
|
"00:0C:29": "VMware",
|
|
"00:05:69": "VMware",
|
|
"08:00:27": "VirtualBox",
|
|
"00:1C:42": "Parallels",
|
|
"00:16:3E": "Xen",
|
|
"DC:A6:32": "Raspberry Pi",
|
|
"B8:27:EB": "Raspberry Pi",
|
|
"E4:5F:01": "Raspberry Pi",
|
|
"28:CD:C1": "Raspberry Pi",
|
|
"00:1A:11": "Google",
|
|
"00:1A:22": "Google",
|
|
"3C:5A:B4": "Google",
|
|
"54:60:09": "Google",
|
|
"94:EB:2C": "Google",
|
|
"F4:F5:D8": "Google",
|
|
"00:17:C4": "Netgear",
|
|
"00:1B:2F": "Netgear",
|
|
"00:1E:2A": "Netgear",
|
|
"00:22:3F": "Netgear",
|
|
"00:24:B2": "Netgear",
|
|
"00:26:F2": "Netgear",
|
|
"00:18:F8": "Cisco",
|
|
"00:1A:A1": "Cisco",
|
|
"00:1B:0C": "Cisco",
|
|
"00:1B:D4": "Cisco",
|
|
"00:1C:0E": "Cisco",
|
|
"00:1C:57": "Cisco",
|
|
"00:40:96": "Cisco",
|
|
"00:50:54": "Cisco",
|
|
"00:60:5C": "Cisco",
|
|
"E8:65:D4": "Ubiquiti",
|
|
"FC:EC:DA": "Ubiquiti",
|
|
"00:27:22": "Ubiquiti",
|
|
"04:18:D6": "Ubiquiti",
|
|
"18:E8:29": "Ubiquiti",
|
|
"24:A4:3C": "Ubiquiti",
|
|
"44:D9:E7": "Ubiquiti",
|
|
"68:72:51": "Ubiquiti",
|
|
"74:83:C2": "Ubiquiti",
|
|
"78:8A:20": "Ubiquiti",
|
|
"B4:FB:E4": "Ubiquiti",
|
|
"F0:9F:C2": "Ubiquiti",
|
|
"00:0C:F1": "Intel",
|
|
"00:13:02": "Intel",
|
|
"00:13:20": "Intel",
|
|
"00:13:CE": "Intel",
|
|
"00:13:E8": "Intel",
|
|
"00:15:00": "Intel",
|
|
"00:15:17": "Intel",
|
|
"00:16:6F": "Intel",
|
|
"00:16:76": "Intel",
|
|
"00:16:EA": "Intel",
|
|
"00:16:EB": "Intel",
|
|
"00:18:DE": "Intel",
|
|
"00:19:D1": "Intel",
|
|
"00:19:D2": "Intel",
|
|
"00:1B:21": "Intel",
|
|
"00:1B:77": "Intel",
|
|
"00:1C:BF": "Intel",
|
|
"00:1D:E0": "Intel",
|
|
"00:1D:E1": "Intel",
|
|
"00:1E:64": "Intel",
|
|
"00:1E:65": "Intel",
|
|
"00:1E:67": "Intel",
|
|
"00:1F:3B": "Intel",
|
|
"00:1F:3C": "Intel",
|
|
"00:20:E0": "TP-Link",
|
|
"00:23:CD": "TP-Link",
|
|
"00:25:86": "TP-Link",
|
|
"00:27:19": "TP-Link",
|
|
"14:CC:20": "TP-Link",
|
|
"14:CF:92": "TP-Link",
|
|
"18:A6:F7": "TP-Link",
|
|
"1C:3B:F3": "TP-Link",
|
|
"30:B5:C2": "TP-Link",
|
|
"50:C7:BF": "TP-Link",
|
|
"54:C8:0F": "TP-Link",
|
|
"60:E3:27": "TP-Link",
|
|
"64:56:01": "TP-Link",
|
|
"64:66:B3": "TP-Link",
|
|
"64:70:02": "TP-Link",
|
|
}
|
|
|
|
|
|
def get_vendor_from_mac(mac: str) -> str | None:
|
|
"""Get vendor name from MAC address OUI."""
|
|
if not mac:
|
|
return None
|
|
# Normalize MAC format
|
|
mac_upper = mac.upper().replace("-", ":")
|
|
oui = mac_upper[:8]
|
|
vendor = VENDOR_OUIS.get(oui)
|
|
if vendor:
|
|
return vendor
|
|
|
|
# Fallback to expanded OUI database if available
|
|
try:
|
|
from data.oui import get_manufacturer
|
|
|
|
manufacturer = get_manufacturer(mac_upper)
|
|
if manufacturer and manufacturer != "Unknown":
|
|
return manufacturer
|
|
except Exception:
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
# =============================================================================
|
|
# HIDDEN SSID CORRELATION
|
|
# =============================================================================
|
|
|
|
# Time window for correlating probe requests with hidden AP associations
|
|
HIDDEN_CORRELATION_WINDOW_SECONDS = 60
|
|
|
|
# Minimum confidence for hidden SSID revelation
|
|
HIDDEN_MIN_CORRELATION_CONFIDENCE = 0.7
|
|
|
|
# =============================================================================
|
|
# CHANNEL ANALYSIS
|
|
# =============================================================================
|
|
|
|
# Weights for channel utilization scoring
|
|
CHANNEL_WEIGHT_AP_COUNT = 0.6
|
|
CHANNEL_WEIGHT_CLIENT_COUNT = 0.4
|
|
|
|
# RSSI adjustment factor (stronger signals = more interference)
|
|
CHANNEL_RSSI_INTERFERENCE_FACTOR = 0.1
|