diff --git a/wifite/attack/pmkid.py b/wifite/attack/pmkid.py index d1faae7..a2fbba8 100644 --- a/wifite/attack/pmkid.py +++ b/wifite/attack/pmkid.py @@ -24,8 +24,60 @@ class AttackPMKID(Attack): self.pcapng_file = Configuration.temp('pmkid.pcapng') + def get_existing_pmkid_file(self, bssid): + ''' + Load PMKID Hash from a previously-captured hash in ./hs/ + Returns: + The hashcat hash (hash*bssid*station*essid) if found. + None if not found. + ''' + if not os.path.exists(Configuration.wpa_handshake_dir): + return None + + bssid = bssid.lower().replace(':', '') + + file_re = re.compile('.*pmkid_.*\.16800') + for filename in os.listdir(Configuration.wpa_handshake_dir): + pmkid_filename = os.path.join(Configuration.wpa_handshake_dir, filename) + if not os.path.isfile(pmkid_filename): + continue + if not re.match(file_re, pmkid_filename): + continue + + with open(pmkid_filename, 'r') as pmkid_handle: + pmkid_hash = pmkid_handle.read().strip() + if pmkid_hash.count('*') < 3: + continue + existing_bssid = pmkid_hash.split('*')[1].lower().replace(':', '') + if existing_bssid == bssid: + return pmkid_filename + return None + + def run(self): # TODO: Check ./hs/ for previously-captured PMKID, skip to crack if found. + pmkid_file = None + + # Load exisitng has from filesystem + if Configuration.ignore_old_handshakes == False: + pmkid_file = self.get_existing_pmkid_file(self.target.bssid) + if pmkid_file is not None: + Color.pattack('PMKID', self.target, 'CAPTURE', + 'Loaded {C}existing{W} PMKID hash: {C}%s{W}\n' % pmkid_file) + + # Capture hash from live target. + if pmkid_file is None: + pmkid_file = self.capture_pmkid() + + if pmkid_file is None: + return False # No hash found. + + # Crack it. + self.success = self.crack_pmkid_file(pmkid_file) + return self.success + + + def capture_pmkid(self): self.keep_capturing = True self.timer = Timer(60) @@ -49,36 +101,49 @@ class AttackPMKID(Attack): if pmkid_hash is None: Color.pattack('PMKID', self.target, 'CAPTURE', - '{R}Failed{O} to capture PMKID.') - return False # No hash found. + '{R}Failed{O} to capture PMKID\n') + Color.pl("") + return None # No hash found. - Color.pattack('PMKID', self.target, 'CAPTURE', '{G}Captured PMKID{W}') + Color.clear_entire_line() + Color.pattack('PMKID', self.target, 'CAPTURE', '{G}Captured PMKID{W}\n') pmkid_file = self.save_pmkid(pmkid_hash) + return pmkid_file + + def crack_pmkid_file(self, pmkid_file): + ''' + Cracks file containing PMKID hash (*.16800). + If cracked, saves results in self.crack_result + Returns: + True if cracked, False otherwise. + ''' + with open(pmkid_file, 'r') as pmkid_handle: + Color.pl("\nPMKID_FILE:%s HASH:'%s'" % (pmkid_file, pmkid_handle.read())) # 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('\n{!} {O}Not cracking because {R}wordlist{O} is not found.') 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... ') + Color.clear_entire_line() + Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID...\n') key = Hashcat.crack_pmkid(pmkid_file) if key is None: # Failed to crack. - Color.pattack('PMKID', self.target, 'CRACK', - '{R}Failed{O} to crack PMKID ') + Color.clear_entire_line() + Color.pattack('PMKID', self.target, '{R}CRACK', + '{R}Failed{O} to crack PMKID\n') Color.pl("") return False else: # Successfully cracked. - Color.pattack('PMKID', self.target, '', - '{C}Cracked PMKID. Key: {G}%s{W}' % key) - Color.pl("") + Color.clear_entire_line() + Color.pattack('PMKID', self.target, 'CRACKED', '{C}Key: {G}%s{W}\n' % key) self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, - pmkid_hash, pmkid_file, key) + pmkid_file, key) self.crack_result.dump() - self.success = True return True @@ -111,6 +176,5 @@ class AttackPMKID(Attack): 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 diff --git a/wifite/attack/wpa.py b/wifite/attack/wpa.py index 4d9e77f..742538c 100755 --- a/wifite/attack/wpa.py +++ b/wifite/attack/wpa.py @@ -34,11 +34,8 @@ class AttackWPA(Attack): self.success = False return self.success - handshake = None - - # Capture the handshake ("do it live!") - if handshake is None: - handshake = self.capture_handshake() + # Capture the handshake (or use an old one) + handshake = self.capture_handshake() if handshake is None: # Failed to capture handshake diff --git a/wifite/model/pmkid_result.py b/wifite/model/pmkid_result.py index 755594e..11a7676 100644 --- a/wifite/model/pmkid_result.py +++ b/wifite/model/pmkid_result.py @@ -5,11 +5,10 @@ from ..util.color import Color from .result import CrackResult class CrackResultPMKID(CrackResult): - def __init__(self, bssid, essid, pmkid_hash, pmkid_file, key): + def __init__(self, bssid, essid, 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__() @@ -23,9 +22,6 @@ class CrackResultPMKID(CrackResult): ('Access Point BSSID'.rjust(19), self.bssid)) Color.pl('{+} %s: {C}%s{W}' % ('Encryption'.rjust(19), self.result_type)) - if self.pmkid_hash: - Color.pl('{+} %s: {C}%s{W}' % - ('PMKID Hash'.rjust(19), self.pmkid_hash)) if self.pmkid_file: Color.pl('{+} %s: {C}%s{W}' % ('PMKID File'.rjust(19), self.pmkid_file)) @@ -41,15 +37,14 @@ class CrackResultPMKID(CrackResult): 'essid' : self.essid, 'bssid' : self.bssid, 'key' : self.key, - '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', 'hs/pmkid_blah-123213.16800', 'abcd1234') + w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'abcd1234') w.dump() - w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'hs/pmkid_blah-123213.16800', 'Key') + w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'Key') print('\n') w.dump() w.save() diff --git a/wifite/model/result.py b/wifite/model/result.py index 8636ff9..1e9d770 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -94,7 +94,6 @@ class CrackResult(object): from .pmkid_result import CrackResultPMKID result = CrackResultPMKID(json['bssid'], json['essid'], - json['pmkid_hash'], json['pmkid_file'], json['key']) result.date = json['date'] diff --git a/wifite/tools/hashcat.py b/wifite/tools/hashcat.py new file mode 100644 index 0000000..c78f0ae --- /dev/null +++ b/wifite/tools/hashcat.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .dependency import Dependency +from ..config import Configuration +from ..util.process import Process +from ..util.color import Color + +import os + + +class Hashcat(Dependency): + dependency_required = False + dependency_name = 'hashcat' + dependency_url = 'https://hashcat.net/hashcat/' + + @staticmethod + def crack_pmkid(pmkid_file): + ''' + Cracks a given pmkid_file using the PMKID/WPA2 attack (-m 16800) + Returns: + Key (str) if found; `None` if not found. + ''' + + command = [ + 'hashcat', + '--force', + '--quiet', + '-m', '16800', + '-a', '0', # TODO: Configure + '-w', '2', # TODO: Configure + pmkid_file, + Configuration.wordlist + ] + + # 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 + Color.pl('\n{!} {O}Interrupted hashcat cracking{W}') + stdout = '' + + if ':' not in stdout: + # Failed + return None + else: + # Cracked + key = stdout.strip().split(':', 1)[1] + return key + + +class HcxDumpTool(Dependency): + dependency_required = False + dependency_name = 'hcxdumptool' + dependency_url = 'https://github.com/ZerBea/hcxdumptool' + + def __init__(self, target, pcapng_file): + # Create filterlist + filterlist = Configuration.temp('pmkid.filterlist') + with open(filterlist, 'w') as filter_handle: + filter_handle.write(target.bssid.replace(':', '')) + + if os.path.exists(pcapng_file): + os.remove(pcapng_file) + + command = [ + "hcxdumptool", + "-i", Configuration.interface, + "--filterlist", filterlist, + "--filtermode", "2", + "-c", str(target.channel), + "-o", pcapng_file + ] + + self.proc = Process(command) + + def poll(self): + return self.proc.poll() + + def interrupt(self): + self.proc.interrupt() + + +class HcxPcapTool(Dependency): + dependency_required = False + dependency_name = 'hcxpcaptool' + dependency_url = 'https://github.com/ZerBea/hcxtools' + + def __init__(self, target): + self.target = target + self.bssid = self.target.bssid.lower().replace(':', '') + self.pmkid_file = Configuration.temp('pmkid-%s.16800' % self.bssid) + + def get_pmkid_hash(self, pcapng_file): + if os.path.exists(self.pmkid_file): + os.remove(self.pmkid_file) + + command = [ + 'hcxpcaptool', + '-z', self.pmkid_file, + pcapng_file + ] + hcxpcap_proc = Process(command) + hcxpcap_proc.wait() + + if not os.path.exists(self.pmkid_file): + return None + + with open(self.pmkid_file, 'r') as f: + output = f.read() + # Each line looks like: + # hash*bssid*station*essid + + # Note: The dumptool will record *anything* it finds, ignoring the filterlist. + # Check that we got the right target (filter by BSSID) + matching_pmkid_hash = None + for line in output.split('\n'): + fields = line.split('*') + if len(fields) >= 3 and fields[1].lower() == self.bssid: + # Found it + matching_pmkid_hash = line + break + + os.remove(self.pmkid_file) + return matching_pmkid_hash