Scapy/Airodump Clone
From charlesreid1
A Python-based clone of airodump-ng, based heavily on Dan McInerney's wifijammer.py script: https://github.com/DanMcInerney/wifijammer/blob/master/wifijammer.py
Contents
Summary
This page covers a script that will use Scapy to monitor wireless networks, clients, and access points nearby, and create an output similar to airodump-ng.
This will introduce the reader to some basic, but essential, techniques for using Python to manipulate the wireless hardware alongside calls to the Scapy library.
The Output
run the script like this:
$ python monitor_mode.py
The Script
In this article we will cover the following script, based heavily on Dan McInerney's wifijammer.py script, but without the sharp teeth: https://github.com/DanMcInerney/wifijammer/blob/master/wifijammer.py
#!/usr/bin/env python
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
conf.verb = 0
import os
import sys
import time
from threading import Thread, Lock
from subprocess import Popen, PIPE
from signal import SIGINT, signal
import argparse
import socket
import struct
import fcntl
# Console colors
W = '\033[0m' # white (normal)
R = '\033[31m' # red
G = '\033[32m' # green
O = '\033[33m' # orange
B = '\033[34m' # blue
P = '\033[35m' # purple
C = '\033[36m' # cyan
GR = '\033[37m' # gray
T = '\033[93m' # tan
def parse_args():
#Create the arguments
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--interface", help="Choose monitor mode interface. By default script will find the most powerful interface and starts monitor mode on it. Example: -i mon5")
parser.add_argument("-c", "--channel", help="Listen on and deauth only clients on the specified channel. Example: -c 6")
parser.add_argument("-m", "--maximum", help="Choose the maximum number of clients to deauth. List of clients will be emptied and repopulated after hitting the limit. Example: -m 5")
parser.add_argument("-n", "--noupdate", help="Do not clear the deauth list when the maximum (-m) number of client/AP combos is reached. Must be used in conjunction with -m. Example: -m 10 -n", action='store_true')
parser.add_argument("-t", "--timeinterval", help="Choose the time interval between packets being sent. Default is as fast as possible. If you see scapy errors like 'no buffer space' try: -t .00001")
parser.add_argument("-p", "--packets", help="Choose the number of packets to send in each deauth burst. Default value is 1; 1 packet to the client and 1 packet to the AP. Send 2 deauth packets to the client and 2 deauth packets to the AP: -p 2")
parser.add_argument("-d", "--directedonly", help="Skip the deauthentication packets to the broadcast address of the access points and only send them to client/AP pairs", action='store_true')
parser.add_argument("-a", "--accesspoint", help="Enter the MAC address of a specific access point to target")
parser.add_argument("--world", help="N. American standard is 11 channels but the rest of the world it's 13 so this options enables the scanning of 13 channels", action="store_true")
return parser.parse_args()
########################################
# Begin interface settings
########################################
def get_mon_iface(args):
global monitor_on
monitors, interfaces = iwconfig()
if args.interface:
monitor_on = True
return args.interface
if len(monitors) > 0:
monitor_on = True
return monitors[0]
else:
# Start monitor mode on a wireless interface
print '['+G+'*'+W+'] Finding the most powerful interface...'
interface = get_iface(interfaces)
monmode = start_mon_mode(interface)
return monmode
def iwconfig():
monitors = []
interfaces = {}
try:
proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN)
except OSError:
sys.exit('['+R+'-'+W+'] Could not execute "iwconfig"')
for line in proc.communicate()[0].split('\n'):
if len(line) == 0: continue # Isn't an empty string
if line[0] != ' ': # Doesn't start with space
wired_search = re.search('eth[0-9]|em[0-9]|p[1-9]p[1-9]', line)
if not wired_search: # Isn't wired
iface = line[:line.find(' ')] # is the interface
if 'Mode:Monitor' in line:
monitors.append(iface)
elif 'IEEE 802.11' in line:
if "ESSID:\"" in line:
interfaces[iface] = 1
else:
interfaces[iface] = 0
return monitors, interfaces
def get_iface(interfaces):
scanned_aps = []
if len(interfaces) < 1:
sys.exit('['+R+'-'+W+'] No wireless interfaces found, bring one up and try again')
if len(interfaces) == 1:
for interface in interfaces:
return interface
# Find most powerful interface
for iface in interfaces:
count = 0
proc = Popen(['iwlist', iface, 'scan'], stdout=PIPE, stderr=DN)
for line in proc.communicate()[0].split('\n'):
if ' - Address:' in line: # first line in iwlist scan for a new AP
count += 1
scanned_aps.append((count, iface))
print '['+G+'+'+W+'] Networks discovered by '+G+iface+W+': '+T+str(count)+W
try:
interface = max(scanned_aps)[1]
return interface
except Exception as e:
for iface in interfaces:
interface = iface
print '['+R+'-'+W+'] Minor error:',e
print ' Starting monitor mode on '+G+interface+W
return interface
def start_mon_mode(interface):
print '['+G+'+'+W+'] Starting monitor mode off '+G+interface+W
try:
os.system('ifconfig %s down' % interface)
os.system('iwconfig %s mode monitor' % interface)
os.system('ifconfig %s up' % interface)
return interface
except Exception:
sys.exit('['+R+'-'+W+'] Could not start monitor mode')
def remove_mon_iface(mon_iface):
os.system('ifconfig %s down' % mon_iface)
os.system('iwconfig %s mode managed' % mon_iface)
os.system('ifconfig %s up' % mon_iface)
def mon_mac(mon_iface):
'''
http://stackoverflow.com/questions/159137/getting-mac-address
'''
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', mon_iface[:15]))
mac = ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
print '['+G+'*'+W+'] Monitor mode: '+G+mon_iface+W+' - '+O+mac+W
return mac
########################################
# End interface settings
########################################
########################################
# Set channel hop behavior
########################################
def channel_hop(mon_iface, args):
'''
First time through, scan each channel for 5 seconds.
Then skip through all channels quickly.
'''
global monchannel, first_pass
channelNum = 0
maxChan = 11 if not args.world else 13
err = None
while 1:
if args.channel:
with lock:
monchannel = args.channel
else:
channelNum +=1
if channelNum > maxChan:
channelNum = 1
with lock:
first_pass = 0
with lock:
monchannel = str(channelNum)
try:
proc = Popen(['iw', 'dev', mon_iface, 'set', 'channel', monchannel], stdout=DN, stderr=PIPE)
except OSError:
print '['+R+'-'+W+'] Could not execute "iw"'
os.kill(os.getpid(),SIGINT)
sys.exit(1)
for line in proc.communicate()[1].split('\n'):
if len(line) > 2: # iw dev shouldnt display output unless there's an error
err = '['+R+'-'+W+'] Channel hopping failed: '+R+line+W
output(err, monchannel)
if args.channel:
time.sleep(.05)
else:
# For the first channel hop thru, do not deauth
if first_pass == 1:
time.sleep(1)
continue
print "this is where deauth would go:" ,monchannel
########################################
# End channel hop behavior
########################################
########################################
# Set output filtering
########################################
def output(err, monchannel):
os.system('clear')
if err:
print err
else:
print '['+G+'+'+W+'] '+mon_iface+' channel: '+G+monchannel+W+'\n'
if len(clients_APs) > 0:
print ' Clients ch ESSID'
# Print the clients list
with lock:
for ca in clients_APs:
if len(ca) > 3:
print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2].ljust(2)+' - '+T+ca[3]+W
else:
print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2]
if len(APs) > 0:
print '\n Access Points ch ESSID'
with lock:
for ap in APs:
print '['+T+'*'+W+'] '+O+ap[0]+W+' - '+ap[1].ljust(2)+' - '+T+ap[2]+W
print ''
def noise_filter(addr1, addr2):
# Broadcast, broadcast, IPv6mcast, spanning tree, spanning tree, multicast, broadcast
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]
for i in ignore:
if i in addr1 or i in addr2:
return True
########################################
# End output filtering
########################################
########################################
# Set packet handling
########################################
def cb(pkt):
'''
Look for dot11 packets that aren't to or from broadcast address,
are type 1 or 2 (control, data), and append the addr1 and addr2
to the list of clients
'''
global clients_APs, APs
# return these if's keeping clients_APs the same or just reset clients_APs?
# I like the idea of the tool repopulating the variable more
if args.maximum:
if args.noupdate:
if len(clients_APs) > int(args.maximum):
return
else:
if len(clients_APs) > int(args.maximum):
with lock:
clients_APs = []
APs = []
# We're adding the AP and channel to the clients list at time of creation rather
# than updating on the fly in order to avoid costly for loops that require a lock
if pkt.haslayer(Dot11):
if pkt.addr1 and pkt.addr2:
pkt.addr1 = pkt.addr1.lower()
pkt.addr2 = pkt.addr2.lower()
# Filter out all other APs and clients if asked
if args.accesspoint:
if args.accesspoint not in [pkt.addr1, pkt.addr2]:
return
# Check if it's added to our AP list
if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp):
APs_add(clients_APs, APs, pkt, args.channel, args.world)
# Ignore all the noisy packets like spanning tree
if noise_filter(pkt.addr1, pkt.addr2):
return
# Management = 1, data = 2
if pkt.type in [1, 2]:
clients_APs_add(clients_APs, pkt.addr1, pkt.addr2)
def APs_add(clients_APs, APs, pkt, chan_arg, world_arg):
ssid = pkt[Dot11Elt].info
bssid = pkt[Dot11].addr3.lower()
try:
# Thanks to airoscapy for below
ap_channel = str(ord(pkt[Dot11Elt:3].info))
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']
if ap_channel not in chans:
return
if chan_arg:
if ap_channel != chan_arg:
return
except Exception as e:
return
if len(APs) == 0:
with lock:
return APs.append([bssid, ap_channel, ssid])
else:
for b in APs:
if bssid in b[0]:
return
with lock:
return APs.append([bssid, ap_channel, ssid])
def clients_APs_add(clients_APs, addr1, addr2):
if len(clients_APs) == 0:
if len(APs) == 0:
with lock:
return clients_APs.append([addr1, addr2, monchannel])
else:
AP_check(addr1, addr2)
# Append new clients/APs if they're not in the list
else:
for ca in clients_APs:
if addr1 in ca and addr2 in ca:
return
if len(APs) > 0:
return AP_check(addr1, addr2)
else:
with lock:
return clients_APs.append([addr1, addr2, monchannel])
def AP_check(addr1, addr2):
for ap in APs:
if ap[0].lower() in addr1.lower() or ap[0].lower() in addr2.lower():
with lock:
return clients_APs.append([addr1, addr2, ap[1], ap[2]])
def AP_check(addr1, addr2):
for ap in APs:
if ap[0].lower() in addr1.lower() or ap[0].lower() in addr2.lower():
with lock:
return clients_APs.append([addr1, addr2, ap[1], ap[2]])
def stop(signal, frame):
if monitor_on:
sys.exit('\n['+R+'!'+W+'] Closing')
else:
remove_mon_iface(mon_iface)
os.system('service network-manager restart')
sys.exit('\n['+R+'!'+W+'] Closing')
########################################
# End packet handling
########################################
########################################
# Now get busy!
########################################
if __name__ == "__main__":
if os.geteuid():
sys.exit('['+R+'-'+W+'] Please run as root')
clients_APs = []
APs = []
DN = open(os.devnull, 'w')
lock = Lock()
args = parse_args()
monitor_on = None
mon_iface = get_mon_iface(args)
conf.iface = mon_iface
mon_MAC = mon_mac(mon_iface)
first_pass = 1
# Start channel hopping
hop = Thread(target=channel_hop, args=(mon_iface, args))
hop.daemon = True
hop.start()
signal(SIGINT, stop)
print "sniffing"
sniff(iface=mon_iface, store=0, prn=cb)
|
The Pieces
I'll cover some of the code for the components of this script in detail below. The script consists of a few sets of functions:
- Interface settings - looking for available interfaces, getting information about them, bring interfaces online or offline, and putting interfaces into monitor mode.
- Channel hop behavior - hopping through channels slowly or quickly, how often, and when, and so forth
- Output and filtering - functions for how to print out the information that's been gathered
- Packet handling - functions to handle information from individual packets, extract sources and destinations, accumulate traffic statistics, etc.
Interface settings
The first interface settings function is get_mon_iface
, which looks for network interfaces and interfaces in monitor mode.
def get_mon_iface(args): global monitor_on monitors, interfaces = iwconfig() if args.interface: monitor_on = True return args.interface if len(monitors) > 0: monitor_on = True return monitors[0] else: # Start monitor mode on a wireless interface print '['+G+'*'+W+'] Finding the most powerful interface...' interface = get_iface(interfaces) monmode = start_mon_mode(interface) return monmode
This method requires a few other methods:
iwconfig()
compiles a list of interfacesget_iface()
picks the most powerful interfacestart_mon_mode()
puts the interface in monitor mode using ifconfig and iwconfig
Compile list of interfaces
First, it calls the iwconfig method to compile a list of interfaces and interfaces that are in monitor mode. If this does not return any interfaces in monitor mode, it automatically picks an interface to put into monitor mode.
def iwconfig(): monitors = [] interfaces = {} try: proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN) except OSError: sys.exit('['+R+'-'+W+'] Could not execute "iwconfig"') for line in proc.communicate()[0].split('\n'): if len(line) == 0: continue # Isn't an empty string if line[0] != ' ': # Doesn't start with space wired_search = re.search('eth[0-9]|em[0-9]|p[1-9]p[1-9]', line) if not wired_search: # Isn't wired iface = line[:line.find(' ')] # is the interface if 'Mode:Monitor' in line: monitors.append(iface) elif 'IEEE 802.11' in line: if "ESSID:\"" in line: interfaces[iface] = 1 else: interfaces[iface] = 0 return monitors, interfaces
Find strongest interface
Next, the method for finding the most powerful interface:
def get_iface(interfaces): scanned_aps = [] if len(interfaces) < 1: sys.exit('['+R+'-'+W+'] No wireless interfaces found, bring one up and try again') if len(interfaces) == 1: for interface in interfaces: return interface # Find most powerful interface for iface in interfaces: count = 0 proc = Popen(['iwlist', iface, 'scan'], stdout=PIPE, stderr=DN) for line in proc.communicate()[0].split('\n'): if ' - Address:' in line: # first line in iwlist scan for a new AP count += 1 scanned_aps.append((count, iface)) print '['+G+'+'+W+'] Networks discovered by '+G+iface+W+': '+T+str(count)+W try: interface = max(scanned_aps)[1] return interface except Exception as e: for iface in interfaces: interface = iface print '['+R+'-'+W+'] Minor error:',e print ' Starting monitor mode on '+G+interface+W return interface
Put interface into monitor mode
Finally, a method to put the selected interface into monitor mode:
def start_mon_mode(interface): print '['+G+'+'+W+'] Starting monitor mode off '+G+interface+W try: os.system('ifconfig %s down' % interface) os.system('iwconfig %s mode monitor' % interface) os.system('ifconfig %s up' % interface) return interface except Exception: sys.exit('['+R+'-'+W+'] Could not start monitor mode')
Channel hop settings
The channel-hopping method used in this script can be customized to do whatever is needed. It uses the iw
command to change the channel of the wireless interfaces. As programmed, this function will loop through each channel for 1 second, then will skip through all channels rapidly.
def channel_hop(mon_iface, args): ''' First time through, scan each channel for 1 second. Then skip through all channels quickly. ''' global monchannel, first_pass channelNum = 0 maxChan = 11 if not args.world else 13 err = None while 1: if args.channel: with lock: monchannel = args.channel else: channelNum +=1 if channelNum > maxChan: channelNum = 1 with lock: first_pass = 0 with lock: monchannel = str(channelNum) try: proc = Popen(['iw', 'dev', mon_iface, 'set', 'channel', monchannel], stdout=DN, stderr=PIPE) except OSError: print '['+R+'-'+W+'] Could not execute "iw"' os.kill(os.getpid(),SIGINT) sys.exit(1) for line in proc.communicate()[1].split('\n'): if len(line) > 2: # iw dev shouldnt display output unless there's an error err = '['+R+'-'+W+'] Channel hopping failed: '+R+line+W output(err, monchannel) if args.channel: time.sleep(.05) else: # For the first channel hop thru, do not deauth if first_pass == 1: time.sleep(1) continue print "switched to channel " ,monchannel
Output and filtering
This function is not the best, but it will work in a jam. It prints out a list of clients and right below that a list of access points, similar to the output of the Aircrack utility airodump-ng. However, it uses "clear" and does not refresh the screen, so it is choppy.
def output(err, monchannel): os.system('clear') if err: print err else: print '['+G+'+'+W+'] '+mon_iface+' channel: '+G+monchannel+W+'\n' if len(clients_APs) > 0: print ' Clients ch ESSID' # Print the clients list with lock: for ca in clients_APs: if len(ca) > 3: print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2].ljust(2)+' - '+T+ca[3]+W else: print '['+T+'*'+W+'] '+O+ca[0]+W+' - '+O+ca[1]+W+' - '+ca[2] if len(APs) > 0: print '\n Access Points ch ESSID' with lock: for ap in APs: print '['+T+'*'+W+'] '+O+ap[0]+W+' - '+ap[1].ljust(2)+' - '+T+ap[2]+W print ''
There is also a function called "noise_filter" that checks the MAC addresses of the packet's source and destination. If the MAC addresses are boring MAC addresses, or on an ignore list, it will alert the print method not to print out information from that packet.
def noise_filter(addr1, addr2): # Broadcast, broadcast, IPv6mcast, spanning tree, spanning tree, multicast, broadcast 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] for i in ignore: if i in addr1 or i in addr2: return True
Packet handling
A Python function must be written that will handle each packet as it is received by the wireless card. In this case the packet-handling function will check the destination and source of the packet, and add either or both to the client list or access point list.
def cb(pkt): ''' Look for dot11 packets that aren't to or from broadcast address, are type 1 or 2 (control, data), and append the addr1 and addr2 to the list of clients ''' global clients_APs, APs if args.maximum: if args.noupdate: if len(clients_APs) > int(args.maximum): return else: if len(clients_APs) > int(args.maximum): with lock: clients_APs = [] APs = [] # We're adding the AP and channel to the clients list at time of creation rather # than updating on the fly in order to avoid costly for loops that require a lock if pkt.haslayer(Dot11): if pkt.addr1 and pkt.addr2: pkt.addr1 = pkt.addr1.lower() pkt.addr2 = pkt.addr2.lower() # Filter out all other APs and clients if asked if args.accesspoint: if args.accesspoint not in [pkt.addr1, pkt.addr2]: return # Check if it's added to our AP list if pkt.haslayer(Dot11Beacon) or pkt.haslayer(Dot11ProbeResp): APs_add(clients_APs, APs, pkt, args.channel, args.world) # Ignore all the noisy packets like spanning tree if noise_filter(pkt.addr1, pkt.addr2): return # Management = 1, data = 2 if pkt.type in [1, 2]: clients_APs_add(clients_APs, pkt.addr1, pkt.addr2)
Adding clients and access points
Two functions are used for adding new clients and new access points to a "master list," against which every incoming packet is checked.
The first adds new access points, storing their SSID, BSSID, and channel number.
def APs_add(clients_APs, APs, pkt, chan_arg, world_arg): ssid = pkt[Dot11Elt].info bssid = pkt[Dot11].addr3.lower() try: # Thanks to airoscapy for below ap_channel = str(ord(pkt[Dot11Elt:3].info)) 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'] if ap_channel not in chans: return if chan_arg: if ap_channel != chan_arg: return except Exception as e: return if len(APs) == 0: with lock: return APs.append([bssid, ap_channel, ssid]) else: for b in APs: if bssid in b[0]: return with lock: return APs.append([bssid, ap_channel, ssid])
The second method checks the uniqueness of a MAC address and its associated access point - meaning, if this script sees "Foobar's iPhone" connected to a wireless network "Linksys," it will create a new record for the new client-AP pair; if it later sees "Foobar's iPhone" connected to a wireless network "7-11 Free Wifi," it will consider it a new client-AP pair, and will create a new record for it.
def clients_APs_add(clients_APs, addr1, addr2): if len(clients_APs) == 0: if len(APs) == 0: with lock: return clients_APs.append([addr1, addr2, monchannel]) else: AP_check(addr1, addr2) # Append new clients/APs if they're not in the list else: for ca in clients_APs: if addr1 in ca and addr2 in ca: return if len(APs) > 0: return AP_check(addr1, addr2) else: with lock: return clients_APs.append([addr1, addr2, monchannel])
Flags
scapy a Python library for interfacing with network devices and analyzing packets from Python.
Building Wireless Utilities: Scapy/Airodump Clone · Scapy/AP Scanner Analyzing Conversations: Scapy/Conversations Database: Scapy/Wifi Database Category:Scapy · Category:Python · Category:Networking
|
Wireless all things wireless.
Networking:
Software:
|
Networking pages and notes about computer networks.
Man in the Middle attack vectors on wired networks: Man in the Middle/Wired Packet analysis with Wireshark: Packet Analysis Linux networking: Linux/Networking
Using Aircrack: Aircrack Many Ways to Crack a Wifi: Cracking Wifi
Linux/Networking · Linux/SSH · Linux/File Server
Notes on OpenVPN: OpenVPN Setting Up a Static Key VPN: OpenVPN/Static Key
Domain Name Servers: DNS · Linux/DNS IP Version 6: IPv6
Wireshark · SSH · Stunnel · Tor · Ettercap · Aircrack · Tcpdump
Tunnels · HTTP and HTTPS · SSH Tunnels · Linux/SSH
|