Skip to content

Commit e6a0084

Browse files
committed
process: improved process and thread handling significant
1 parent 822163d commit e6a0084

10 files changed

Lines changed: 137 additions & 26 deletions

File tree

wifite/attack/all.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,12 @@ def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0):
223223
prompt += ' or {R}exit{W} %s? {C}' % options
224224

225225
Color.p(prompt)
226-
answer = input().lower()
226+
try:
227+
answer = input().lower()
228+
except KeyboardInterrupt:
229+
# If user presses Ctrl+C during input, default to exit
230+
Color.pl('\n{!} {O}Interrupted during input, exiting...{W}')
231+
return Answer.ExitOrReturn
227232

228233
if answer.startswith('s'):
229234
return Answer.Skip

wifite/attack/pmkid.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ def run_aircrack(self):
136136

137137
Color.clear_entire_line()
138138
Color.pattack('WPA', self.target, 'PMKID capture', 'Waiting for target to appear...')
139-
airodump_target = self.wait_for_target(airodump)
139+
try:
140+
airodump_target = self.wait_for_target(airodump)
141+
except Exception as e:
142+
Color.pl('\n{!} {R}Target timeout:{W} %s' % str(e))
143+
return None
140144

141145
# # Try to load existing handshake
142146
# if Configuration.ignore_old_handshakes == False:
@@ -286,8 +290,7 @@ def crack_pmkid_file(self, pmkid_file):
286290
if Configuration.wordlist is None:
287291
Color.pl('\n{!} {O}Not cracking PMKID because there is no {R}wordlist{O} (re-run with {C}--dict{O})')
288292

289-
# TODO: Uncomment once --crack is updated to support recracking PMKIDs.
290-
# Color.pl('{!} {O}Run Wifite with the {R}--crack{O} and {R}--dict{O} options to try again.')
293+
Color.pl('{!} {O}Run Wifite with the {R}--crack{O} and {R}--dict{O} options to try again.')
291294

292295
key = None
293296
else:

wifite/attack/wep.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ def run(self):
6161

6262
Color.clear_line()
6363
Color.p('\r{+} {O}waiting{W} for target to appear...')
64-
airodump_target = self.wait_for_target(airodump)
64+
try:
65+
airodump_target = self.wait_for_target(airodump)
66+
except Exception as e:
67+
Color.pl('\n{!} {R}Target timeout:{W} %s' % str(e))
68+
break
6569

6670
fakeauth_proc = None
6771
if self.fake_auth():
@@ -95,7 +99,11 @@ def run(self):
9599

96100
# Loop until attack completes.
97101
while True:
98-
airodump_target = self.wait_for_target(airodump)
102+
try:
103+
airodump_target = self.wait_for_target(airodump)
104+
except Exception as e:
105+
Color.pl('\n{!} {R}Target timeout:{W} %s' % str(e))
106+
break
99107

100108
if client_mac is None and len(airodump_target.clients) > 0:
101109
client_mac = airodump_target.clients[0].station

wifite/attack/wpa.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
from ..model.attack import Attack
5-
from ..tools.aircrack import Aircrack # Still used for other things like WEP
5+
from ..tools.aircrack import Aircrack
66
from ..tools.hashcat import Hashcat
77
from ..tools.airodump import Airodump
88
from ..tools.aireplay import Aireplay
@@ -72,7 +72,7 @@ def run(self):
7272
cracker = "Hashcat" # Default to Hashcat
7373
# TODO: Potentially add a fallback or user choice for aircrack-ng for non-SAE?
7474
# For now, transitioning WPA/WPA2 cracking to Hashcat as well for consistency,
75-
# as Hashcat mode 2500 (hccapx) is generally preferred over aircrack-ng.
75+
# as Hashcat mode 22000 (hccapx) is generally preferred over aircrack-ng.
7676
# Aircrack.crack_handshake might be removed or kept for WEP only in future.
7777

7878
Color.pl(f'\n{{+}} {{C}}Cracking {"WPA3-SAE" if target_is_wpa3_sae else "WPA/WPA2"} Handshake:{{W}} Running {{C}}{cracker}{{W}} with '
@@ -112,7 +112,11 @@ def capture_handshake(self):
112112

113113
Color.clear_entire_line()
114114
Color.pattack('WPA', self.target, 'Handshake capture', 'Waiting for target to appear...')
115-
airodump_target = self.wait_for_target(airodump)
115+
try:
116+
airodump_target = self.wait_for_target(airodump)
117+
except Exception as e:
118+
Color.pl('\n{!} {R}Target timeout:{W} %s' % str(e))
119+
return None
116120

117121
self.clients = []
118122

@@ -181,7 +185,11 @@ def capture_handshake(self):
181185
os.remove(temp_file)
182186

183187
# Look for new clients
184-
airodump_target = self.wait_for_target(airodump)
188+
try:
189+
airodump_target = self.wait_for_target(airodump)
190+
except Exception as e:
191+
Color.pl('\n{!} {R}Target timeout:{W} %s' % str(e))
192+
break # Exit the capture loop
185193
for client in airodump_target.clients:
186194
if client.station not in self.clients:
187195
Color.clear_entire_line()

wifite/model/attack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def wait_for_target(self, airodump):
2323
while len(targets) == 0:
2424
# Wait for target to appear in airodump.
2525
if int(time.time() - start_time) > Attack.target_wait:
26-
raise Exception(f'Target did not appear after {Attack.target_wait:d} seconds, stopping')
26+
raise Exception(f'Target did not appear after {Attack.target_wait:d} seconds, target may be out of range or turned off')
2727
time.sleep(1)
2828
targets = airodump.get_targets()
2929
continue

wifite/tools/bully.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ def run(self):
7373
output_file_prefix='wps_pin') as airodump:
7474
# Wait for target
7575
self.pattack('Waiting for target to appear...')
76-
self.target = self.wait_for_target(airodump)
76+
try:
77+
self.target = self.wait_for_target(airodump)
78+
except Exception as e:
79+
self.pattack('{R}Failed: {O}Target timeout: %s{W}' % str(e), newline=True)
80+
return self.crack_result is not None
7781

7882
# Start bully
7983
self.bully_proc = Process(self.cmd,

wifite/tools/reaver.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ def _run(self):
101101

102102
# Wait for target
103103
self.pattack('Waiting for target to appear...')
104-
self.target = self.wait_for_target(airodump)
104+
try:
105+
self.target = self.wait_for_target(airodump)
106+
except Exception as e:
107+
self.pattack('{R}Failed: {O}Target timeout: %s{W}' % str(e), newline=True)
108+
return self.crack_result is not None
105109

106110
# Start reaver
107111
self.reaver_proc = Process(self.reaver_cmd,
@@ -115,7 +119,11 @@ def _run(self):
115119
while self.crack_result is None and self.reaver_proc.poll() is None:
116120

117121
# Refresh target information (power)
118-
self.target = self.wait_for_target(airodump)
122+
try:
123+
self.target = self.wait_for_target(airodump)
124+
except Exception as e:
125+
self.pattack('{R}Failed: {O}Target timeout: %s{W}' % str(e), newline=True)
126+
break
119127

120128
# Update based on reaver output
121129
stdout = self.get_output()

wifite/util/process.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import atexit
99
import threading
10+
import subprocess
1011
from subprocess import Popen, PIPE
1112
from ..util.color import Color
1213
from ..config import Configuration
@@ -213,9 +214,18 @@ def stdin(self, text):
213214
def get_output(self):
214215
""" Waits for process to finish, sets stdout & stderr """
215216
if self.pid.poll() is None:
216-
self.pid.wait()
217+
try:
218+
self.pid.wait(timeout=30) # 30 second timeout
219+
except subprocess.TimeoutExpired:
220+
# Force kill if process doesn't finish in time
221+
self.force_kill()
217222
if self.out is None:
218-
(self.out, self.err) = self.pid.communicate()
223+
try:
224+
(self.out, self.err) = self.pid.communicate(timeout=10) # 10 second timeout
225+
except subprocess.TimeoutExpired:
226+
# Force kill and get partial output
227+
self.force_kill()
228+
(self.out, self.err) = self.pid.communicate()
219229

220230
if type(self.out) is bytes:
221231
self.out = self.out.decode('utf-8')

wifite/util/scanner.py

100755100644
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ def _extracted_from_find_targets_50(self):
9696
self.print_targets()
9797
Color.clear_entire_line()
9898
Color.p(prompt)
99-
answer = input().lower()
99+
try:
100+
answer = input().lower()
101+
except KeyboardInterrupt:
102+
# If user presses Ctrl+C during input, default to exit
103+
Color.pl('\n{!} {O}Interrupted during input, exiting...{W}')
104+
return False # Exit
100105

101106
return not answer.startswith('e')
102107

@@ -283,7 +288,14 @@ def _prompt_user_for_targets(self):
283288
chosen_targets = []
284289

285290
Color.p(input_str)
286-
for choice in input().split(','):
291+
try:
292+
user_input = input()
293+
except KeyboardInterrupt:
294+
# If user presses Ctrl+C during input, return empty list to exit
295+
Color.pl('\n{!} {O}Interrupted during target selection, exiting...{W}')
296+
return []
297+
298+
for choice in user_input.split(','):
287299
choice = choice.strip()
288300
if choice.lower() == 'all':
289301
chosen_targets = self.targets

wifite/wifite.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ def scan_and_attack():
8585

8686
Color.pl('')
8787

88-
# Scan
88+
# Scan (no signal handler during scanning to allow proper target selection)
8989
s = Scanner()
9090
do_continue = s.find_targets()
9191
targets = s.select_targets()
9292

93+
# Attack modules handle KeyboardInterrupt properly, no global handler needed
94+
9395
if Configuration.infinite_mode:
9496
while do_continue:
9597
AttackAll.attack_multiple(targets)
@@ -105,6 +107,14 @@ def scan_and_attack():
105107
Color.pl('{+} Finished attacking {C}%d{W} target(s), exiting' % attacked_targets)
106108

107109

110+
111+
112+
def force_exit_handler(signum, frame):
113+
"""Force exit on multiple Ctrl+C during cleanup"""
114+
import sys
115+
print('\n[!] Force exiting...')
116+
sys.exit(1)
117+
108118
def main():
109119
try:
110120
wifite = Wifite()
@@ -120,23 +130,66 @@ def main():
120130
Color.pl('\n{!} {R}Try running with sudo{W}\n')
121131
except KeyboardInterrupt:
122132
Color.pl('\n{!} {O}Interrupted, Shutting down...{W}')
133+
# Set up force exit handler for cleanup phase
134+
import signal
135+
signal.signal(signal.SIGINT, force_exit_handler)
123136
except Exception as e:
124137
Color.pl('\n{!} {R}Unexpected Error{W}: %s' % str(e))
125138
Color.pexception(e)
126139
Color.pl('\n{!} {R}Exiting{W}\n')
127140

128141
finally:
129-
# Ensure all processes are cleaned up
130-
from .util.process import ProcessManager
131-
ProcessManager().cleanup_all()
142+
# Set up aggressive force exit handler during cleanup
143+
import signal
144+
import sys
145+
146+
def emergency_exit(signum, frame):
147+
print('\n[!] Emergency exit!')
148+
# Disable atexit callbacks and suppress stderr to prevent ugly exception messages
149+
import atexit
150+
import os
151+
atexit._clear()
152+
# Redirect stderr to devnull to hide any remaining cleanup exceptions
153+
os.dup2(os.open(os.devnull, os.O_WRONLY), 2)
154+
sys.exit(1)
155+
156+
signal.signal(signal.SIGINT, emergency_exit)
157+
158+
# Quick cleanup with short timeouts
159+
try:
160+
from .util.process import ProcessManager
161+
import threading
162+
163+
# Run cleanup in thread with timeout
164+
cleanup_thread = threading.Thread(target=ProcessManager().cleanup_all)
165+
cleanup_thread.daemon = True
166+
cleanup_thread.start()
167+
cleanup_thread.join(timeout=3) # 3 second timeout
168+
except:
169+
pass # Ignore cleanup errors
132170

133-
# Delete Reaver .pcap
171+
# Delete Reaver .pcap quickly
134172
try:
135-
subprocess.run(["rm", "reaver_output.pcap"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
173+
subprocess.run(["rm", "-f", "reaver_output.pcap"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
174+
except:
175+
pass
176+
177+
# Try graceful exit with timeout
178+
try:
179+
import threading
180+
181+
def graceful_exit():
182+
Configuration.exit_gracefully()
183+
184+
exit_thread = threading.Thread(target=graceful_exit)
185+
exit_thread.daemon = True
186+
exit_thread.start()
187+
exit_thread.join(timeout=2) # 2 second timeout
136188
except:
137-
pass # Ignore if file doesn't exist
189+
pass
138190

139-
Configuration.exit_gracefully()
191+
# Force exit regardless
192+
sys.exit(0)
140193

141194

142195
if __name__ == '__main__':

0 commit comments

Comments
 (0)