From 625642fee74bbc0b18babfd61c12785eee23f201 Mon Sep 17 00:00:00 2001 From: derv82 Date: Mon, 1 Jun 2015 00:30:02 -0700 Subject: [PATCH] WPA handshake capture and cracking almost setup --- py/AttackWEP.py | 12 ++--- py/AttackWPA.py | 124 ++++++++++++++++++++++++++++++++++++++++++-- py/Color.py | 10 ++++ py/Configuration.py | 4 +- py/Handshake.py | 13 +++-- py/Process.py | 3 ++ py/Scanner.py | 10 ++-- py/WPAResult.py | 32 ++++++++++++ 8 files changed, 189 insertions(+), 19 deletions(-) create mode 100644 py/WPAResult.py diff --git a/py/AttackWEP.py b/py/AttackWEP.py index d555b06..32592e2 100644 --- a/py/AttackWEP.py +++ b/py/AttackWEP.py @@ -85,10 +85,8 @@ class AttackWEP(Attack): essid = airodump_target.essid else: essid = None - print '\n' - Color.pl('{+} {C}%s{W} WEP attack {G}successful{W}' + Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' % attack_name) - print '' if aireplay: aireplay.stop() self.crack_result = CrackResultWEP(bssid, \ @@ -138,8 +136,8 @@ class AttackWEP(Attack): # TODO: Check for .xor file. # If .xor is not there, the process failed. Check stdout. # XXX: For debugging - print '\n%s stopped, output:' % attack_name - print aireplay.get_output() + Color.pl('\n%s stopped, output:' % attack_name) + Color.pl(aireplay.get_output()) break # If .xor exists, run packetforge-ng to create .cap @@ -148,8 +146,8 @@ class AttackWEP(Attack): # 2. Start Aireplay to replay the .cap file else: Color.pl('\n{!} {O}aireplay-ng exited unexpectedly{W}') - print '\naireplay.get_output():' - print aireplay.get_output() + Color.pl('\naireplay.get_output():') + Color.pl(aireplay.get_output()) break # Check if IVs stopped flowing (same for > N seconds) diff --git a/py/AttackWPA.py b/py/AttackWPA.py index 528a77f..6a480dc 100644 --- a/py/AttackWPA.py +++ b/py/AttackWPA.py @@ -1,10 +1,19 @@ #!/usr/bin/python from Attack import Attack +from Airodump import Airodump +from Color import Color +from Configuration import Configuration +from Handshake import Handshake +from Process import Process +from WPAResult import WPAResult + +import time class AttackWPA(Attack): def __init__(self, target): super(AttackWPA, self).__init__(target) + self.crack_result = None def run(self): ''' @@ -15,8 +24,117 @@ class AttackWPA(Attack): target_bssid=self.target.bssid, output_file_prefix='wpa') as airodump: + Color.p('\r{+} {O}waiting{W} for target to appear...') airodump_target = self.wait_for_target(airodump) - - for attack_num in xrange(1, 6): - attack_type = WEPAttackType(attack_num) + clients = airodump_target.clients + client_index = 0 + + handshake = None + + time_since_deauth = time.time() + + while True: + Color.p('\r %s' % (' ' * 45)) + 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] + # Check for Handshake + bssid = airodump_target.bssid + essid = None + if airodump_target.essid_known: + essid = airodump_target.essid + handshake = Handshake(cap_file, bssid=bssid, essid=essid) + if handshake.has_handshake(): + # We got a handshake + Color.pl(' {G}captured handshake!{W}') + break + + # TODO: Send deauth to a client or broadcast + if time.time()-time_since_deauth > Configuration.wpa_deauth_timeout: + if len(clients) == 0 or client_index >= len(clients): + # Send deauth for broadcoast + client_index = 0 + else: + # Send deauth for client + client = clients[client_index] + client_index += 1 + time_since_deauth = time.time() + continue + + if not handshake: + # No handshake, attack failed. + raise Exception('Handshake not captured') + return False + + 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 + + # TODO: Crack handshake + wordlist = Configuration.wordlist + if wordlist != None: + 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]) + + # TODO: More-verbose cracking status + # 1. Read number of lines in 'wordlist' + # 2. Pipe aircrack stdout to file + # 3. Read from file every second, get keys tried so far + # 4. Display # of keys tried / total keys, and ETA + + key_file = Configuration.temp('wpakey.txt') + command = [ + 'aircrack-ng', + '-a', '2', + '-w', wordlist, + '-l', key_file, + handshake.capfile + ] + aircrack = Process(command, devnull=True) + aircrack.wait() + if os.path.exists(key_file): + # We cracked it. + Color.pl('{G}cracked{W}') + f = open(key_file, 'r') + key = f.read() + f.close() + else: + Color.pl('{R}failed{W}') + + self.crack_result = WPAResult(bssid, essid, handshake.capfile, key) + self.crack_result.dump() + return True + + +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(',') + target = Target(fields) + wpa = AttackWPA(target) + wpa.run() + diff --git a/py/Color.py b/py/Color.py index 1895b2e..846ca44 100644 --- a/py/Color.py +++ b/py/Color.py @@ -23,6 +23,8 @@ class Color(object): '{!}': ' {W}[{R}!{W}]' } + last_sameline_length = 0 + @staticmethod def p(text): ''' @@ -32,6 +34,7 @@ class Color(object): ''' sys.stdout.write(Color.s(text)) sys.stdout.flush() + Color.last_sameline_length += len(text) @staticmethod def pl(text): @@ -39,6 +42,7 @@ class Color(object): Prints text using colored format with trailing new line. ''' Color.p('%s\n' % text) + Color.last_sameline_length = 0 @staticmethod def s(text): @@ -50,6 +54,12 @@ class Color(object): output = output.replace("{%s}" % key, value) return output + @staticmethod + def clear_line(): + spaces = ' ' * Color.last_sameline_length + sys.stdout.write('\r%s\r' % spaces) + sys.stdout.flush() + 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/Configuration.py b/py/Configuration.py index c74b500..8c06249 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -95,11 +95,11 @@ class Configuration(object): @staticmethod - def temp(): + def temp(subfile=''): ''' Creates and/or returns the temporary directory ''' if Configuration.temp_dir == None: Configuration.temp_dir = Configuration.create_temp() - return Configuration.temp_dir + return Configuration.temp_dir + subfile @staticmethod def create_temp(): diff --git a/py/Handshake.py b/py/Handshake.py index 6b18f27..f6fc9ea 100644 --- a/py/Handshake.py +++ b/py/Handshake.py @@ -59,15 +59,22 @@ class Handshake(object): if not self.bssid or not self.essid: self.divine_essid_and_bssid() - if self.tshark_handshakes(): + if len(self.tshark_handshakes()) > 0: return True - if self.cowpatty_handshakes(): + if len(self.cowpatty_handshakes()) > 0: return True - if self.pyrit_handshakes(): + if len(self.pyrit_handshakes()) > 0: return True + # XXX: Disabling aircrack check since I don't think it's reliable. + ''' + if len(self.aircrack_handshakes()) > 0: + return True + ''' + return False + def tshark_bssid_essid_pairs(self): ''' diff --git a/py/Process.py b/py/Process.py index 2ef8a38..66c9629 100644 --- a/py/Process.py +++ b/py/Process.py @@ -86,6 +86,9 @@ class Process(object): ''' Returns exit code if process is dead, otherwise "None" ''' return self.pid.poll() + def wait(self): + self.pid.wait() + def running_time(self): ''' Returns number of seconds since process was started ''' return int(time.time() - self.start_time) diff --git a/py/Scanner.py b/py/Scanner.py index 7d6ec95..ddd37c1 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -39,7 +39,7 @@ class Scanner(object): [len(t.clients) for t in self.targets]) Color.p( - "\r {+} Scanning, " + + "\r{+} Scanning, " + "found {G}%d{W} target(s)," % target_count + " {G}%d{W} clients" % client_count + ". {O}Ctrl+C{W} when ready") @@ -71,7 +71,7 @@ class Scanner(object): else: # We can fit the targets in the terminal without scrolling # "Move" cursor up so we will print over the previous list - print Scanner.UP_CHAR * (3 + self.previous_target_count) + Color.pl(Scanner.UP_CHAR * (3 + self.previous_target_count)) self.previous_target_count = len(self.targets) @@ -109,6 +109,9 @@ class Scanner(object): chosen_targets = [] for choice in raw_input(Color.s(input_str)).split(','): + if choice == 'all': + chosen_targets = self.targets + break if '-' in choice: # User selected a range (lower,upper) = [int(x) - 1 for x in choice.split('-')] @@ -130,7 +133,6 @@ if __name__ == '__main__': Color.pl('\r {!} {R}Error{W}: %s' % str(e)) Configuration.exit_gracefully(0) for t in targets: - Color.p("{W}Selected: ") - print t + Color.pl(" {W}Selected: %s" % t) Configuration.exit_gracefully(0) diff --git a/py/WPAResult.py b/py/WPAResult.py new file mode 100644 index 0000000..82d164c --- /dev/null +++ b/py/WPAResult.py @@ -0,0 +1,32 @@ +#!/usr/bin/python + +from Color import Color + +class WPAResult(object): + def __init__(self, bssid, essid, handshake_file, key): + self.bssid = bssid + self.essid = essid + self.handshake_file = handshake_file + self.key = key + + def dump(self): + if self.essid: + Color.pl('{+} %s: {C}%s{W}' % + ('Access Point Name'.rjust(19), self.essid)) + if self.bssid: + Color.pl('{+} %s: {C}%s{W}' % + ('Access Point BSSID'.rjust(19), self.bssid)) + if self.handshake_file: + Color.pl('{+} %s: {C}%s{W}' % + ('Handshake File'.rjust(19), self.handshake_file)) + if self.key: + Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key)) + else: + Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) + +if __name__ == '__main__': + w = WPAResult('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', 'abcd1234') + w.dump() + print '\n' + w = WPAResult('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', None) + w.dump()