From 231ce7e8b08d2def24cbd329b40aea30fd3424fc Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 2 May 2025 11:20:42 -0500 Subject: [PATCH 1/5] Association Frame Support (Augments client tracking) --- listener.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/listener.py b/listener.py index 21f6bd3..32bc26a 100755 --- a/listener.py +++ b/listener.py @@ -128,6 +128,17 @@ def handle_packet(pkt): if mac not in aps: unlinked_candidates.add(mac) + if dot11.type == 0 and dot11.subtype in [0, 1]: + sa = dot11.addr2.lower() if dot11.addr2 else None + da = dot11.addr1.lower() if dot11.addr1 else None + for mac in (sa, da): + if is_unicast(mac) and mac != target_ap_bssid: + clients[mac] += 1 + if mac not in aps: + unlinked_candidates.add(mac) + if da in aps and is_unicast(sa): + ap_clients[da][sa] += 5 + # Track clients talking to any known AP if a1 and a2: for bssid in aps: From 66496e151813d3a9ff4eaf9b00ed50fc4a6f2472 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 2 May 2025 11:22:00 -0500 Subject: [PATCH 2/5] Optimize AP-client matching loop --- listener.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/listener.py b/listener.py index 32bc26a..99ab8a4 100755 --- a/listener.py +++ b/listener.py @@ -140,12 +140,10 @@ def handle_packet(pkt): ap_clients[da][sa] += 5 # Track clients talking to any known AP - if a1 and a2: - for bssid in aps: - if bssid in (a1, a2): - peer = a2 if bssid == a1 else a1 - if is_unicast(peer): - ap_clients[bssid][peer] += 1 + if a1 in aps and is_unicast(a2): + ap_clients[a1][a2] += 1 + elif a2 in aps and is_unicast(a1): + ap_clients[a2][a1] += 1 # === Signal strength tracking === try: From be2c674a2c56d0082ab90cc28e430824cf6cebc9 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 2 May 2025 11:23:28 -0500 Subject: [PATCH 3/5] =?UTF-8?q?Ensure=20ClientsOnChannel=20respects=20main?= =?UTF-8?q?=20interface=E2=80=99s=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- listener.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/listener.py b/listener.py index 99ab8a4..d5f8a30 100755 --- a/listener.py +++ b/listener.py @@ -162,7 +162,10 @@ def write_csv(outfile): row = { "Timestamp": timestamp, "ClientsOnAP": len([mac for mac, count in ap_clients[target_ap_bssid].items() if count > 3]), - "ClientsOnChannel": len([mac for mac, count in clients.items() if count > 3]), + "ClientsOnChannel": len([ + mac for mac, count in clients.items() + if count > 3 and mac in ap_clients and current_channel == bssid_channels.get(mac) + ]), "APsOnChannel": len(aps), "AvgAPSignal": round(sum([sum(v)/len(v) for v in ap_signals.values() if v]) / len(ap_signals) if ap_signals else 0, 2), "StrongestAPSignal": max([max(v) for v in ap_signals.values() if v], default=0), From d0a356158645af4d56c44a0edd8cd047ba76ced6 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 2 May 2025 11:25:12 -0500 Subject: [PATCH 4/5] MAC Vendor Resolution (with local cache) --- .gitignore | 3 ++- listener.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e039ba9..5e22279 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ settings.env -__pycache__/ \ No newline at end of file +__pycache__/ +oui.txt \ No newline at end of file diff --git a/listener.py b/listener.py index d5f8a30..4d45b1a 100755 --- a/listener.py +++ b/listener.py @@ -29,6 +29,7 @@ include_probes = False # deadpoint_candidates = set() unlinked_candidates = set() bssid_channels = {} +vendor_cache = {} 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 @@ -312,7 +313,33 @@ def main(): print(f"[+] Total APsOnChannel: {len(aps)}") print_suspect_aps() + print("\n[+] Vendor Summary (known APs):") + for bssid in sorted(aps): + vendor = get_mac_vendor(bssid) + print(f" {bssid} → {vendor}") + reset_interface(args.monitor_iface) +def get_mac_vendor(mac): + prefix = mac.upper()[0:8].replace(":", "-") + if prefix in vendor_cache: + return vendor_cache[prefix] + try: + if not os.path.exists("oui.txt"): + print("[~] Downloading IEEE OUI list...") + import urllib.request + url = "http://standards-oui.ieee.org/oui/oui.txt" + urllib.request.urlretrieve(url, "oui.txt") + with open("oui.txt", "r", encoding="utf-8", errors="ignore") as f: + for line in f: + if prefix in line: + vendor = line.strip().split("\t")[-1] + vendor_cache[prefix] = vendor + return vendor + except Exception as e: + pass + vendor_cache[prefix] = "Unknown" + return "Unknown" + if __name__ == "__main__": main() From a5f8123c45c5737aada8afe4350e4e359e2c9f87 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 2 May 2025 11:27:12 -0500 Subject: [PATCH 5/5] Refactor ClientsOnChannel metric calculation to simplify logic --- listener.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/listener.py b/listener.py index 4d45b1a..7234c6e 100755 --- a/listener.py +++ b/listener.py @@ -163,10 +163,7 @@ def write_csv(outfile): row = { "Timestamp": timestamp, "ClientsOnAP": len([mac for mac, count in ap_clients[target_ap_bssid].items() if count > 3]), - "ClientsOnChannel": len([ - mac for mac, count in clients.items() - if count > 3 and mac in ap_clients and current_channel == bssid_channels.get(mac) - ]), + "ClientsOnChannel": len([mac for mac, count in clients.items() if count > 3]), "APsOnChannel": len(aps), "AvgAPSignal": round(sum([sum(v)/len(v) for v in ap_signals.values() if v]) / len(ap_signals) if ap_signals else 0, 2), "StrongestAPSignal": max([max(v) for v in ap_signals.values() if v], default=0),