diff --git a/listener.py b/listener.py index 7eaa7b7..908bf5f 100755 --- a/listener.py +++ b/listener.py @@ -6,6 +6,7 @@ import signal import subprocess import sys import time +import threading from argparse import ArgumentParser from collections import defaultdict @@ -27,6 +28,10 @@ current_channel = None include_probes = False deadpoint_candidates = set() unlinked_candidates = set() +bssid_channels = {} + +CHANNEL_LIST = [1, 6, 11, 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165] # Channels to hop +CHANNEL_HOP_INTERVAL = 5 # Seconds per channel # === Signal handling === def stop_sniff(signum, frame): @@ -92,6 +97,10 @@ def handle_packet(pkt): ssid = parse_ssid(pkt) if ssid: ssid_map[a2] = ssid + # Track channel seen on + if pkt.haslayer(RadioTap) and hasattr(pkt[RadioTap], 'ChannelFrequency'): + # Nah, be lazy + bssid_channels[a2] = current_channel # === Track all seen clients === if dot11.type == 2: @@ -193,18 +202,30 @@ def print_suspect_aps(): if suspects: for bssid, ssid, flags in suspects: - print(f" - {bssid} (SSID: {ssid}) <-- {' + '.join(flags)}") + ch = bssid_channels.get(bssid, "?") + print(f" - {bssid} (SSID: {ssid}, Channel: {ch}) <-- {' + '.join(flags)}") else: print(" None found (yet).") +def channel_hopper(interface): + global running + i = 0 + while running: + channel = CHANNEL_LIST[i % len(CHANNEL_LIST)] + set_monitor_channel(interface, channel) + i += 1 + time.sleep(CHANNEL_HOP_INTERVAL) + # === Main === def main(): parser = ArgumentParser() parser.add_argument("--main-iface", required=True, help="Active interface (used to determine channel)") parser.add_argument("--monitor-iface", required=True, help="Monitor interface to sniff on") parser.add_argument("--outfile", required=True, help="CSV file to append metrics row") - parser.add_argument("--channel", type=int, help="Channel to lock monitor interface to (overrides main iface)") parser.add_argument("--include-probes", action="store_true", help="Include probe responses as valid APs") + group = parser.add_mutually_exclusive_group() + group.add_argument("--channel", type=int, help="Channel to lock monitor interface to") + group.add_argument("--channel-hop", action="store_true", help="Enable channel hopping") args = parser.parse_args() @@ -237,8 +258,11 @@ def main(): target_ap_bssid = None # Can't determine AP if we're not associated print("[+] Sniffing... (waiting for SIGINT to stop)") - - + if args.channel_hop: + hopper_thread = threading.Thread(target=channel_hopper, args=(args.monitor_iface,)) + hopper_thread.daemon = True + hopper_thread.start() + while running: sniff(iface=args.monitor_iface, prn=handle_packet, store=False, timeout=5) @@ -247,7 +271,8 @@ def main(): for bssid in sorted(aps): ssid = ssid_map.get(bssid, "") - print(f" - {bssid} (SSID: {ssid})") + ch = bssid_channels.get(bssid, "?") + print(f" - {bssid} (SSID: {ssid}, Channel: {ch})") print(f"[+] Total APsOnChannel: {len(aps)}") print_suspect_aps()