2.1.3: Better WPS attack messaging. Leave device in Monitor Mode.
Unrelated to WPS: * Do not take device out of monitor mode when finished (informs user) * Do not restart NetworkManager when finished (informs user) Changes to CLI switches: * --wps-time X: Total time for WPS attack to complete * --wps-timeouts X: Max number of timeouts before failing * --wps-fails X: Max number of WPSFails before failing * Removed unused WPS switches. * Improved --help messaging for WPS switches. * Fail/Timeout threshold default is 100 Bully now outputs useful information: * Current PIN + status * Time remaining * Number of Timeout messages * Number of "WPSFail" messages * If AP is locked Better reaver output. * Looks more like Bully's output. * Timer shows time remaining for attack. * Mentions "Running pixiewps" during "M2 message" step. * pixiewps failure looks like this: "Reaver says: 'WPS pin not found'" * Counts Timeouts and "WPS Transaction Failure" (WPSFail) For #28
This commit is contained in:
@@ -297,56 +297,55 @@ class Arguments(object):
|
||||
dest='wps_filter',
|
||||
help=Color.s('Filter to display only WPS-enabled networks'))
|
||||
wps.add_argument('-wps', help=argparse.SUPPRESS, action='store_true', dest='wps_filter')
|
||||
|
||||
wps.add_argument('--bully',
|
||||
action='store_true',
|
||||
dest='use_bully',
|
||||
help=Color.s('Use {C}bully{W} instead of {C}reaver{W} for WPS attacks (default: {G}reaver{W})'))
|
||||
# Alias
|
||||
wps.add_argument('-bully', help=argparse.SUPPRESS, action='store_true', dest='use_bully')
|
||||
|
||||
wps.add_argument('--no-wps',
|
||||
action='store_true',
|
||||
dest='no_wps',
|
||||
help=Color.s('{O}NEVER{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})'))
|
||||
|
||||
wps.add_argument('--wps-only',
|
||||
action='store_true',
|
||||
dest='wps_only',
|
||||
help=Color.s('{G}ALWAYS{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})'))
|
||||
# Alias
|
||||
wps.add_argument('--pixie', help=argparse.SUPPRESS, action='store_true', dest='wps_only')
|
||||
|
||||
# Same as --wps-only
|
||||
wps.add_argument('--pixie',
|
||||
help=argparse.SUPPRESS,
|
||||
action='store_true',
|
||||
dest='wps_only')
|
||||
|
||||
wps.add_argument('--pixiet',
|
||||
# Time limit on entire attack.
|
||||
wps.add_argument('--wps-time',
|
||||
action='store',
|
||||
dest='wps_pixie_timeout',
|
||||
metavar='[seconds]',
|
||||
metavar='[sec]',
|
||||
type=int,
|
||||
help=self._verbose('Time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout))
|
||||
wps.add_argument('--pixiest',
|
||||
action='store',
|
||||
dest='wps_pixie_step_timeout',
|
||||
metavar='[seconds]',
|
||||
type=int,
|
||||
help=self._verbose('Time to wait for a step to progress before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_step_timeout))
|
||||
wps.add_argument('--wpsmf',
|
||||
help=self._verbose('Total time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout))
|
||||
# Alias
|
||||
wps.add_argument('-wpst', help=argparse.SUPPRESS, action='store', dest='wps_pixie_timeout', type=int)
|
||||
|
||||
# Maximum number of "failures" (WPSFail)
|
||||
wps.add_argument('--wps-fails',
|
||||
action='store',
|
||||
dest='wps_fail_threshold',
|
||||
metavar='[fails]',
|
||||
metavar='[num]',
|
||||
type=int,
|
||||
help=self._verbose('Maximum number of WPS Failures before failing attack (default: {G}%d{W})' % self.config.wps_fail_threshold))
|
||||
wps.add_argument('-wpsmf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int)
|
||||
wps.add_argument('--wpsmt',
|
||||
help=self._verbose('Maximum number of WPSFail/NoAssoc errors before failing (default: {G}%d{W})' % self.config.wps_fail_threshold))
|
||||
# Alias
|
||||
wps.add_argument('-wpsf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int)
|
||||
|
||||
# Maximum number of "timeouts"
|
||||
wps.add_argument('--wps-timeouts',
|
||||
action='store',
|
||||
dest='wps_timeout_threshold',
|
||||
metavar='[timeouts]',
|
||||
metavar='[num]',
|
||||
type=int,
|
||||
help=self._verbose('Maximum number of Timeouts before stopping (default: {G}%d{W})' % self.config.wps_timeout_threshold))
|
||||
wps.add_argument('-wpsmt', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int)
|
||||
wps.add_argument('--ignore-ratelimit',
|
||||
action='store_false',
|
||||
dest='wps_skip_rate_limit',
|
||||
help=Color.s('Ignores attack if WPS is rate-limited (default: {G}on{W})'))
|
||||
wps.add_argument('-ignore-ratelimit', help=argparse.SUPPRESS, action='store_false', dest='wps_skip_rate_limit')
|
||||
help=self._verbose('Maximum number of Timeouts before failing (default: {G}%d{W})' % self.config.wps_timeout_threshold))
|
||||
# Alias
|
||||
wps.add_argument('-wpsto', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int)
|
||||
|
||||
|
||||
def _add_command_args(self, commands):
|
||||
|
||||
@@ -29,19 +29,18 @@ class AttackWPS(Attack):
|
||||
bully = Bully(self.target)
|
||||
bully.run()
|
||||
bully.stop()
|
||||
if bully.crack_result is not None:
|
||||
self.crack_result = bully.crack_result
|
||||
self.success = True
|
||||
return True
|
||||
self.crack_result = bully.crack_result
|
||||
self.success = self.crack_result is not None
|
||||
return self.success
|
||||
else:
|
||||
reaver = Reaver(self.target)
|
||||
if reaver.is_pixiedust_supported():
|
||||
# Reaver: Pixie-dust
|
||||
reaver = Reaver(self.target)
|
||||
if reaver.run_pixiedust_attack():
|
||||
self.crack_result = reaver.crack_result
|
||||
self.success = True
|
||||
return True
|
||||
reaver.run()
|
||||
self.crack_result = reaver.crack_result
|
||||
self.success = self.crack_result is not None
|
||||
return self.success
|
||||
else:
|
||||
Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}")
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Configuration(object):
|
||||
|
||||
initialized = False # Flag indicating config has been initialized
|
||||
temp_dir = None # Temporary directory
|
||||
version = '2.1.2'
|
||||
version = '2.1.3'
|
||||
|
||||
@staticmethod
|
||||
def initialize(load_interface=True):
|
||||
@@ -88,10 +88,8 @@ class Configuration(object):
|
||||
Configuration.wps_only = False # ONLY use WPS attacks on non-WEP networks
|
||||
Configuration.use_bully = False # Use bully instead of reaver
|
||||
Configuration.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails
|
||||
Configuration.wps_pixie_step_timeout = 30 # Seconds to wait for a step to change before pixie fails
|
||||
Configuration.wps_fail_threshold = 30 # Max number of failures
|
||||
Configuration.wps_timeout_threshold = 30 # Max number of timeouts
|
||||
Configuration.wps_skip_rate_limit = True # Skip rate-limited WPS APs
|
||||
Configuration.wps_fail_threshold = 100 # Max number of failures
|
||||
Configuration.wps_timeout_threshold = 100 # Max number of timeouts
|
||||
|
||||
# Commands
|
||||
Configuration.show_cracked = False
|
||||
@@ -214,31 +212,25 @@ class Configuration(object):
|
||||
|
||||
# WPS
|
||||
if args.wps_filter:
|
||||
Configuration.wps_filter = args.wps_filter
|
||||
Configuration.wps_filter = args.wps_filter
|
||||
if args.wps_only:
|
||||
Configuration.wps_only = True
|
||||
Color.pl('{+} {C}option:{W} will *only* attack non-WEP networks with {G}WPS attacks{W} (no handshake capture)')
|
||||
if args.no_wps:
|
||||
Configuration.no_wps = args.no_wps
|
||||
Configuration.no_wps = args.no_wps
|
||||
Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} (Pixie-Dust/PIN) on targets')
|
||||
if args.use_bully:
|
||||
Configuration.use_bully = args.use_bully
|
||||
Configuration.use_bully = args.use_bully
|
||||
Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} for WPS Attacks')
|
||||
if args.wps_pixie_timeout:
|
||||
Configuration.wps_pixie_timeout = args.wps_pixie_timeout
|
||||
Color.pl('{+} {C}option:{W} WPS pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_timeout)
|
||||
if args.wps_pixie_step_timeout:
|
||||
Configuration.wps_pixie_step_timeout = args.wps_pixie_step_timeout
|
||||
Color.pl('{+} {C}option:{W} Any step in the pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_step_timeout)
|
||||
Color.pl('{+} {C}option:{W} WPS pixie-dust attack will fail after {O}%d seconds{W}' % args.wps_pixie_timeout)
|
||||
if args.wps_fail_threshold:
|
||||
Configuration.wps_fail_threshold = args.wps_fail_threshold
|
||||
Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d failures{W}' % args.wps_fail_threshold)
|
||||
Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d failures{W}' % args.wps_fail_threshold)
|
||||
if args.wps_timeout_threshold:
|
||||
Configuration.wps_timeout_threshold = args.wps_timeout_threshold
|
||||
Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d timeouts{W}' % args.wps_timeout_threshold)
|
||||
if args.wps_skip_rate_limit == False:
|
||||
Configuration.wps_skip_rate_limit = False
|
||||
Color.pl('{+} {C}option:{W} will {G}continue{W} WPS attacks when rate-limited')
|
||||
Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d timeouts{W}' % args.wps_timeout_threshold)
|
||||
|
||||
# Adjust encryption filter
|
||||
Configuration.encryption_filter = []
|
||||
@@ -321,11 +313,14 @@ class Configuration(object):
|
||||
Macchanger.reset_if_changed()
|
||||
from .tools.airmon import Airmon
|
||||
if hasattr(Configuration, "interface") and Configuration.interface is not None and Airmon.base_interface is not None:
|
||||
Airmon.stop(Configuration.interface)
|
||||
Airmon.put_interface_up(Airmon.base_interface)
|
||||
Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface)
|
||||
Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface)
|
||||
#Airmon.stop(Configuration.interface)
|
||||
#Airmon.put_interface_up(Airmon.base_interface)
|
||||
|
||||
if Airmon.killed_network_manager:
|
||||
Airmon.start_network_manager()
|
||||
Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})')
|
||||
#Airmon.start_network_manager()
|
||||
|
||||
exit(code)
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ from threading import Thread
|
||||
class Bully(Attack):
|
||||
def __init__(self, target):
|
||||
super(Bully, self).__init__(target)
|
||||
self.consecutive_lockouts = self.consecutive_timeouts = self.consecutive_noassoc = 0
|
||||
self.pins_attempted = 0
|
||||
self.total_timeouts = 0
|
||||
self.total_failures = 0
|
||||
self.locked = False
|
||||
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
|
||||
@@ -46,8 +46,6 @@ class Bully(Attack):
|
||||
|
||||
self.bully_proc = None
|
||||
|
||||
def attack_type(self):
|
||||
return "Pixie-Dust"
|
||||
|
||||
def run(self):
|
||||
with Airodump(channel=self.target.channel,
|
||||
@@ -55,11 +53,7 @@ class Bully(Attack):
|
||||
skip_wps=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.pattack("Waiting for target to appear...")
|
||||
self.target = self.wait_for_target(airodump)
|
||||
|
||||
# Start bully
|
||||
@@ -67,27 +61,42 @@ class Bully(Attack):
|
||||
stderr=Process.devnull(),
|
||||
bufsize=0,
|
||||
cwd=Configuration.temp())
|
||||
|
||||
# Start bully status thread
|
||||
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.pattack('{R}Failed: {O}%s{W}' % e, newline=True)
|
||||
self.stop()
|
||||
break
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
self.target,
|
||||
self.attack_type(),
|
||||
self.get_status())
|
||||
|
||||
# Update status
|
||||
self.pattack(self.get_status())
|
||||
|
||||
# Check if entire attack timed out.
|
||||
if self.running_time() > Configuration.wps_pixie_timeout:
|
||||
self.pattack('{R}Failed: {O}Timeout after %d seconds{W}' % Configuration.wps_pixie_timeout, newline=True)
|
||||
self.stop()
|
||||
return
|
||||
|
||||
# Check if timeout threshold was breached
|
||||
if self.total_timeouts >= Configuration.wps_timeout_threshold:
|
||||
self.pattack('{R}Failed: {O}More than %d timeouts{W}' % Configuration.wps_timeout_threshold, newline=True)
|
||||
self.stop()
|
||||
return
|
||||
|
||||
# Check if WPSFail threshold was breached
|
||||
if self.total_failures >= Configuration.wps_fail_threshold:
|
||||
self.pattack('{R}Failed: {O}More than %d WPSFails{W}' % Configuration.wps_fail_threshold, newline=True)
|
||||
self.stop()
|
||||
return
|
||||
|
||||
time.sleep(0.5)
|
||||
except KeyboardInterrupt as e:
|
||||
self.stop()
|
||||
@@ -97,111 +106,97 @@ class Bully(Attack):
|
||||
raise e
|
||||
|
||||
if self.crack_result is None:
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
self.target,
|
||||
self.attack_type(),
|
||||
"{R}Failed{W}\n")
|
||||
self.pattack("{R}Failed{W}", newline=True)
|
||||
|
||||
|
||||
def pattack(self, message, newline=False):
|
||||
# Print message with attack information.
|
||||
time_left = Configuration.wps_pixie_timeout - self.running_time()
|
||||
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
self.target,
|
||||
'Pixie-Dust',
|
||||
'{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message))
|
||||
if newline:
|
||||
Color.pl("")
|
||||
|
||||
|
||||
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
|
||||
main_status = self.state
|
||||
|
||||
meta_statuses = []
|
||||
if self.total_timeouts > 0:
|
||||
meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts)
|
||||
|
||||
if self.total_failures > 0:
|
||||
meta_statuses.append("{O}WPSFail:%d{W}" % self.total_failures)
|
||||
|
||||
if self.locked:
|
||||
meta_statuses.append("{R}Locked{W}")
|
||||
|
||||
if len(meta_statuses) > 0:
|
||||
main_status += ' (%s)' % ', '.join(meta_statuses)
|
||||
|
||||
return main_status
|
||||
|
||||
|
||||
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.search(r".*Got beacon for '(.*)' \((.*)\)", line)
|
||||
if got_beacon:
|
||||
# group(1)=ESSID, group(2)=BSSID
|
||||
self.state = "Got beacon"
|
||||
if Configuration.verbose > 1:
|
||||
Color.pe('\n{P} [bully:stdout] %s' % line)
|
||||
|
||||
# [+] Last State = 'NoAssoc' Next pin '48855501'
|
||||
last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", 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
|
||||
self.state = self.parse_state(line)
|
||||
|
||||
# [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505'
|
||||
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
|
||||
rx_m = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", 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)
|
||||
self.crack_result = self.parse_crack_result(line)
|
||||
|
||||
# [!] WPS lockout reported, sleeping for 43 seconds ...
|
||||
lock_out = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", 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.search(r".*\[Pixie-Dust\] WPS pin not found", line)
|
||||
if pixie_re:
|
||||
self.state = "{R}Failed{W}"
|
||||
if self.crack_result:
|
||||
break
|
||||
|
||||
|
||||
# [+] Running pixiewps with the information, wait ...
|
||||
pixie_re = re.search(r".*Running pixiewps with the information", line)
|
||||
if pixie_re:
|
||||
self.state = "{G}Running pixiewps...{W}"
|
||||
|
||||
def parse_crack_result(self, line):
|
||||
# Check for line containing PIN and PSK
|
||||
# [*] Pin is '80246213', key is 'password'
|
||||
# [*] Pin is '11867722', key is '9a6f7997'
|
||||
pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", 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.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line)
|
||||
if pin_re:
|
||||
self.cracked_pin = pin_re.group(1)
|
||||
###############
|
||||
# Check for PIN
|
||||
if self.cracked_pin is None:
|
||||
# PIN : '80246213'
|
||||
pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line)
|
||||
if pin_re:
|
||||
self.cracked_pin = pin_re.group(1)
|
||||
|
||||
# [Pixie-Dust] PIN FOUND: 01030365
|
||||
pin_re = re.search(r"^\[Pixie-Dust\] PIN FOUND: '?(\d*)'?\s*$", line)
|
||||
if pin_re:
|
||||
self.cracked_pin = pin_re.group(1)
|
||||
|
||||
if self.cracked_pin is not None:
|
||||
# Mention the PIN & that we're not done yet.
|
||||
self.pattack("{G}Cracked PIN: {C}%s{W}" % self.cracked_pin, newline=True)
|
||||
|
||||
self.state = "{G}Finding PSK...{C}"
|
||||
time.sleep(2)
|
||||
|
||||
###########################
|
||||
# KEY : 'password'
|
||||
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
|
||||
if key_re:
|
||||
self.cracked_key = key_re.group(1)
|
||||
|
||||
#warn_re = re.search(r"\[\!\]\s*(.*)$", 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}")
|
||||
Color.pl("")
|
||||
self.pattack("{G}Cracked PSK: {C}%s{W}" % self.cracked_key, newline=True)
|
||||
self.crack_result = CrackResultWPS(
|
||||
self.target.bssid,
|
||||
self.target.essid,
|
||||
@@ -209,19 +204,81 @@ class Bully(Attack):
|
||||
self.cracked_key)
|
||||
Color.pl("")
|
||||
self.crack_result.dump()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return self.crack_result
|
||||
|
||||
|
||||
def parse_state(self, line):
|
||||
state = self.state
|
||||
|
||||
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
|
||||
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
|
||||
if got_beacon:
|
||||
# group(1)=ESSID, group(2)=BSSID
|
||||
state = "Got beacon"
|
||||
|
||||
# [+] Last State = 'NoAssoc' Next pin '48855501'
|
||||
last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line)
|
||||
if last_state:
|
||||
# group(1)=result, group(2)=PIN
|
||||
pin = last_state.group(2)
|
||||
state = "Trying PIN {C}%s{W} (%s)" % (pin, last_state.group(1))
|
||||
|
||||
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
|
||||
mx_result_pin = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
|
||||
if mx_result_pin:
|
||||
self.locked = False
|
||||
# group(1)=M3/M5, group(2)=result, group(3)=PIN
|
||||
m_state = mx_result_pin.group(1)
|
||||
result = mx_result_pin.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad
|
||||
pin = mx_result_pin.group(3)
|
||||
|
||||
if result == "Timeout":
|
||||
self.total_timeouts += 1
|
||||
result = "{O}%s{W}" % result
|
||||
elif result == "WPSFail":
|
||||
self.total_failures += 1
|
||||
result = "{O}%s{W}" % result
|
||||
elif result == "NoAssoc":
|
||||
result = "{O}%s{W}" % result
|
||||
else:
|
||||
result = "{R}%s{W}" % result
|
||||
|
||||
result = "{P}%s{W}:%s" % (m_state.strip(), result.strip())
|
||||
state = "Trying PIN {C}%s{W} (%s)" % (pin, result)
|
||||
|
||||
# [!] WPS lockout reported, sleeping for 43 seconds ...
|
||||
re_lockout = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line)
|
||||
if re_lockout:
|
||||
self.locked = True
|
||||
sleeping = re_lockout.group(1)
|
||||
state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
|
||||
|
||||
# [Pixie-Dust] WPS pin not found
|
||||
re_pin_not_found = re.search(r".*\[Pixie-Dust\] WPS pin not found", line)
|
||||
if re_pin_not_found:
|
||||
state = "{R}Failed: {O}Bully says 'WPS pin not found'{W}"
|
||||
|
||||
# [+] Running pixiewps with the information, wait ...
|
||||
re_running_pixiewps = re.search(r".*Running pixiewps with the information", line)
|
||||
if re_running_pixiewps:
|
||||
state = "{G}Running pixiewps...{W}"
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def stop(self):
|
||||
if hasattr(self, "pid") and self.pid and self.pid.poll() is None:
|
||||
self.pid.interrupt()
|
||||
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_psk_from_pin(target, pin):
|
||||
# Fetches PSK from a Target assuming "pin" is the correct PIN
|
||||
'''
|
||||
bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon
|
||||
PIN : '01030365'
|
||||
@@ -229,8 +286,6 @@ class Bully(Attack):
|
||||
BSSID : '34:21:09:01:92:7c'
|
||||
ESSID : 'AirLink89300'
|
||||
'''
|
||||
Color.pl('\n{+} found PIN: {G}%s{W}' % pin)
|
||||
Color.p('{+} fetching {C}PSK{W} using {C}bully{W}... ')
|
||||
cmd = [
|
||||
'bully',
|
||||
'--channel', target.channel,
|
||||
@@ -247,12 +302,11 @@ class Bully(Attack):
|
||||
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
|
||||
if key_re is not None:
|
||||
psk = key_re.group(1)
|
||||
Color.pl('{W}found PSK: {G}%s{W}' % psk)
|
||||
return psk
|
||||
|
||||
Color.pl('{R}failed{W}')
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Configuration.initialize()
|
||||
Configuration.interface = 'wlan0mon'
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 ..tools.airodump import Airodump
|
||||
from ..tools.bully import Bully # for PSK retrieval
|
||||
from ..model.wps_result import CrackResultWPS
|
||||
@@ -14,155 +15,228 @@ import os, time, re
|
||||
class Reaver(Attack):
|
||||
def __init__(self, target):
|
||||
super(Reaver, self).__init__(target)
|
||||
self.success = False
|
||||
|
||||
self.start_time = None
|
||||
self.state = 'Initializing'
|
||||
self.locked = False
|
||||
self.total_timeouts = 0
|
||||
self.total_wpsfails = 0
|
||||
|
||||
self.crack_result = None
|
||||
|
||||
self.output_filename = Configuration.temp('reaver.out')
|
||||
if os.path.exists(self.output_filename):
|
||||
os.remove(self.output_filename)
|
||||
|
||||
self.output_write = open(self.output_filename, 'a')
|
||||
|
||||
self.reaver_cmd = [
|
||||
'reaver',
|
||||
'--interface', Configuration.interface,
|
||||
'--bssid', self.target.bssid,
|
||||
'--channel', self.target.channel,
|
||||
'--pixie-dust', '1', # pixie-dust attack
|
||||
'--session', '/dev/null', # Don't restart session
|
||||
'-vv' # (very) verbose
|
||||
]
|
||||
|
||||
self.reaver_proc = 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)
|
||||
def run(self):
|
||||
''' Returns True if attack is successful. '''
|
||||
try:
|
||||
self._run() # Run-loop
|
||||
except Exception as e:
|
||||
# Failed with error
|
||||
self.pattack('{R}Failed:{O} %s' % str(e), newline=True)
|
||||
return self.crack_result is not None
|
||||
|
||||
command = [
|
||||
'reaver',
|
||||
'--interface', Configuration.interface,
|
||||
'--bssid', self.target.bssid,
|
||||
'--channel', self.target.channel,
|
||||
'--pixie-dust', '1', # pixie-dust attack
|
||||
'--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())
|
||||
# Stop reaver if it's still running
|
||||
if self.reaver_proc.poll() is None:
|
||||
self.reaver_proc.interrupt()
|
||||
|
||||
pin = None
|
||||
step = 'initializing'
|
||||
time_since_last_step = 0
|
||||
# Clean up open file handle
|
||||
if self.output_write:
|
||||
self.output_write.close()
|
||||
|
||||
return self.crack_result is not None
|
||||
|
||||
|
||||
def _run(self):
|
||||
self.start_time = time.time()
|
||||
|
||||
with Airodump(channel=self.target.channel,
|
||||
target_bssid=self.target.bssid,
|
||||
skip_wps=True,
|
||||
output_file_prefix='pixie') as airodump:
|
||||
|
||||
Color.clear_line()
|
||||
Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...")
|
||||
# Wait for target
|
||||
self.pattack("Waiting for target to appear...")
|
||||
self.target = self.wait_for_target(airodump)
|
||||
|
||||
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)
|
||||
# Start reaver
|
||||
self.reaver_proc = Process(self.reaver_cmd,
|
||||
stdout=self.output_write,
|
||||
stderr=Process.devnull())
|
||||
|
||||
# Loop while reaver is running
|
||||
while self.crack_result is None and self.reaver_proc.poll() is None:
|
||||
|
||||
# Refresh target information (power)
|
||||
self.target = self.wait_for_target(airodump)
|
||||
|
||||
# Update based on reaver output
|
||||
stdout = self.get_output()
|
||||
self.state = self.parse_state(stdout)
|
||||
self.parse_failure(stdout)
|
||||
|
||||
# Print status line
|
||||
self.pattack(self.get_status())
|
||||
|
||||
# Check if we cracked it
|
||||
self.crack_result = self.parse_crack_result(stdout)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if crack result is in output
|
||||
stdout = self.get_output()
|
||||
self.crack_result = self.parse_crack_result(stdout)
|
||||
|
||||
# Show any failures found
|
||||
if self.crack_result is None:
|
||||
self.parse_failure(stdout)
|
||||
|
||||
if self.crack_result is None and self.reaver_proc.poll() is not None:
|
||||
raise Exception('Reaver process stopped (exit code: %s)' % self.reaver_proc.poll())
|
||||
|
||||
|
||||
def get_status(self):
|
||||
main_status = self.state
|
||||
|
||||
meta_statuses = []
|
||||
if self.total_timeouts > 0:
|
||||
meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts)
|
||||
|
||||
if self.total_wpsfails > 0:
|
||||
meta_statuses.append("{O}WPSFail:%d{W}" % self.total_wpsfails)
|
||||
|
||||
if self.locked:
|
||||
meta_statuses.append("{R}Locked{W}")
|
||||
|
||||
if len(meta_statuses) > 0:
|
||||
main_status += ' (%s)' % ', '.join(meta_statuses)
|
||||
|
||||
return main_status
|
||||
|
||||
|
||||
def parse_crack_result(self, stdout):
|
||||
if self.crack_result is not None:
|
||||
return self.crack_result
|
||||
|
||||
(pin, psk, ssid) = self.get_pin_psk_ssid(stdout)
|
||||
|
||||
# Check if we cracked it, or if process stopped.
|
||||
if pin is not None:
|
||||
# We cracked it.
|
||||
|
||||
if psk is not None:
|
||||
# Reaver provided PSK
|
||||
self.pattack('{G}Cracked WPS PIN: {C}%s{W} {G}PSK: {C}%s{W}' % (pin, psk), newline=True)
|
||||
else:
|
||||
self.pattack('{G}Cracked WPS PIN: {C}%s' % pin, newline=True)
|
||||
|
||||
# Try to derive PSK from PIN using Bully
|
||||
self.pattack('{W}Retrieving PSK using {C}bully{W}...')
|
||||
psk = Bully.get_psk_from_pin(self.target, pin)
|
||||
if psk is None:
|
||||
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 is not None or reaver.poll() is not 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 is not None:
|
||||
# We cracked it.
|
||||
|
||||
if psk is None:
|
||||
# Try to derive PSK from PIN using Bully
|
||||
psk = Bully.get_psk_from_pin(self.target, pin)
|
||||
|
||||
bssid = self.target.bssid
|
||||
Color.clear_entire_line()
|
||||
if psk is None:
|
||||
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN{W} (but not PSK)")
|
||||
else:
|
||||
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}")
|
||||
Color.pl("")
|
||||
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
|
||||
self.pattack('{R}Failed {O}to get PSK using bully', newline=True)
|
||||
else:
|
||||
time_since_last_step += 1
|
||||
self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True)
|
||||
|
||||
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
|
||||
crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
|
||||
crack_result.dump()
|
||||
return crack_result
|
||||
|
||||
# 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
|
||||
return None
|
||||
|
||||
# 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)
|
||||
def parse_failure(self, stdout):
|
||||
# Total failure
|
||||
if 'WPS pin not found' in stdout:
|
||||
raise Exception('Reaver says "WPS pin not found"')
|
||||
|
||||
time.sleep(1)
|
||||
continue
|
||||
# Running-time failure
|
||||
if self.running_time() > Configuration.wps_pixie_timeout:
|
||||
raise Exception('Timeout after %d seconds' % Configuration.wps_pixie_timeout)
|
||||
|
||||
# WPSFail count
|
||||
self.total_wpsfails = stdout.count('WPS transaction failed')
|
||||
if self.total_wpsfails >= Configuration.wps_fail_threshold:
|
||||
raise Exception('Too many failures (%d)' % self.total_wpsfails)
|
||||
|
||||
# Timeout count
|
||||
self.total_timeouts = stdout.count('Receive timeout occurred')
|
||||
if self.total_timeouts >= Configuration.wps_timeout_threshold:
|
||||
raise Exception('Too many timeouts (%d)' % self.total_timeouts)
|
||||
|
||||
|
||||
def parse_state(self, stdout):
|
||||
state = self.state
|
||||
|
||||
stdout_last_line = stdout.split('\n')[-1]
|
||||
|
||||
if 'Waiting for beacon from' in stdout_last_line:
|
||||
state = 'Waiting for beacon'
|
||||
|
||||
elif 'Associated with' in stdout_last_line:
|
||||
state = 'Associated'
|
||||
|
||||
elif 'Starting Cracking Session.' in stdout_last_line:
|
||||
state = 'Waiting to try PIN'
|
||||
|
||||
elif 'Trying pin' in stdout_last_line:
|
||||
state = 'Trying PIN'
|
||||
|
||||
elif 'Sending EAPOL START request' in stdout_last_line:
|
||||
state = 'Sending EAPOL Start request'
|
||||
|
||||
elif 'Sending identity response' in stdout_last_line:
|
||||
state = 'Sending identity response'
|
||||
self.locked = False
|
||||
|
||||
elif 'Sending M2 message' in stdout_last_line:
|
||||
state = 'Sending M2 / Running pixiewps'
|
||||
self.locked = False
|
||||
|
||||
elif 'Detected AP rate limiting,' in stdout_last_line:
|
||||
state = 'Rate-Limited by AP'
|
||||
self.locked = True
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def pattack(self, message, newline=False):
|
||||
# Print message with attack information.
|
||||
time_left = Configuration.wps_pixie_timeout - self.running_time()
|
||||
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
self.target,
|
||||
'Pixie-Dust',
|
||||
'{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message))
|
||||
if newline:
|
||||
Color.pl("")
|
||||
|
||||
|
||||
def running_time(self):
|
||||
return int(time.time() - self.start_time)
|
||||
|
||||
# Attack failed, already printed reason why
|
||||
reaver.interrupt()
|
||||
stdout_write.close()
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_pin_psk_ssid(stdout):
|
||||
@@ -196,12 +270,21 @@ class Reaver(Attack):
|
||||
|
||||
return (pin, psk, ssid)
|
||||
|
||||
def get_stdout(self):
|
||||
''' Gets output from stdout_file '''
|
||||
if not self.stdout_file:
|
||||
|
||||
def get_output(self):
|
||||
''' Gets output from reaver's output file '''
|
||||
if not self.output_filename:
|
||||
return ''
|
||||
with open(self.stdout_file, 'r') as fid:
|
||||
|
||||
if self.output_write:
|
||||
self.output_write.flush()
|
||||
|
||||
with open(self.output_filename, 'r') as fid:
|
||||
stdout = fid.read()
|
||||
|
||||
if Configuration.verbose > 1:
|
||||
Color.pe('\n{P} [reaver:stdout] %s' % '\n [reaver:stdout] '.join(stdout.split('\n')))
|
||||
|
||||
return stdout.strip()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user