diff --git a/wifite/__main__.py b/wifite/__main__.py index 4afd71384..fc1cee57f 100755 --- a/wifite/__main__.py +++ b/wifite/__main__.py @@ -45,7 +45,10 @@ def start(self): from .util.crack import CrackHelper if Configuration.show_cracked: - CrackResult.display() + CrackResult.display('cracked') + + elif Configuration.show_ignored: + CrackResult.display('ignored') elif Configuration.check_handshake: Handshake.check() diff --git a/wifite/args.py b/wifite/args.py index 6c07e6201..c2a641263 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -476,6 +476,11 @@ def _add_command_args(commands): dest='cracked', help=Color.s('Print previously-cracked access points')) + commands.add_argument('--ignored', + action='store_true', + dest='ignored', + help=Color.s('Print ignored access points')) + commands.add_argument('-cracked', help=argparse.SUPPRESS, action='store_true', diff --git a/wifite/attack/all.py b/wifite/attack/all.py index 01b619569..1eab298af 100755 --- a/wifite/attack/all.py +++ b/wifite/attack/all.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from enum import Enum from .pmkid import AttackPMKID from .wep import AttackWEP from .wpa import AttackWPA @@ -9,6 +10,11 @@ from ..model.target import WPSState from ..util.color import Color +class Answer(Enum): + Skip = 1 + ExitOrReturn = 2 + Continue = 3 + Ignore = 4 class AttackAll(object): @classmethod @@ -109,10 +115,14 @@ def attack_single(cls, target, targets_remaining): except KeyboardInterrupt: Color.pl('\n{!} {O}Interrupted{W}\n') answer = cls.user_wants_to_continue(targets_remaining, len(attacks)) - if answer is True: + if answer == Answer.Continue: continue # Keep attacking the same target (continue) - elif answer is None: + elif answer == Answer.Skip: return True # Keep attacking other targets (skip) + elif answer == Answer.Ignore: + from ..model.result import CrackResult + CrackResult.ignore_target(target) + return True # Ignore current target and keep attacking other targets (ignore) else: return False # Stop all attacks (exit) @@ -126,9 +136,10 @@ def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0): """ Asks user if attacks should continue onto other targets Returns: - None if the user wants to skip the current target - True if the user wants to continue to the next attack on the current target - False if the user wants to stop the remaining attacks + Answer.Skip if the user wants to skip the current target + Answer.Ignore if the user wants to ignore the current target + Answer.Continue if the user wants to continue to the next attack on the current target + Answer.ExitOrReturn if the user wants to stop the remaining attacks """ if attacks_remaining == 0 and targets_remaining == 0: return # No targets or attacksleft, drop out @@ -152,6 +163,9 @@ def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0): prompt += ' {O}skip{W} to the next target,' options += '{O}s{W}{D}, {W}' + prompt += ' skip and {P}ignore{W} current target,' + options += '{P}i{W}{D}, {W}' + if Configuration.infinite_mode: options += '{R}r{W})' prompt += ' or {R}return{W} to scanning %s? {C}' % options @@ -163,8 +177,10 @@ def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0): answer = input().lower() if answer.startswith('s'): - return None # Skip + return Answer.Skip elif answer.startswith('e') or answer.startswith('r'): - return False # Exit/Return + return Answer.ExitOrReturn # Exit/Return + elif answer.startswith('i'): + return Answer.Ignore # Ignore else: - return True # Continue + return Answer.Continue # Continue diff --git a/wifite/config.py b/wifite/config.py index 6a7705ec4..2aae0c3ef 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -45,6 +45,7 @@ class Configuration(object): scan_time = None show_bssids = None show_cracked = None + show_ignored = None show_manufacturers = None skip_crack = None target_bssid = None @@ -209,6 +210,7 @@ def initialize(cls, load_interface=True): # Commands cls.show_cracked = False + cls.show_ignored = False cls.check_handshake = None cls.crack_handshake = False @@ -257,6 +259,8 @@ def load_from_arguments(cls): # Commands if args.cracked: cls.show_cracked = True + if args.ignored: + cls.show_ignored = True if args.check_handshake: cls.check_handshake = args.check_handshake if args.crack_handshake: @@ -356,10 +360,11 @@ def parse_settings_args(cls, args): Color.pl('{+} {C}option: {O}ignoring ESSID(s): {R}%s{W}' % ', '.join(args.ignore_essids)) + from .model.result import CrackResult + cls.ignore_cracked = CrackResult.load_ignored_bssids(args.ignore_cracked) + if args.ignore_cracked: - from .model.result import CrackResult - if cracked_targets := CrackResult.load_all(): - cls.ignore_cracked = [item['bssid'] for item in cracked_targets] + if cls.ignore_cracked: Color.pl('{+} {C}option: {O}ignoring {R}%s{O} previously-cracked targets' % len(cls.ignore_cracked)) else: diff --git a/wifite/model/ignored_result.py b/wifite/model/ignored_result.py new file mode 100755 index 000000000..926d1ab85 --- /dev/null +++ b/wifite/model/ignored_result.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from ..util.color import Color +from ..model.result import CrackResult +from contextlib import contextmanager, redirect_stderr, redirect_stdout +from os import devnull + + +@contextmanager +def suppress_stdout_stderr(): + """A context manager that redirects stdout and stderr to devnull""" + with open(devnull, 'w') as fnull: + with redirect_stderr(fnull) as err, redirect_stdout(fnull) as out: + yield err, out + + +class CrackResultIgnored(CrackResult): + def __init__(self, bssid, essid): + self.result_type = 'IGN' + self.bssid = bssid + self.essid = essid + super(CrackResultIgnored, self).__init__() + + def dump(self): + if self.essid is not None: + Color.pl(f'{{+}} {"ESSID".rjust(12)}: {{C}}{self.essid}{{W}}') + Color.pl('{+} %s: {C}%s{W}' % ('BSSID'.rjust(12), self.bssid)) + + def print_single_line(self, longest_essid): + self.print_single_line_prefix(longest_essid) + Color.p('{G}%s{W}' % 'IGN'.ljust(9)) + Color.pl('') + + def to_dict(self): + with suppress_stdout_stderr(): + print('@@@ to dict', self.__dict__) + return { + 'type': self.result_type, + 'date': self.date, + 'essid': self.essid, + 'bssid': self.bssid, + } + + +if __name__ == '__main__': + crw = CrackResultIgnored('AA:BB:CC:DD:EE:FF', 'Test Router') + crw.dump() + crw.save() diff --git a/wifite/model/result.py b/wifite/model/result.py index 57b1a8994..ceb7a434b 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -64,28 +64,33 @@ def save(self): saved_results.append(self.to_dict()) with open(name, 'w') as fid: fid.write(dumps(saved_results, indent=2)) - Color.pl('{+} saved crack result to {C}%s{W} ({G}%d total{W})' + Color.pl('{+} saved result to {C}%s{W} ({G}%d total{W})' % (name, len(saved_results))) @classmethod - def display(cls): - """ Show cracked targets from cracked file """ + def display(cls, result_type): + """ Show targets from results file """ name = cls.cracked_file if not os.path.exists(name): Color.pl('{!} {O}file {C}%s{O} not found{W}' % name) return - with open(name, 'r') as fid: - cracked_targets = loads(fid.read()) + targets = cls.load_all() + only_cracked = result_type == 'cracked' - if len(cracked_targets) == 0: + if only_cracked: + targets = [item for item in targets if item.get('type') != 'IGN'] + else: + targets = [item for item in targets if item.get('type') == 'IGN'] + + if len(targets) == 0: Color.pl('{!} {R}no results found in {O}%s{W}' % name) return - Color.pl('\n{+} Displaying {G}%d{W} cracked target(s) from {C}%s{W}\n' % ( - len(cracked_targets), name)) + Color.pl('\n{+} Displaying {G}%d{W} %s target(s) from {C}%s{W}\n' % ( + len(targets), result_type, cls.cracked_file)) - results = sorted([cls.load(item) for item in cracked_targets], key=lambda x: x.date, reverse=True) + results = sorted([cls.load(item) for item in targets], key=lambda x: x.date, reverse=True) longest_essid = max(len(result.essid or 'ESSID') for result in results) # Header @@ -98,9 +103,10 @@ def display(cls): Color.p(' ') Color.p('TYPE'.ljust(5)) Color.p(' ') - Color.p('KEY') - Color.pl('{D}') - Color.p(' ' + '-' * (longest_essid + 17 + 19 + 5 + 11 + 12)) + if only_cracked: + Color.p('KEY') + Color.pl('{D}') + Color.p(' ' + '-' * (longest_essid + 17 + 19 + 5 + 11 + 12)) Color.pl('{W}') # Results for result in results: @@ -118,6 +124,24 @@ def load_all(cls): return [] return json + @classmethod + def load_ignored_bssids(cls, ignore_cracked = False): + json = cls.load_all() + ignored_bssids = [ + item.get('bssid', '') + for item in json + if item.get('result_type') == 'IGN' + ] + + if not ignore_cracked: + return ignored_bssids + + return ignored_bssids + [ + item.get('bssid', '') + for item in json + if item.get('result_type') != 'IGN' + ] + @staticmethod def load(json): """ Returns an instance of the appropriate object given a json instance """ @@ -149,10 +173,30 @@ def load(json): essid=json['essid'], pmkid_file=json['pmkid_file'], key=json['key']) + + else: + from .ignored_result import CrackResultIgnored + result = CrackResultIgnored(bssid=json['bssid'], + essid=json['essid']) + result.date = json['date'] result.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.date)) return result + @classmethod + def ignore_target(cls, target): + ignored_targets = cls.load_all() + + for ignored_target in ignored_targets: + is_ignored = ignored_target == 'IGN' + bssid_match = target.bssid == ignored_target.get('bssid') + essid_match = target.essid == ignored_target.get('essid') + if is_ignored and bssid_match and essid_match: + return + + from .ignored_result import CrackResultIgnored + ignored_target = CrackResultIgnored(target.bssid, target.essid) + ignored_target.save() if __name__ == '__main__': # Deserialize WPA object @@ -178,3 +222,11 @@ def load(json): '"date": 1433403278, "type": "WPS"}') obj = CrackResult.load(json) obj.dump() + + # Deserialize Ignored object + Color.pl('\nIgnored:') + json = loads( + '{"bssid": "AA:BB:CC:DD:EE:FF", "essid": "Test Router", ' + '"date": 1433403278, "type": "IGN"}') + obj = CrackResult.load(json) + obj.dump()