diff --git a/wifite/attack/pmkid.py b/wifite/attack/pmkid.py index a63ada3..dbbf877 100755 --- a/wifite/attack/pmkid.py +++ b/wifite/attack/pmkid.py @@ -97,7 +97,12 @@ class AttackPMKID(Attack): return False # No hash found. # Crack it. - self.success = self.crack_pmkid_file(pmkid_file) + try: + self.success = self.crack_pmkid_file(pmkid_file) + except KeyboardInterrupt: + Color.pl('\n{!} {R}Failed to crack PMKID: {O}Cracking interrupted by user{W}') + self.success = False + return False return True # Even if we don't crack it, capturing a PMKID is 'successful' diff --git a/wifite/attack/wpa.py b/wifite/attack/wpa.py index 469ffa8..2e982ea 100755 --- a/wifite/attack/wpa.py +++ b/wifite/attack/wpa.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from ..model.attack import Attack +from ..tools.aircrack import Aircrack from ..tools.airodump import Airodump from ..tools.aireplay import Aireplay from ..config import Configuration @@ -47,11 +48,29 @@ class AttackWPA(Attack): Color.pl('\n{+} analysis of captured handshake file:') handshake.analyze() + # Check wordlist + if Configuration.wordlist is None: + Color.pl('{!} {O}Not cracking handshake because' + + ' wordlist ({R}--dict{O}) is not set') + self.success = False + return False + + elif not os.path.exists(Configuration.wordlist): + Color.pl('{!} {O}Not cracking handshake because' + + ' wordlist {R}%s{O} was not found' % Configuration.wordlist) + self.success = False + return False + + Color.pl('\n{+} {C}Cracking WPA Handshake:{W} Using {C}aircrack-ng{W} via' + + ' {C}%s{W} wordlist' % os.path.split(Configuration.wordlist)[-1]) + # Crack it - key = self.crack_handshake(handshake, Configuration.wordlist) + key = Aircrack.crack_handshake(handshake, Configuration.wordlist) if key is None: + Color.pl('{!} {R}Failed to crack handshake: {O}%s{R} did not contain password{W}' % Configuration.wordlist.split(os.sep)[-1]) self.success = False else: + Color.pl('{+} {G}Cracked WPA Handshake{W} PSK: {G}%s{W}\n' % key) self.crack_result = CrackResultWPA(handshake.bssid, handshake.essid, handshake.capfile, key) self.crack_result.dump() self.success = True @@ -157,84 +176,6 @@ class AttackWPA(Attack): self.save_handshake(handshake) return handshake - @staticmethod - def crack_handshake(handshake, wordlist, verbose=False): - '''Tries to crack a handshake. Returns WPA key if found, otherwise None.''' - if wordlist is None: - Color.pl('{!} {O}Not cracking handshake because' + - ' wordlist ({R}--dict{O}) is not set') - return None - elif not os.path.exists(wordlist): - Color.pl('{!} {O}Not cracking handshake because' + - ' wordlist {R}%s{O} was not found' % wordlist) - return None - - if not verbose: - Color.pl('\n{+} {C}Cracking WPA Handshake:{W} Using {C}aircrack-ng{W} via' + - ' {C}%s{W} wordlist' % os.path.split(wordlist)[-1]) - - key_file = Configuration.temp('wpakey.txt') - command = [ - 'aircrack-ng', - '-a', '2', - '-w', wordlist, - '--bssid', handshake.bssid, - '-l', key_file, - handshake.capfile - ] - if verbose: - Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) - crack_proc = Process(command) - - # Report progress of cracking - aircrack_nums_re = re.compile(r'(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s') - aircrack_key_re = re.compile(r'Current passphrase:\s*([^\s].*[^\s])\s*$') - num_tried = num_total = 0 - percent = num_kps = 0.0 - eta_str = 'unknown' - current_key = '' - while crack_proc.poll() is None: - line = crack_proc.pid.stdout.readline() - match_nums = aircrack_nums_re.search(line.decode('utf-8')) - match_keys = aircrack_key_re.search(line.decode('utf-8')) - if match_nums: - num_tried = int(match_nums.group(1)) - num_total = int(match_nums.group(2)) - num_kps = float(match_nums.group(3)) - eta_seconds = (num_total - num_tried) / num_kps - eta_str = Timer.secs_to_str(eta_seconds) - percent = 100.0 * float(num_tried) / float(num_total) - elif match_keys: - current_key = match_keys.group(1) - else: - continue - - status = '\r{+} {C}Cracking WPA Handshake: %0.2f%%{W}' % percent - status += ' ETA: {C}%s{W}' % eta_str - status += ' @ {C}%0.1fkps{W}' % num_kps - #status += ' ({C}%d{W}/{C}%d{W} keys)' % (num_tried, num_total) - status += ' (current key: {C}%s{W})' % current_key - if not verbose: - Color.clear_entire_line() - Color.p(status) - - if not verbose: - Color.pl('') - - # Check crack result - if os.path.exists(key_file): - with open(key_file, 'r') as fid: - key = fid.read().strip() - os.remove(key_file) - - if not verbose: - Color.pl('{+} {G}Cracked WPA Handshake{W} PSK: {G}%s{W}\n' % key) - return key - else: - if not verbose: - Color.pl('{!} {R}Failed to crack handshake: {O}%s{R} did not contain password{W}' % wordlist.split(os.sep)[-1]) - return None - def load_handshake(self, bssid, essid): if not os.path.exists(Configuration.wpa_handshake_dir): return None diff --git a/wifite/config.py b/wifite/config.py index 2edbe4e..4fe37c9 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -84,9 +84,10 @@ class Configuration(object): # Default dictionary for cracking cls.wordlist = None wordlists = [ - './wordlist-top4800-probable.txt', - '/usr/share/wordlists/wordlist-top4800-probable.txt', - '/usr/local/share/wordlists/wordlist-top4800-probable.txt', + './wordlist-top4800-probable.txt', # Local file (ran from cloned repo) + '/usr/dict/wordlists/wordlist-top4800-probable.txt', # setup.py with prefix=/usr + '/usr/local/dict/wordlists/wordlist-top4800-probable.txt', # setup.py with prefix=/usr/local + # Other passwords found on Kali '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/wordlists/fern-wifi/common.txt' diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index 3177b70..6303570 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -38,7 +38,7 @@ class Handshake(object): if len(pairs) == 0 and not self.bssid and not self.essid: # Tshark and Pyrit failed us, nothing else we can do. - raise Exception('Cannot find BSSID or ESSID in cap file') + raise ValueError('Cannot find BSSID or ESSID in cap file %s' % self.capfile) if not self.essid and not self.bssid: # We do not know the bssid nor the essid diff --git a/wifite/tools/aircrack.py b/wifite/tools/aircrack.py index dbad9e9..bb0bbe2 100755 --- a/wifite/tools/aircrack.py +++ b/wifite/tools/aircrack.py @@ -7,6 +7,7 @@ from ..util.input import xrange from ..config import Configuration import os +import re class Aircrack(Dependency): dependency_required = True @@ -77,6 +78,70 @@ class Aircrack(Dependency): if os.path.exists(self.cracked_file): os.remove(self.cracked_file) + + @staticmethod + def crack_handshake(handshake, show_command=False): + from ..util.color import Color + from ..util.timer import Timer + '''Tries to crack a handshake. Returns WPA key if found, otherwise None.''' + + key_file = Configuration.temp('wpakey.txt') + command = [ + 'aircrack-ng', + '-a', '2', + '-w', Configuration.wordlist, + '--bssid', handshake.bssid, + '-l', key_file, + handshake.capfile + ] + if show_command: + Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) + crack_proc = Process(command) + + # Report progress of cracking + aircrack_nums_re = re.compile(r'(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s') + aircrack_key_re = re.compile(r'Current passphrase:\s*([^\s].*[^\s])\s*$') + num_tried = num_total = 0 + percent = num_kps = 0.0 + eta_str = 'unknown' + current_key = '' + while crack_proc.poll() is None: + line = crack_proc.pid.stdout.readline() + match_nums = aircrack_nums_re.search(line.decode('utf-8')) + match_keys = aircrack_key_re.search(line.decode('utf-8')) + if match_nums: + num_tried = int(match_nums.group(1)) + num_total = int(match_nums.group(2)) + num_kps = float(match_nums.group(3)) + eta_seconds = (num_total - num_tried) / num_kps + eta_str = Timer.secs_to_str(eta_seconds) + percent = 100.0 * float(num_tried) / float(num_total) + elif match_keys: + current_key = match_keys.group(1) + else: + continue + + status = '\r{+} {C}Cracking WPA Handshake: %0.2f%%{W}' % percent + status += ' ETA: {C}%s{W}' % eta_str + status += ' @ {C}%0.1fkps{W}' % num_kps + #status += ' ({C}%d{W}/{C}%d{W} keys)' % (num_tried, num_total) + status += ' (current key: {C}%s{W})' % current_key + Color.clear_entire_line() + Color.p(status) + + Color.pl('') + + # Check crack result + if os.path.exists(key_file): + with open(key_file, 'r') as fid: + key = fid.read().strip() + os.remove(key_file) + + return key + else: + return None + + if __name__ == '__main__': (hexkey, asciikey) = Aircrack._hex_and_ascii_key('A1B1C1D1E1') assert hexkey == 'A1:B1:C1:D1:E1', 'hexkey was "%s", expected "A1:B1:C1:D1:E1"' % hexkey diff --git a/wifite/tools/cowpatty.py b/wifite/tools/cowpatty.py new file mode 100644 index 0000000..144c48e --- /dev/null +++ b/wifite/tools/cowpatty.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .dependency import Dependency +from ..config import Configuration +from ..util.color import Color +from ..util.process import Process +from ..tools.hashcat import HcxPcapTool + +import os +import re + + +class Cowpatty(Dependency): + ''' Wrapper for Cowpatty program. ''' + dependency_required = False + dependency_name = 'cowpatty' + dependency_url = 'https://tools.kali.org/wireless-attacks/cowpatty' + + + @staticmethod + def crack_handshake(handshake, show_command=False): + # Crack john file + command = [ + 'cowpatty', + '-f', Configuration.wordlist, + '-r', handshake.capfile, + '-s', handshake.essid + ] + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + process = Process(command) + stdout, stderr = process.get_output() + + key = None + for line in stdout.split('\n'): + if 'The PSK is "' in line: + key = line.split('"', 1)[1][:-2] + break + + return key diff --git a/wifite/tools/hashcat.py b/wifite/tools/hashcat.py index 88b6e49..0e01a3d 100755 --- a/wifite/tools/hashcat.py +++ b/wifite/tools/hashcat.py @@ -14,6 +14,47 @@ class Hashcat(Dependency): dependency_name = 'hashcat' dependency_url = 'https://hashcat.net/hashcat/' + @staticmethod + def should_use_force(): + command = ['hashcat', '-I'] + stderr = Process(command).stderr() + return 'No devices found/left' in stderr + + @staticmethod + def crack_handshake(handshake, show_command=False): + # Generate hccapx + hccapx_file = HcxPcapTool.generate_hccapx_file( + handshake, show_command=show_command) + + key = None + # Crack hccapx + for additional_arg in [ [], ['--show']]: + command = [ + 'hashcat', + '--quiet', + '-m', '2500', + hccapx_file, + Configuration.wordlist + ] + if Hashcat.should_use_force(): + command.append('--force') + command.extend(additional_arg) + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + process = Process(command) + stdout, stderr = process.get_output() + if ':' not in stdout: + continue + else: + key = stdout.split(':', 5)[-1].strip() + break + + if os.path.exists(hccapx_file): + os.remove(hccapx_file) + + return key + + @staticmethod def crack_pmkid(pmkid_file, verbose=False): ''' @@ -27,27 +68,23 @@ class Hashcat(Dependency): for additional_arg in [ [], ['--show']]: command = [ 'hashcat', - '--force', '--quiet', # Only output the password if found. '-m', '16800', # WPA-PMKID-PBKDF2 - '-a', '0', # TODO: Configure - '-w', '2', # TODO: Configure + '-a', '0', # Wordlist attack-mode pmkid_file, Configuration.wordlist ] + if Hashcat.should_use_force(): + command.append('--force') command.extend(additional_arg) if verbose and additional_arg == []: Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) # 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 = '' + hashcat_proc = Process(command) + hashcat_proc.wait() + stdout = hashcat_proc.stdout() if ':' not in stdout: # Failed @@ -100,6 +137,52 @@ class HcxPcapTool(Dependency): self.bssid = self.target.bssid.lower().replace(':', '') self.pmkid_file = Configuration.temp('pmkid-%s.16800' % self.bssid) + @staticmethod + def generate_hccapx_file(handshake, show_command=False): + hccapx_file = Configuration.temp('generated.hccapx') + if os.path.exists(hccapx_file): + os.remove(hccapx_file) + + command = [ + 'hcxpcaptool', + '-o', hccapx_file, + handshake.capfile + ] + + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + + process = Process(command) + stdout, stderr = process.get_output() + if not os.path.exists(hccapx_file): + raise ValueError('Failed to generate .hccapx file, output: \n%s\n%s' % ( + stdout, stderr)) + + return hccapx_file + + @staticmethod + def generate_john_file(handshake, show_command=False): + john_file = Configuration.temp('generated.john') + if os.path.exists(john_file): + os.remove(john_file) + + command = [ + 'hcxpcaptool', + '-j', john_file, + handshake.capfile + ] + + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + + process = Process(command) + stdout, stderr = process.get_output() + if not os.path.exists(john_file): + raise ValueError('Failed to generate .john file, output: \n%s\n%s' % ( + stdout, stderr)) + + return john_file + def get_pmkid_hash(self, pcapng_file): if os.path.exists(self.pmkid_file): os.remove(self.pmkid_file) diff --git a/wifite/tools/john.py b/wifite/tools/john.py new file mode 100644 index 0000000..d61f8c3 --- /dev/null +++ b/wifite/tools/john.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .dependency import Dependency +from ..config import Configuration +from ..util.color import Color +from ..util.process import Process +from ..tools.hashcat import HcxPcapTool + +import os + + +class John(Dependency): + ''' Wrapper for John program. ''' + dependency_required = False + dependency_name = 'john' + dependency_url = 'http://www.openwall.com/john/' + + + @staticmethod + def crack_handshake(handshake, show_command=False): + john_file = HcxPcapTool.generate_john_file(handshake, show_command=show_command) + + # Crack john file + command = [ + 'john', + '--format=wpapsk', # wpapsk-cuda or wpapsk-opencl + '--wordlist', Configuration.wordlist, + john_file + ] + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + process = Process(command) + process.wait() + + # Show the password (if found) + command = ['john', '--show', john_file] + if show_command: + Color.pl('{+} {D}{C}Running %s{W}' % ' '.join(command)) + process = Process(command) + stdout, stderr = process.get_output() + + key = None + if not '0 password hashes cracked' in stdout: + for line in stdout.split('\n'): + if handshake.capfile in line: + key = line.split(':')[1] + break + + if os.path.exists(john_file): + os.remove(john_file) + + return key diff --git a/wifite/util/crack.py b/wifite/util/crack.py index 1ebba0b..c30bf84 100755 --- a/wifite/util/crack.py +++ b/wifite/util/crack.py @@ -1,10 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from ..config import Configuration +from ..model.handshake import Handshake +from ..model.wpa_result import CrackResultWPA +from ..model.pmkid_result import CrackResultPMKID from ..util.process import Process from ..util.color import Color from ..util.input import raw_input -from ..config import Configuration +from ..tools.aircrack import Aircrack +from ..tools.cowpatty import Cowpatty +from ..tools.hashcat import Hashcat, HcxPcapTool +from ..tools.john import John from datetime import datetime @@ -19,8 +26,8 @@ class CrackHelper: '''Manages handshake retrieval, selection, and running the cracking commands.''' TYPES = { - '4-WAY': 'WPA 4-Way Handshake', - 'PMKID': 'WPA PKID Hash' + '4-WAY': '4-Way Handshake', + 'PMKID': 'PMKID Hash' } @@ -28,6 +35,7 @@ class CrackHelper: def run(cls): Configuration.initialize(False) + # Get wordlist if not Configuration.wordlist: Color.p('\n{+} Enter wordlist file to use for cracking: {G}') Configuration.wordlist = raw_input() @@ -36,16 +44,35 @@ class CrackHelper: return Color.pl('') + # Get handshakes handshakes = cls.get_handshakes() if len(handshakes) == 0: Color.pl('{!} {O}No handshakes found{W}') return + hs_to_crack = cls.get_user_selection(handshakes) - # TODO: Ask what method to use for WPA (aircrack, pyrit, john, hashcat, cowpatty) + # Get tool + available_tools = ['aircrack', 'hashcat', 'john', 'cowpatty'] + if not Process.exists(HcxPcapTool.dependency_name): + Color.pl('{!} {R}Unable to use hashcat: {O}missing required hcxpcaptool{W}') + available_tools.remove('hashcat') + if not Process.exists(John.dependency_name): + Color.pl('{!} {R}Unable to use john: {O}missing required "john" program{W}') + available_tools.remove('john') - for hs in hs_to_crack: - cls.crack(hs) + Color.p('{+} Enter the {C}cracking tool{W} to use ({C}%s{W}): {G}' % ( + '{W}, {C}'.join(available_tools))) + tool_name = raw_input() + if tool_name not in available_tools: + Color.pl('{!} {O}%s not found, defaulting to aircrack' % tool_name) + tool_name = 'aircrack' + + try: + for hs in hs_to_crack: + cls.crack(hs, tool_name) + except KeyboardInterrupt: + Color.pl('\n{!} {O}Interrupted{W}') @classmethod def get_handshakes(cls): @@ -136,7 +163,7 @@ class CrackHelper: def get_user_selection(cls, handshakes): cls.print_handshakes(handshakes) - Color.p('{+} Select handshake(s) to crack ({G}%d{W}-{G}%d{W}, select multiple with {C},{W} or {C}-{W}): {G}' % (1, len(handshakes))) + Color.p('{+} Select handshake(s) to crack ({G}%d{W}-{G}%d{W}, select multiple with {C},{W} or {C}-{W} or {C}all{W}): {G}' % (1, len(handshakes))) choices = raw_input() selection = [] @@ -145,7 +172,10 @@ class CrackHelper: first, last = [int(x) for x in choice.split('-')] for index in range(first, last + 1): selection.append(handshakes[index-1]) - else: + elif choice.strip().lower() == 'all': + selection = handshakes[:] + break + elif [c.isdigit() for c in choice]: index = int(choice) selection.append(handshakes[index-1]) @@ -153,12 +183,14 @@ class CrackHelper: @classmethod - def crack(cls, hs): - Color.pl('\n{+} Cracking {C}%s{W} ({C}%s{W}) using {G}%s{W} method' % (hs['essid'], hs['bssid'], hs['type'])) + def crack(cls, hs, tool): + Color.pl('\n{+} Cracking {G}%s {C}%s{W} ({C}%s{W})' % ( + cls.TYPES[hs['type']], hs['essid'], hs['bssid'])) + if hs['type'] == 'PMKID': - crack_result = cls.crack_pmkid(hs) + crack_result = cls.crack_pmkid(hs, tool) elif hs['type'] == '4-WAY': - crack_result = cls.crack_4way(hs) + crack_result = cls.crack_4way(hs, tool) else: raise ValueError('Cannot crack handshake: Type is not PMKID or 4-WAY. Handshake=%s' % hs) @@ -174,20 +206,25 @@ class CrackHelper: @classmethod - def crack_4way(cls, hs): - from ..attack.wpa import AttackWPA - from ..model.handshake import Handshake - from ..model.wpa_result import CrackResultWPA + def crack_4way(cls, hs, tool): handshake = Handshake(hs['filename'], bssid=hs['bssid'], essid=hs['essid']) - - key = None try: - key = AttackWPA.crack_handshake(handshake, Configuration.wordlist, verbose=True) - except KeyboardInterrupt: - Color.pl('\n{!} Interrupted') + handshake.divine_bssid_and_essid() + except ValueError as e: + Color.pl('{!} {R}Error: {O}%s{W}' % e) + return None + + if tool == 'aircrack': + key = Aircrack.crack_handshake(handshake, show_command=True) + elif tool == 'hashcat': + key = Hashcat.crack_handshake(handshake, show_command=True) + elif tool == 'john': + key = John.crack_handshake(handshake, show_command=True) + elif tool == 'cowpatty': + key = Cowpatty.crack_handshake(handshake, show_command=True) if key is not None: return CrackResultWPA(hs['bssid'], hs['essid'], hs['filename'], key) @@ -196,15 +233,11 @@ class CrackHelper: @classmethod - def crack_pmkid(cls, hs): - from ..tools.hashcat import Hashcat - from ..model.pmkid_result import CrackResultPMKID + def crack_pmkid(cls, hs, tool_name): + if tool_name != 'hashcat': + Color.pl('{!} {O}Note: PMKIDs can only be cracked using hashcat{W}') - key = None - try: - key = Hashcat.crack_pmkid(hs['filename'], verbose=True) - except KeyboardInterrupt: - Color.pl('\n{!} Interrupted') + key = Hashcat.crack_pmkid(hs['filename'], verbose=True) if key is not None: return CrackResultPMKID(hs['bssid'], hs['essid'], hs['filename'], key) diff --git a/wifite/wifite.py b/wifite/wifite.py index 3b2bdf2..8713a7d 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -81,7 +81,7 @@ class Wifite(object): # Attack attacked_targets = AttackAll.attack_multiple(targets) - Color.pl('{+} Finished attacking {C}%d{W} target(s), exiting' % attacked_targets) + Color.pl('\n{+} Finished attacking {C}%d{W} target(s), exiting' % attacked_targets) ##############################################################