diff --git a/wifite/attack/pmkid.py b/wifite/attack/pmkid.py index db4b5e8..d1faae7 100644 --- a/wifite/attack/pmkid.py +++ b/wifite/attack/pmkid.py @@ -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 diff --git a/wifite/model/pmkid_result.py b/wifite/model/pmkid_result.py index 4b77900..755594e 100644 --- a/wifite/model/pmkid_result.py +++ b/wifite/model/pmkid_result.py @@ -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() diff --git a/wifite/model/result.py b/wifite/model/result.py index f1d59d3..8636ff9 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -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 diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 4ef388b..05d1dea 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -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() diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index 839e6dc..a8b86a3 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -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', diff --git a/wifite/util/scanner.py b/wifite/util/scanner.py index b5e36f7..024dff0 100755 --- a/wifite/util/scanner.py +++ b/wifite/util/scanner.py @@ -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':