Trying to fix bully & reaver Pixie-Dust attacks.

Haven't even looked at PIN attacks yet.

Hopefully helps out with #28
This commit is contained in:
derv82
2018-03-03 23:40:15 -05:00
parent 1a063edc42
commit 82f0a2ae96
5 changed files with 144 additions and 72 deletions

48
TODO.md
View File

@@ -65,36 +65,36 @@ If it's possible to run these programs on Windows or OSX, Wifite should suporrt
------------------------------------------------------ ------------------------------------------------------
### WPS detection ### WPS Attacks
See https://github.com/derv82/wifite2/issues/62 for discussion. Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used?
WASH Ideally for Pixie-Dust, we'd have:
* Wash does not seem to detect APs when given a .cap file
* Wash can scan, but is slow and does not provide as much info as airodump
* We could run Wash as a daemon on the same channel as airodump...
* Channel-hopping might interfere with each-other?
* Could we tell wash to channel hop & tell airodump-ng to not channelhop? Vice versa?
AIRODUMP 1. Switch to set bully/reaver timeout
* Airodump-ng detects WPS, but does not output to CSV 2. Identical counters between bully/reaver (failures, timeouts, lockouts)
* Airodump-ng WPS detection requires parsing airodump's STDOUT * I don't think users should be able to set failure/timeout thresholds (no switches).
3. Identical statuses between bully/reaver.
* Errors: "WPSFail", "Timeout", "NoAssoc", etc
* Statuses: "Waiting for target", "Trying PIN", "Sending M2 message", "Running pixiewps", etc.
* "Step X/Y" is nice, but not entirely accurate.
* It's weird when we go from (6/8) to (5/8) without explanation. And the first 4 are usually not displayed.
3. Countdown timer until attack is aborted (e.g. 5min)
4. Countdown timer on "step timeout" (time since last status changed, e.g. 30s)
TSHARK Order of statuses:
* DIY: Extract Beacon frames from the .cap file with WPS flags... 1. Waiting for beacon
* `tshark -r f.cap -R "wps.primary_device_type.category == 6" -n -2` 2. Associating with target
3. Trying PIN / EAPOL start / identity response / M1,M2 (M3,M4)
4. Running pixiewps
5. Cracked or Failed
We can extract WPS networks' BSSID and WPS lock status: And as for PIN cracking.. um.. Not even sure this should be an option in Wifite TBH.
PIN cracking takes days and most APs auto-lock after 3 attempts.
Multi-day (possibly multi-month) attacks aren't a good fit for Wifite.
Users with that kind of dedication can run bully/reaver themselves.
```bash ------------------------------------------------------
% tshark -r withwps-01.cap -n -Y "wps.wifi_protected_setup_state && wlan.da == ff:ff:ff:ff:ff:ff" -T fields -e wlan.ta -e wps.ap_setup_locked -E separator=,
# Output:
fc:51:a4:1e:11:67,
98:e7:f4:90:f1:12,0x00000001
10:13:31:30:35:2c,
```
--------------------------------
### Directory structure ### Directory structure

View File

@@ -2,11 +2,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from Attack import Attack from Attack import Attack
from Airodump import Airodump
from Color import Color from Color import Color
from Configuration import Configuration from Configuration import Configuration
from CrackResultWPS import CrackResultWPS
from Process import Process
from Bully import Bully from Bully import Bully
from Reaver import Reaver from Reaver import Reaver
@@ -29,6 +26,8 @@ class AttackWPS(Attack):
if Configuration.use_bully: if Configuration.use_bully:
# Bully: Pixie-dust # Bully: Pixie-dust
bully = Bully(self.target, pixie=True) bully = Bully(self.target, pixie=True)
bully.run()
bully.stop()
if bully.crack_result is not None: if bully.crack_result is not None:
self.crack_result = bully.crack_result self.crack_result = bully.crack_result
return True return True
@@ -51,6 +50,8 @@ class AttackWPS(Attack):
if Configuration.use_bully: if Configuration.use_bully:
# Bully: PIN guessing # Bully: PIN guessing
bully = Bully(self.target, pixie=False) bully = Bully(self.target, pixie=False)
bully.run()
bully.stop()
if bully.crack_result is not None: if bully.crack_result is not None:
self.crack_result = bully.crack_result self.crack_result = bully.crack_result
return True return True

View File

@@ -7,6 +7,7 @@ from Color import Color
from Timer import Timer from Timer import Timer
from Process import Process from Process import Process
from Configuration import Configuration from Configuration import Configuration
from CrackResultWPS import CrackResultWPS
import os, time, re import os, time, re
from threading import Thread from threading import Thread
@@ -37,14 +38,11 @@ class Bully(Attack):
"--force", "--force",
"-v", "4" "-v", "4"
] ]
#self.cmd.extend(["-p", "80246212", "--force", "--bruteforce"])
if self.pixie: if self.pixie:
self.cmd.append("--pixiewps") self.cmd.append("--pixiewps")
self.cmd.append(Configuration.interface) self.cmd.append(Configuration.interface)
self.bully_proc = None self.bully_proc = None
self.run()
self.stop()
def attack_type(self): def attack_type(self):
return "Pixie-Dust" if self.pixie else "PIN Attack" return "Pixie-Dust" if self.pixie else "PIN Attack"
@@ -123,13 +121,13 @@ class Bully(Attack):
def parse_line(self, line): def parse_line(self, line):
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c) # [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
got_beacon = re.compile(r".*Got beacon for '(.*)' \((.*)\)").match(line) got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
if got_beacon: if got_beacon:
# group(1)=ESSID, group(2)=BSSID # group(1)=ESSID, group(2)=BSSID
self.state = "Got beacon" self.state = "Got beacon"
# [+] Last State = 'NoAssoc' Next pin '48855501' # [+] Last State = 'NoAssoc' Next pin '48855501'
last_state = re.compile(r".*Last State = '(.*)'\s*Next pin '(.*)'").match(line) last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line)
if last_state: if last_state:
# group(1)=result, group(2)=PIN # group(1)=result, group(2)=PIN
result = "Start" # last_state.group(1) result = "Start" # last_state.group(1)
@@ -138,7 +136,7 @@ class Bully(Attack):
# [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505' # [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505'
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263' # [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
rx_m = re.compile(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'").match(line) rx_m = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
if rx_m: if rx_m:
# group(1)=M3/M5, group(2)=result, group(3)=PIN # group(1)=M3/M5, group(2)=result, group(3)=PIN
self.m_state = rx_m.group(1) self.m_state = rx_m.group(1)
@@ -161,48 +159,54 @@ class Bully(Attack):
self.state = "Trying PIN:{C}%s{W} (%s)" % (pin, result) self.state = "Trying PIN:{C}%s{W} (%s)" % (pin, result)
# [!] WPS lockout reported, sleeping for 43 seconds ... # [!] WPS lockout reported, sleeping for 43 seconds ...
lock_out = re.compile(r".*WPS lockout reported, sleeping for (\d+) seconds").match(line) lock_out = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line)
if lock_out: if lock_out:
sleeping = lock_out.group(1) sleeping = lock_out.group(1)
self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
self.consecutive_lockouts += 1 self.consecutive_lockouts += 1
# [Pixie-Dust] WPS pin not found # [Pixie-Dust] WPS pin not found
pixie_re = re.compile(r".*\[Pixie-Dust\] WPS pin not found").match(line) pixie_re = re.search(r".*\[Pixie-Dust\] WPS pin not found", line)
if pixie_re: if pixie_re:
self.state = "{R}Failed{W}" self.state = "{R}Failed{W}"
# [+] Running pixiewps with the information, wait ... # [+] Running pixiewps with the information, wait ...
pixie_re = re.compile(r".*Running pixiewps with the information").match(line) pixie_re = re.search(r".*Running pixiewps with the information", line)
if pixie_re: if pixie_re:
self.state = "{G}Running pixiewps...{W}" self.state = "{G}Running pixiewps...{W}"
# [*] Pin is '80246213', key is 'password' # [*] Pin is '80246213', key is 'password'
pin_key_re = re.compile(r"^\s*Pin is '(\d*)', key is '(.*)'\s*$").match(line) # [*] Pin is '11867722', key is '9a6f7997'
pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line)
if pin_key_re: if pin_key_re:
self.cracked_pin = pin_key_re.group(1) self.cracked_pin = pin_key_re.group(1)
self.cracked_key = pin_key_re.group(2) self.cracked_key = pin_key_re.group(2)
# PIN : '80246213' # PIN : '80246213'
pin_re = re.compile(r"^\s*PIN\s*:\s*'(.*)'\s*$").match(line) pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line)
if pin_re: self.cracked_pin = pin_re.group(1) if pin_re:
self.cracked_pin = pin_re.group(1)
# KEY : 'password' # KEY : 'password'
key_re = re.compile(r"^\s*KEY\s*:\s*'(.*)'\s*$").match(line) key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
if key_re: self.cracked_key = key_re.group(1) if key_re:
self.cracked_key = key_re.group(1)
#warn_re = re.compile(r"\[\!\]\s*(.*)$").match(line) #warn_re = re.search(r"\[\!\]\s*(.*)$", line)
#if warn_re: self.state = "{O}%s{W}" % warn_re.group(1) #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: if not self.crack_result and self.cracked_pin and self.cracked_key:
Color.clear_entire_line() Color.clear_entire_line()
Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n") Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}")
Color.pl("")
self.crack_result = CrackResultWPS( self.crack_result = CrackResultWPS(
airodump_target.essid, self.target.bssid,
airodump_target.bssid, self.target.essid,
self.cracked_pin, self.cracked_pin,
self.cracked_key) self.cracked_key)
Color.pl("")
self.crack_result.dump()
return True return True
else: else:
return False return False
@@ -213,3 +217,12 @@ class Bully(Attack):
def __del__(self): def __del__(self):
self.stop() self.stop()
if __name__ == '__main__':
stdout = " [*] Pin is '11867722', key is '9a6f7997'"
Configuration.initialize(False)
from Target import Target
fields = 'AA:BB:CC:DD:EE:FF,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,HOME-ABCD,'.split(',')
target = Target(fields)
b = Bully(target)
b.parse_line(stdout)

View File

@@ -16,12 +16,16 @@ class CrackResultWPS(CrackResult):
super(CrackResultWPS, self).__init__() super(CrackResultWPS, self).__init__()
def dump(self): def dump(self):
if self.essid: if self.essid is not None:
Color.pl('{+} %s: {C}%s{W}' % ( 'ESSID'.rjust(12), self.essid)) Color.pl('{+} %s: {C}%s{W}' % ( 'ESSID'.rjust(12), self.essid))
if self.psk is None:
psk = '{O}N/A{W}'
else:
psk = '{G}%s{W}' % self.psk
Color.pl('{+} %s: {C}%s{W}' % ( 'BSSID'.rjust(12), self.bssid)) Color.pl('{+} %s: {C}%s{W}' % ( 'BSSID'.rjust(12), self.bssid))
Color.pl('{+} %s: {C}WPA{W} ({C}WPS{W})' % 'Encryption'.rjust(12)) Color.pl('{+} %s: {C}WPA{W} ({C}WPS{W})' % 'Encryption'.rjust(12))
Color.pl('{+} %s: {G}%s{W}' % ( 'WPS PIN'.rjust(12), self.pin)) Color.pl('{+} %s: {G}%s{W}' % ( 'WPS PIN'.rjust(12), self.pin))
Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), self.psk)) Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), psk))
def to_dict(self): def to_dict(self):
return { return {

View File

@@ -70,7 +70,7 @@ class Reaver(Attack):
(pin, psk, ssid) = self.get_pin_psk_ssid(stdout) (pin, psk, ssid) = self.get_pin_psk_ssid(stdout)
# Check if we cracked it, or if process stopped. # Check if we cracked it, or if process stopped.
if (pin and psk and ssid) or reaver.poll() is not None: if pin is not None or reaver.poll() is not None:
reaver.interrupt() reaver.interrupt()
# Check one-last-time for PIN/PSK/SSID, in case of race condition. # Check one-last-time for PIN/PSK/SSID, in case of race condition.
@@ -78,11 +78,12 @@ class Reaver(Attack):
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(stdout) (pin, psk, ssid) = Reaver.get_pin_psk_ssid(stdout)
# Check if we cracked it. # Check if we cracked it.
if pin and psk and ssid: if pin is not None:
# We cracked it. # We cracked it.
bssid = self.target.bssid bssid = self.target.bssid
Color.clear_entire_line() Color.clear_entire_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n") 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 = CrackResultWPS(bssid, ssid, pin, psk)
self.crack_result.dump() self.crack_result.dump()
return True return True
@@ -219,7 +220,11 @@ class Reaver(Attack):
# CHECK FOR CRACK # CHECK FOR CRACK
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(out) (pin, psk, ssid) = Reaver.get_pin_psk_ssid(out)
if pin and psk and ssid: if pin is not None:
if psk is None:
psk = ''
elif ssid is None:
ssid = target.essid
# We cracked it. # We cracked it.
self.success = True self.success = True
Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n') Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n')
@@ -238,15 +243,14 @@ class Reaver(Attack):
if match: if match:
# Reset failures on successful try # Reset failures on successful try
failures = 0 failures = 0
groups = match.groups() pin_current = int(match.group(1))
pin_current = int(groups[0]) pin_total = int(match.group(2))
pin_total = int(groups[1])
# Reaver 1.3, 1.4 # Reaver 1.3, 1.4
match = None match = None
for match in re.finditer('Trying pin (\d+)', out): for match in re.finditer('Trying pin (\d+)', out):
if match: if match:
pin = int(match.groups()[0]) pin = int(match.group(1))
if pin not in pins: if pin not in pins:
# Reset failures on successful try # Reset failures on successful try
failures = 0 failures = 0
@@ -282,14 +286,14 @@ class Reaver(Attack):
match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out) match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out)
if match: if match:
eta = match.groups()[0] eta = match.group(1)
state = '{C}cracking, ETA: {G}%s{W}' % eta 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) match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out)
if match: if match:
eta = match.groups()[0] eta = match.group(1)
state = '{C}cracking, ETA: {G}%s{W}' % eta state = '{C}cracking, ETA: {G}%s{W}' % eta
pins_left = int(match.groups()[1]) pins_left = int(match.group(2))
# Divine pin_current & pin_total from this: # Divine pin_current & pin_total from this:
pin_current = 11000 - pins_left pin_current = 11000 - pins_left
@@ -341,24 +345,29 @@ class Reaver(Attack):
pin = psk = ssid = None pin = psk = ssid = None
# Check for PIN. # Check for PIN.
# PIN: Printed *before* the attack completes. ''' [+] WPS pin: 11867722'''
regex = re.search('WPS pin: *([0-9]*)', stdout) regex = re.search(r"WPS pin:\s*([0-9]*)", stdout, re.IGNORECASE)
if regex: if regex:
pin = regex.groups()[0] pin = regex.group(1)
# PIN: Printed when attack is completed.
regex = re.search("WPS PIN: *'([0-9]+)'", stdout)
if regex:
pin = regex.groups()[0]
# Check for PSK. # Check for PSK.
# Note: Reaver 1.6.x does not appear to return PSK (?)
regex = re.search("WPA PSK: *'(.+)'", stdout) regex = re.search("WPA PSK: *'(.+)'", stdout)
if regex: if regex:
psk = regex.groups()[0] psk = regex.group(1)
# Check for SSID # Check for SSID
regex = re.search("AP SSID: *'(.+)'", stdout) """1.x [Reaver Test] [+] AP SSID: 'Test Router' """
regex = re.search(r"AP SSID:\s*'(.*)'", stdout)
if regex: if regex:
ssid = regex.groups()[0] ssid = regex.group(1)
# Check (again) for SSID
if ssid is None:
"""1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)"""
regex = re.search(r"Associated with [0-9A-F:]+ \(ESSID: (.*)\)", stdout)
if regex:
ssid = regex.group(1)
return (pin, psk, ssid) return (pin, psk, ssid)
@@ -372,7 +381,7 @@ class Reaver(Attack):
if __name__ == '__main__': if __name__ == '__main__':
stdout = ''' old_stdout = '''
[Pixie-Dust] [Pixie-Dust]
[Pixie-Dust] Pixiewps 1.1 [Pixie-Dust] Pixiewps 1.1
[Pixie-Dust] [Pixie-Dust]
@@ -390,7 +399,52 @@ Cmd : reaver -i wlan0mon -b 08:86:3B:8C:FD:9C -c 11 -s y -vv -p 28097402
[Reaver Test] [+] WPS PIN: '12345678' [Reaver Test] [+] WPS PIN: '12345678'
[Reaver Test] [+] WPA PSK: 'Test PSK' [Reaver Test] [+] WPA PSK: 'Test PSK'
[Reaver Test] [+] AP SSID: 'Test Router' [Reaver Test] [+] AP SSID: 'Test Router'
''' '''
print Reaver.get_pin_psk_ssid(stdout)
pass
# From vom513 in https://github.com/derv82/wifite2/issues/60
new_stdout = '''
[+] Switching wlan1mon to channel 5
[+] Waiting for beacon from EC:1A:59:37:70:0E
[+] Received beacon from EC:1A:59:37:70:0E
[+] Vendor: RealtekS
[+] Trying pin "12345670"
[+] Sending authentication request
[+] Sending association request
[+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)
[+] Sending EAPOL START request
[+] Received identity request
[+] Sending identity response
[+] Received M1 message
[+] Sending M2 message
Pixiewps 1.4
[?] Mode: 3 (RTL819x)
[*] Seed N1: -
[*] Seed ES1: -
[*] Seed ES2: -
[*] PSK1: 2c2e33f5e3a870759f0aeebbd2792450
[*] PSK2: 3f4ca4ea81b2e8d233a4b80f9d09805d
[*] ES1: 04d48dc20ec785762ce1a21a50bc46c2
[*] ES2: 04d48dc20ec785762ce1a21a50bc46c2
[+] WPS pin: 11867722
[*] Time taken: 0 s 21 ms
executing pixiewps -e d0141b15656e96b85fcead2e8e76330d2b1ac1576bb026e7a328c0e1baf8cf91664371174c08ee12ec92b0519c54879f21255be5a8770e1fa1880470ef423c90e34d7847a6fcb4924563d1af1db0c481ead9852c519bf1dd429c163951cf69181b132aea2a3684caf35bc54aca1b20c88bb3b7339ff7d56e09139d77f0ac58079097938251dbbe75e86715cc6b7c0ca945fa8dd8d661beb73b414032798dadee32b5dd61bf105f18d89217760b75c5d966a5a490472ceba9e3b4224f3d89fb2b -s 5a67001334e3e4cb236f4e134a4d3b48d625a648e991f978d9aca879469d5da5 -z c8a2ccc5fb6dc4f4d69b245091022dc7e998e42ec1d548d57c35a312ff63ef20 -a 60b59c0c587c6c44007f7081c3372489febbe810a97483f5cc5cd8463c3920de -n 04d48dc20ec785762ce1a21a50bc46c2 -r 7a191e22a7b519f40d3af21b93a21d4f837718b45063a8a69ac6d16c6e5203477c18036ca01e9e56d0322e70c2e1baa66518f1b46d01acc577d1dfa34efd2e9ee36e2b7e68819cddacceb596a8895243e33cb48c570458a539dcb523a4d4c4360e158c29b882f7f385821ea043705eb56538b45daa445157c84e60fc94ef48136eb4e9725b134902b96c90b1ae54cbd42b29b52611903fdae5aa88bfc320f173d2bbe31df4996ebdb51342c6b8bd4e82ae5aa80b2a09a8bf8faa9a8332dc9819
'''
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(old_stdout)
assert pin == '12345678', 'pin was "%s", should have been "12345678"' % pin
assert psk == 'Test PSK', 'psk was "%s", should have been "Test PSK"' % psk
assert ssid == "Test Router", 'ssid was %s, should have been Test Router' % repr(ssid)
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
result.dump()
print ""
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(new_stdout)
assert pin == '11867722', 'pin was "%s", should have been "11867722"' % pin
assert psk == None, 'psk was "%s", should have been "None"' % psk
assert ssid == "belkin.00e", 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
result.dump()