From a157132387a56143cba2644a27aa35dfc76ab653 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 19 Aug 2018 10:24:00 -0700 Subject: [PATCH] 2.1.9: --pmkid option, cleaned up --cracked, other bug fixes. PMKID: * `--pmkid` option only attacks WPA networks with PMKID capture + crack * Decreased PMKID capture time from 60 seconds to 15 seconds. * Ignores PMKID attack if `--wps-only` is set. WPS: * Ctrl+C while waiting for `bully` to fetch PSK = remembers PIN, PSK is unknown. Misc: * `--cracked` prints results on single lines (much easier to read) * Fixed typo when required dependencies are not found (closes #127) --- wifite/args.py | 6 +++++ wifite/attack/all.py | 5 ++-- wifite/attack/pmkid.py | 17 +++++++++---- wifite/attack/wpa.py | 12 +++++++-- wifite/attack/wps.py | 6 ++++- wifite/config.py | 6 ++++- wifite/model/pmkid_result.py | 7 ++++++ wifite/model/result.py | 47 +++++++++++++++++++++++++++++++----- wifite/model/wep_result.py | 11 ++++++++- wifite/model/wpa_result.py | 9 ++++++- wifite/model/wps_result.py | 9 +++++++ wifite/tools/dependency.py | 6 ++--- wifite/tools/reaver.py | 6 ++++- wifite/util/crack.py | 22 +++++++++++------ 14 files changed, 139 insertions(+), 30 deletions(-) diff --git a/wifite/args.py b/wifite/args.py index 6213feb87..442ea9411 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -280,6 +280,12 @@ def _add_wpa_args(self, wpa): help=self._verbose('Time to wait before failing WPA attack (default: {G}%d sec{W})' % self.config.wpa_attack_timeout)) wpa.add_argument('-wpat', help=argparse.SUPPRESS, action='store', dest='wpa_attack_timeout', type=int) + wpa.add_argument('--pmkid', + '-pmkid', + action='store_true', + dest='use_pmkid_only', + help=Color.s('ONLY use PMKID capture on WPA endpoints (default: {G}off{W})')) + wpa.add_argument('--new-hs', action='store_true', dest='ignore_old_handshakes', diff --git a/wifite/attack/all.py b/wifite/attack/all.py index ce77d59cb..6c99a9b66 100755 --- a/wifite/attack/all.py +++ b/wifite/attack/all.py @@ -67,7 +67,8 @@ def attack_single(cls, target, targets_remaining): Color.pl('{!} {R}Error: {O}unable to attack: encryption not WEP or WPA') return - for attack in attacks: + while len(attacks) > 0: + attack = attacks.pop(0) try: result = attack.run() if result: @@ -77,7 +78,7 @@ def attack_single(cls, target, targets_remaining): continue except KeyboardInterrupt: Color.pl('\n{!} {O}interrupted{W}\n') - if not cls.user_wants_to_continue(targets_remaining, 1): + if not cls.user_wants_to_continue(targets_remaining, len(attacks)): return False # Stop attacking other targets if attack.success: diff --git a/wifite/attack/pmkid.py b/wifite/attack/pmkid.py index e3e7f385b..a63ada389 100755 --- a/wifite/attack/pmkid.py +++ b/wifite/attack/pmkid.py @@ -62,6 +62,12 @@ def run(self): Returns: True if handshake is captured. False otherwise. ''' + # Skip if user only wants to run PixieDust attack + if Configuration.wps_only and self.target.wps: + Color.pl('\r{!} {O}Skipping PMKID attack on {R}%s{O} because {R}--wps-only{O} is set{W}' % self.target.essid) + self.success = False + return False + from ..util.process import Process # Check that we have all hashcat programs dependencies = [ @@ -103,7 +109,7 @@ def capture_pmkid(self): The PMKID hash (str) if found, otherwise None. ''' self.keep_capturing = True - self.timer = Timer(60) + self.timer = Timer(15) # Start hcxdumptool t = Thread(target=self.dumptool_thread) @@ -159,10 +165,11 @@ def crack_pmkid_file(self, pmkid_file): if key is None: # Failed to crack. - Color.clear_entire_line() - Color.pattack('PMKID', self.target, '{R}CRACK', - '{R}Failed{O}: passphrase not found in dictionary.\n') - Color.pl('') + if Configuration.wordlist is not None: + Color.clear_entire_line() + Color.pattack('PMKID', self.target, '{R}CRACK', + '{R}Failed {O}Passphrase not found in dictionary.\n') + Color.pl('') return False else: # Successfully cracked. diff --git a/wifite/attack/wpa.py b/wifite/attack/wpa.py index 613d4f263..469ffa822 100755 --- a/wifite/attack/wpa.py +++ b/wifite/attack/wpa.py @@ -26,9 +26,12 @@ def __init__(self, target): def run(self): '''Initiates full WPA handshake capture attack.''' + if Configuration.use_pmkid_only: + self.success = False + return False # Skip if user only wants to run PixieDust attack if Configuration.wps_only and self.target.wps: - Color.pl('\r{!} {O}--wps-only{R} set, ignoring WPA-handshake attack on {O}%s{W}' % self.target.essid) + Color.pl('\r{!} {O}Skipping WPA-Handshake attack on {R}%s{O} because {R}--wps-only{O} is set{W}' % self.target.essid) self.success = False return self.success @@ -110,7 +113,12 @@ def capture_handshake(self): handshake = Handshake(temp_file, bssid=bssid, essid=essid) if handshake.has_handshake(): # We got a handshake - Color.pl('\n\n{+} {G}successfully captured handshake{W}') + Color.clear_entire_line() + Color.pattack('WPA', + airodump_target, + 'Handshake capture', + '{G}Captured handshake{W}') + Color.pl('') break # There is no handshake diff --git a/wifite/attack/wps.py b/wifite/attack/wps.py index afd25d377..fd40d9548 100755 --- a/wifite/attack/wps.py +++ b/wifite/attack/wps.py @@ -15,10 +15,14 @@ def run(self): ''' Run all WPS-related attacks ''' # Drop out if user specified to not use Reaver/Bully + if Configuration.use_pmkid_only: + self.success = False + return False + if Configuration.no_wps: Color.pl('\r{!} {O}--no-wps{R} set, ignoring WPS attack on {O}%s{W}' % self.target.essid) self.success = False - return self.success + return False if Configuration.use_bully: return self.run_bully() diff --git a/wifite/config.py b/wifite/config.py index 2b851a680..a7b58d3a7 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -8,7 +8,7 @@ class Configuration(object): ''' Stores configuration variables and functions for Wifite. ''' - version = '2.1.8' + version = '2.1.9' initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory @@ -79,6 +79,7 @@ def initialize(cls, load_interface=True): cls.wpa_handshake_dir = 'hs' # Dir to store handshakes cls.wpa_strip_handshake = False # Strip non-handshake packets cls.ignore_old_handshakes = False # Always fetch a new handshake + cls.use_pmkid_only = False # Only use PMKID Capture+Crack attack # Default dictionary for cracking cls.wordlist = None @@ -227,6 +228,9 @@ def load_from_arguments(cls): if args.ignore_old_handshakes: cls.ignore_old_handshakes = True Color.pl('{+} {C}option:{W} will {O}ignore{W} existing handshakes (force capture)') + if args.use_pmkid_only: + cls.use_pmkid_only = True + Color.pl('{+} {C}option:{W} will ONLY use {C}PMKID{W} attack on WPA networks') if args.wpa_handshake_dir: cls.wpa_handshake_dir = args.wpa_handshake_dir Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir) diff --git a/wifite/model/pmkid_result.py b/wifite/model/pmkid_result.py index 11a767623..9ba2647ab 100755 --- a/wifite/model/pmkid_result.py +++ b/wifite/model/pmkid_result.py @@ -30,6 +30,13 @@ def dump(self): else: Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) + def print_single_line(self, longest_essid): + self.print_single_line_prefix(longest_essid) + Color.p('{G}%s{W}' % 'PMKID'.ljust(5)) + Color.p(' ') + Color.p('Key: {G}%s{W}' % self.key) + Color.pl('') + def to_dict(self): return { 'type' : self.result_type, diff --git a/wifite/model/result.py b/wifite/model/result.py index 2c7a36209..d1926f96e 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -15,6 +15,7 @@ class CrackResult(object): def __init__(self): self.date = int(time.time()) + self.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.date)) def dump(self): raise Exception('Unimplemented method: dump()') @@ -22,6 +23,19 @@ def dump(self): def to_dict(self): raise Exception('Unimplemented method: to_dict()') + def print_single_line(self, longest_essid): + raise Exception('Unimplemented method: print_single_line()') + + def print_single_line_prefix(self, longest_essid): + essid = self.essid if self.essid else 'N/A' + Color.p('{W} ') + Color.p('{C}%s{W}' % essid.ljust(longest_essid)) + Color.p(' ') + Color.p('{GR}%s{W}' % self.bssid.ljust(17)) + Color.p(' ') + Color.p('{D}%s{W}' % self.readable_date.ljust(19)) + Color.p(' ') + def save(self): ''' Adds this crack result to the cracked file and saves it. ''' name = CrackResult.cracked_file @@ -52,12 +66,32 @@ def display(cls): if len(cracked_targets) == 0: Color.pl('{!} {R}no results found in {O}%s{W}' % name) - else: - Color.pl('{+} displaying {G}%d {C}cracked target(s){W}\n' % len(cracked_targets)) - for item in cracked_targets: - cr = cls.load(item) - cr.dump() - Color.pl('') + return + + Color.pl('\n{+} Displaying {G}%d{W} cracked target(s) from {C}%s{W}\n' % ( + len(cracked_targets), name)) + + results = sorted([cls.load(item) for item in cracked_targets], key=lambda x: x.date, reverse=True) + longest_essid = max([len(result.essid or 'ESSID') for result in results]) + + # Header + Color.p('{D} ') + Color.p('ESSID'.ljust(longest_essid)) + Color.p(' ') + Color.p('BSSID'.ljust(17)) + Color.p(' ') + Color.p('DATE'.ljust(19)) + Color.p(' ') + Color.p('TYPE'.ljust(5)) + Color.p(' ') + Color.p('KEY') + Color.pl('{D}') + Color.p(' ' + '-' * (longest_essid + 17 + 19 + 5 + 11 + 12)) + Color.pl('{W}') + # Results + for result in results: + result.print_single_line(longest_essid) + Color.pl('') @classmethod @@ -97,6 +131,7 @@ def load(json): json['pmkid_file'], json['key']) result.date = json['date'] + result.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.date)) return result if __name__ == '__main__': diff --git a/wifite/model/wep_result.py b/wifite/model/wep_result.py index 58c06adcf..0f014faf6 100755 --- a/wifite/model/wep_result.py +++ b/wifite/model/wep_result.py @@ -23,7 +23,16 @@ def dump(self): Color.pl('{+} Hex Key: {G}%s{W}' % self.hex_key) if self.ascii_key: Color.pl('{+} Ascii Key: {G}%s{W}' % self.ascii_key) - + + def print_single_line(self, longest_essid): + self.print_single_line_prefix(longest_essid) + Color.p('{G}%s{W}' % 'WEP'.ljust(5)) + Color.p(' ') + Color.p('Hex: {G}%s{W}' % self.hex_key.replace(':', '')) + if self.ascii_key: + Color.p(' (ASCII: {G}%s{W})' % self.ascii_key) + Color.pl('') + def to_dict(self): return { 'type' : self.result_type, diff --git a/wifite/model/wpa_result.py b/wifite/model/wpa_result.py index 93ef231c7..63b68564c 100755 --- a/wifite/model/wpa_result.py +++ b/wifite/model/wpa_result.py @@ -29,7 +29,14 @@ def dump(self): Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key)) else: Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) - + + def print_single_line(self, longest_essid): + self.print_single_line_prefix(longest_essid) + Color.p('{G}%s{W}' % 'WPA'.ljust(5)) + Color.p(' ') + Color.p('Key: {G}%s{W}' % self.key) + Color.pl('') + def to_dict(self): return { 'type' : self.result_type, diff --git a/wifite/model/wps_result.py b/wifite/model/wps_result.py index b4dcbe74c..520f0323b 100755 --- a/wifite/model/wps_result.py +++ b/wifite/model/wps_result.py @@ -27,6 +27,15 @@ def dump(self): Color.pl('{+} %s: {G}%s{W}' % ( 'WPS PIN'.rjust(12), self.pin)) Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), psk)) + def print_single_line(self, longest_essid): + self.print_single_line_prefix(longest_essid) + Color.p('{G}%s{W}' % 'WPS'.ljust(5)) + Color.p(' ') + if self.psk: + Color.p('Key: {G}%s{W} ' % self.psk) + Color.p('PIN: {G}%s{W}' % self.pin) + Color.pl('') + def to_dict(self): return { 'type' : self.result_type, diff --git a/wifite/tools/dependency.py b/wifite/tools/dependency.py index 01451d090..556a10939 100755 --- a/wifite/tools/dependency.py +++ b/wifite/tools/dependency.py @@ -50,7 +50,7 @@ def run_dependency_check(cls): missing_required = any([app.fails_dependency_check() for app in apps]) if missing_required: - Color.pl('{!} {R}required app(s) were not found, exiting.{W}') + Color.pl('{!} {O}At least 1 Required app is missing. Wifite needs Required apps to run{W}') import sys sys.exit(-1) @@ -64,11 +64,11 @@ def fails_dependency_check(cls): return False if cls.dependency_required: - Color.pp('{!} {R}error: required app {O}%s{R} was not found' % cls.dependency_name) + Color.p('{!} {O}Error: Required app {R}%s{O} was not found' % cls.dependency_name) Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url) return True else: - Color.p('{!} {O}warning: recommended app {R}%s{O} was not found' % cls.dependency_name) + Color.p('{!} {O}Warning: Recommended app {R}%s{O} was not found' % cls.dependency_name) Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url) return False diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index c7fb7b975..df8033917 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -157,7 +157,11 @@ def parse_crack_result(self, stdout): # Try to derive PSK from PIN using Bully self.pattack('{W}Retrieving PSK using {C}bully{W}...') - psk = Bully.get_psk_from_pin(self.target, pin) + psk = None + try: + psk = Bully.get_psk_from_pin(self.target, pin) + except KeyboardInterrupt: + pass if psk is None: Color.pl('') self.pattack('{R}Failed {O}to get PSK using bully', newline=True) diff --git a/wifite/util/crack.py b/wifite/util/crack.py index ff854c962..1ebba0bf9 100755 --- a/wifite/util/crack.py +++ b/wifite/util/crack.py @@ -11,8 +11,9 @@ import os -# TODO: Bring back the 'print' option, for easy copy/pasting. -# Just one-liners people can paste into terminal. +# TODO: Bring back the 'print' option, for easy copy/pasting. Just one-liners people can paste into terminal. + +# TODO: Do not show handshake files that are in cracked.txt with a key (match on filename). class CrackHelper: '''Manages handshake retrieval, selection, and running the cracking commands.''' @@ -36,6 +37,9 @@ def run(cls): Color.pl('') 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) @@ -50,6 +54,10 @@ def get_handshakes(cls): skipped_pmkid_files = 0 hs_dir = Configuration.wpa_handshake_dir + if not os.path.exists(hs_dir) or not os.path.isdir(hs_dir): + Color.pl('\n{!} {O}directory not found: {R}%s{W}' % hs_dir) + return [] + Color.pl('\n{+} Listing captured handshakes from {C}%s{W} ...\n' % os.path.abspath(hs_dir)) for hs_file in os.listdir(hs_dir): if hs_file.count('_') != 3: @@ -102,12 +110,12 @@ def get_handshakes(cls): @classmethod def print_handshakes(cls, handshakes): # Header - max_essid_len = max(max([len(hs['essid']) for hs in handshakes]), len('ESSID (truncated)')) + max_essid_len = max([len(hs['essid']) for hs in handshakes] + [len('ESSID (truncated)')]) Color.p('{D} NUM') - Color.p(' ESSID (truncated)'.ljust(max_essid_len)) - Color.p(' BSSID'.ljust(19)) - Color.p(' TYPE'.ljust(7)) - Color.p(' DATE CAPTURED\n') + Color.p(' ' + 'ESSID (truncated)'.ljust(max_essid_len)) + Color.p(' ' + 'BSSID'.ljust(17)) + Color.p(' ' + 'TYPE'.ljust(5)) + Color.p(' ' + 'DATE CAPTURED\n') Color.p(' ---') Color.p(' ' + ('-' * max_essid_len)) Color.p(' ' + ('-' * 17))