()
| 15233 | deauth_events.append(f'ch{ch}: {ln}') |
| 15234 | |
| 15235 | except Exception: |
| 15236 | pass |
| 15237 | finally: |
| 15238 | # Cleanup: delete virtual mon or restore secondary to managed |
| 15239 | try: |
| 15240 | if capture_iface == _mon: |
| 15241 | subprocess.run(['sudo', 'ip', 'link', 'set', _mon, 'down'], |
| 15242 | capture_output=True, timeout=3) |
| 15243 | subprocess.run(['sudo', 'iw', 'dev', _mon, 'del'], |
| 15244 | capture_output=True, timeout=3) |
| 15245 | elif restore_secondary: |
| 15246 | subprocess.run(['sudo', 'ip', 'link', 'set', restore_secondary, 'down'], |
| 15247 | capture_output=True, timeout=3) |
| 15248 | subprocess.run(['sudo', 'iw', 'dev', restore_secondary, 'set', 'type', 'managed'], |
| 15249 | capture_output=True, timeout=3) |
| 15250 | subprocess.run(['sudo', 'ip', 'link', 'set', restore_secondary, 'up'], |
| 15251 | capture_output=True, timeout=3) |
| 15252 | except Exception: |
| 15253 | pass |
| 15254 | |
| 15255 | ev_thread = _threading.Thread(target=_capture_deauth_frames, daemon=True) |
| 15256 | ev_thread.start() |
| 15257 | |
| 15258 | _time.sleep(4) |
| 15259 | |
| 15260 | # Get results WITH BSSID, SSID, signal, security, frequency, channel |
| 15261 | result = subprocess.run( |
| 15262 | ['nmcli', '-t', '-f', 'BSSID,SSID,SIGNAL,SECURITY,FREQ,CHAN', |
| 15263 | 'dev', 'wifi', 'list', '--rescan', 'no', 'ifname', iface], |
| 15264 | capture_output=True, text=True, timeout=15 |
| 15265 | ) |
| 15266 | |
| 15267 | if result.returncode != 0: |
| 15268 | return jsonify({'success': False, 'error': 'WiFi scan failed', 'findings': []}), 500 |
| 15269 | |
| 15270 | # Get our own connected network for evil twin detection |
| 15271 | link_result = subprocess.run( |
| 15272 | ['iw', 'dev', iface, 'link'], |
| 15273 | capture_output=True, text=True, timeout=5 |
| 15274 | ) |
| 15275 | own_ssid = '' |
| 15276 | own_bssid = '' |
| 15277 | for ln in link_result.stdout.splitlines(): |
| 15278 | ln = ln.strip() |
| 15279 | if ln.startswith('SSID:'): |
| 15280 | own_ssid = ln.split(':', 1)[1].strip() |
| 15281 | elif ln.startswith('Connected to'): |
| 15282 | own_bssid = ln.split(' ')[2].strip().upper() |
| 15283 | |
| 15284 | # Rogue SSID patterns |
| 15285 | _ROGUE_SSID_PATTERNS = [ |
| 15286 | (_re.compile(r'piglet', _re.I), 'Piglet Wardriver', 'high', |
| 15287 | 'Piglet wardriving device AP detected nearby'), |
| 15288 | (_re.compile(r'pineapple|hak5|^pager$|pagerap', _re.I), 'WiFi Pineapple', 'critical', |
| 15289 | 'Hak5 WiFi Pineapple rogue AP detected'), |
| 15290 | (_re.compile(r'pwned|pwnagotchi', _re.I), 'Pwnagotchi', 'high', |
| 15291 | 'Pwnagotchi handshake capture device detected'), |
| 15292 | (_re.compile(r'deauth|dstike|spacehuhn', _re.I), 'Deauther', 'high', |
nothing calls this directly
no test coverage detected