Skip to content

Commit 2143b22

Browse files
committed
wpa: improve captures by using 'hcxdumptool' as default and 'aircrack-ng' on fallback
1 parent 658c544 commit 2143b22

5 files changed

Lines changed: 216 additions & 150 deletions

File tree

wifite/args.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,9 @@ def _add_global_args(self, glob):
451451
glob.add_argument('--hcxdump',
452452
action='store_true',
453453
dest='use_hcxdump',
454-
help=Color.s('Use {C}hcxdumptool{W} for dual interface WPA handshake capture. '
455-
'Provides PMF-aware capture and full spectrum monitoring. '
454+
help=Color.s('Use {C}hcxdumptool{W} for WPA handshake capture (single or dual interface). '
455+
'Creates .pcapng files with better compatibility for Hashcat. '
456+
'Provides PMF-aware capture and built-in deauth capabilities. '
456457
'Falls back to airodump-ng if hcxdumptool is unavailable. '
457458
'Requires hcxdumptool v6.2.0+ (default: {G}off{W})'))
458459

wifite/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,10 +1055,10 @@ def parse_dual_interface_args(cls, args):
10551055
cls.auto_assign_interfaces = False
10561056
Color.pl('{+} {C}option:{W} automatic interface assignment {O}disabled{W}')
10571057

1058-
# hcxdump mode for dual interface WPA capture
1058+
# hcxdump mode for WPA capture (single or dual interface)
10591059
if hasattr(args, 'use_hcxdump') and args.use_hcxdump:
10601060
cls.use_hcxdump = True
1061-
Color.pl('{+} {C}option:{W} using {G}hcxdumptool{W} for dual interface WPA capture')
1061+
Color.pl('{+} {C}option:{W} using {G}hcxdumptool{W} for WPA handshake capture')
10621062

10631063
@classmethod
10641064
def parse_wpasec_args(cls, args):

wifite/tools/hashcat.py

Lines changed: 165 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -37,52 +37,63 @@ def crack_handshake(handshake_obj, target_is_wpa3_sae, show_command=False):
3737
return Aircrack.crack_handshake(handshake_obj, show_command=show_command)
3838

3939
key = None
40-
# Mode 22000 supports both WPA/WPA2 and WPA3-SAE (WPA-PBKDF2-PMKID+EAPOL)
41-
hashcat_mode = '22000'
42-
file_type_msg = "WPA3-SAE hash" if target_is_wpa3_sae else "WPA/WPA2 hash"
43-
44-
Color.pl(f"{{+}} {{C}}Attempting to crack {file_type_msg} using Hashcat mode {hashcat_mode}{{W}}")
45-
46-
# Crack hash_file
47-
for additional_arg in ([], ['--show']):
48-
command = [
49-
'hashcat',
50-
'--quiet',
51-
'-m', hashcat_mode,
52-
hash_file,
53-
Configuration.wordlist
54-
]
55-
if Hashcat.should_use_force():
56-
command.append('--force')
57-
command.extend(additional_arg)
58-
if show_command:
59-
Color.pl(f'{{+}} {{D}}Running: {{W}}{{P}}{" ".join(command)}{{W}}')
60-
process = Process(command)
61-
stdout, stderr = process.get_output()
62-
63-
# Check for errors first
64-
if 'No hashes loaded' in stdout or 'No hashes loaded' in stderr:
65-
continue # No valid hashes to crack
66-
67-
if ':' not in stdout:
68-
continue # No cracked results
69-
70-
# Parse the key from hashcat output
71-
# Expected format: hash:password
72-
lines = stdout.strip().split('\n')
73-
for line in lines:
74-
if ':' in line and not line.startswith('The plugin') and 'hashcat.net' not in line:
75-
# Take the last part after the last colon as the password
76-
parts = line.split(':')
77-
if len(parts) >= 2:
78-
key = parts[-1].strip()
79-
if key and len(key) > 0:
80-
break
81-
else:
82-
continue
83-
break
40+
try:
41+
# Mode 22000 supports both WPA/WPA2 and WPA3-SAE (WPA-PBKDF2-PMKID+EAPOL)
42+
hashcat_mode = '22000'
43+
file_type_msg = "WPA3-SAE hash" if target_is_wpa3_sae else "WPA/WPA2 hash"
44+
45+
Color.pl(f"{{+}} {{C}}Attempting to crack {file_type_msg} using Hashcat mode {hashcat_mode}{{W}}")
46+
47+
# Crack hash_file
48+
for additional_arg in ([], ['--show']):
49+
command = [
50+
'hashcat',
51+
'--quiet',
52+
'-m', hashcat_mode,
53+
hash_file,
54+
Configuration.wordlist
55+
]
56+
if Hashcat.should_use_force():
57+
command.append('--force')
58+
command.extend(additional_arg)
59+
if show_command:
60+
Color.pl(f'{{+}} {{D}}Running: {{W}}{{P}}{" ".join(command)}{{W}}')
61+
process = Process(command)
62+
stdout, stderr = process.get_output()
63+
64+
# Check for errors first
65+
if 'No hashes loaded' in stdout or 'No hashes loaded' in stderr:
66+
continue # No valid hashes to crack
67+
68+
if ':' not in stdout:
69+
continue # No cracked results
70+
71+
# Parse the key from hashcat output
72+
# Expected format: hash:password
73+
lines = stdout.strip().split('\n')
74+
for line in lines:
75+
if ':' in line and not line.startswith('The plugin') and 'hashcat.net' not in line:
76+
# Take the last part after the last colon as the password
77+
parts = line.split(':')
78+
if len(parts) >= 2:
79+
key = parts[-1].strip()
80+
if key and len(key) > 0:
81+
break
82+
else:
83+
continue
84+
break
8485

85-
return key
86+
return key
87+
finally:
88+
# Cleanup temporary hash file
89+
if hash_file and os.path.exists(hash_file):
90+
try:
91+
os.remove(hash_file)
92+
if Configuration.verbose > 1:
93+
Color.pl('{!} {O}Cleaned up temporary hash file{W}')
94+
except OSError as e:
95+
if Configuration.verbose > 0:
96+
Color.pl('{!} {O}Warning: Could not remove hash file: %s{W}' % str(e))
8697

8798
@staticmethod
8899
def crack_pmkid(pmkid_file, verbose=False):
@@ -175,73 +186,113 @@ def generate_hash_file(handshake_obj, is_wpa3_sae, show_command=False):
175186
For WPA3-SAE, generates hash file for mode 22001.
176187
Both use the same hcxpcapngtool -o flag, as mode 22000 supports both WPA2 and WPA3-SAE.
177188
"""
189+
import tempfile
190+
178191
# Use mode 22000 format for both WPA2 and WPA3-SAE
179192
# Hashcat mode 22000 supports WPA-PBKDF2-PMKID+EAPOL (includes SAE)
180193
# Mode 22001 is for WPA-PMK-PMKID+EAPOL (pre-computed PMK)
181-
hash_file = Configuration.temp('generated.22000')
182-
183-
if os.path.exists(hash_file):
184-
os.remove(hash_file)
185-
186-
command = [
187-
'hcxpcapngtool',
188-
'-o', hash_file,
189-
handshake_obj.capfile # Assuming handshake_obj has a capfile attribute
190-
]
194+
195+
# Create secure temporary file with proper permissions (0600)
196+
# Using NamedTemporaryFile with delete=False to prevent race conditions
197+
with tempfile.NamedTemporaryFile(mode='w', suffix='.22000', delete=False, prefix='wifite_hash_') as tmp:
198+
hash_file = tmp.name
199+
200+
# Verify file permissions are secure (0600)
201+
os.chmod(hash_file, 0o600)
191202

192-
if show_command:
193-
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
203+
try:
204+
command = [
205+
'hcxpcapngtool',
206+
'-o', hash_file,
207+
handshake_obj.capfile # Assuming handshake_obj has a capfile attribute
208+
]
194209

195-
process = Process(command)
196-
stdout, stderr = process.get_output()
197-
if not os.path.exists(hash_file) or os.path.getsize(hash_file) == 0:
198-
# Check if this is due to missing frames (common with airodump captures)
199-
if 'no hashes written' in stdout.lower() or 'missing frames' in stdout.lower():
200-
Color.pl('{!} {O}Warning: hcxpcapngtool could not extract hash (capture quality issue){W}')
201-
Color.pl('{!} {O}The capture file is missing required frames or metadata{W}')
202-
Color.pl('{!} {O}This is common with airodump-ng captures - consider using hcxdumptool instead{W}')
203-
# Return None to signal fallback to aircrack-ng should be used
204-
return None
205-
206-
# For other errors, provide detailed error message
207-
error_msg = f'Failed to generate {"SAE hash" if is_wpa3_sae else "WPA/WPA2 hash"} file.'
208-
error_msg += f'\nOutput from hcxpcapngtool:\nSTDOUT: {stdout}\nSTDERR: {stderr}'
209-
# Also include tshark check for WPA3
210-
if is_wpa3_sae:
211-
from .tshark import Tshark
212-
tshark_check_cmd = ['tshark', '-r', handshake_obj.capfile, '-Y', 'wlan.fc.type_subtype == 0x0b'] # Authentication frames
213-
tshark_process = Process(tshark_check_cmd)
214-
tshark_stdout, _ = tshark_process.get_output()
215-
if not tshark_stdout:
216-
error_msg += '\nAdditionally, tshark found no authentication frames in the capture file. Ensure it is a valid WPA3-SAE handshake.'
217-
else:
218-
error_msg += f'\nTshark found {len(tshark_stdout.strip().split(chr(10)))} authentication frames in the capture.'
210+
if show_command:
211+
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
219212

220-
raise ValueError(error_msg)
221-
return hash_file
213+
process = Process(command)
214+
stdout, stderr = process.get_output()
215+
if not os.path.exists(hash_file) or os.path.getsize(hash_file) == 0:
216+
# Check if this is due to missing frames (common with airodump captures)
217+
if 'no hashes written' in stdout.lower() or 'missing frames' in stdout.lower():
218+
Color.pl('{!} {O}Warning: hcxpcapngtool could not extract hash (capture quality issue){W}')
219+
Color.pl('{!} {O}The capture file is missing required frames or metadata{W}')
220+
Color.pl('{!} {O}This is common with airodump-ng captures - consider using hcxdumptool instead{W}')
221+
# Cleanup failed hash file
222+
if os.path.exists(hash_file):
223+
try:
224+
os.remove(hash_file)
225+
except OSError:
226+
pass
227+
# Return None to signal fallback to aircrack-ng should be used
228+
return None
229+
230+
# For other errors, provide detailed error message
231+
error_msg = f'Failed to generate {"SAE hash" if is_wpa3_sae else "WPA/WPA2 hash"} file.'
232+
error_msg += f'\nOutput from hcxpcapngtool:\nSTDOUT: {stdout}\nSTDERR: {stderr}'
233+
# Also include tshark check for WPA3
234+
if is_wpa3_sae:
235+
from .tshark import Tshark
236+
tshark_check_cmd = ['tshark', '-r', handshake_obj.capfile, '-Y', 'wlan.fc.type_subtype == 0x0b'] # Authentication frames
237+
tshark_process = Process(tshark_check_cmd)
238+
tshark_stdout, _ = tshark_process.get_output()
239+
if not tshark_stdout:
240+
error_msg += '\nAdditionally, tshark found no authentication frames in the capture file. Ensure it is a valid WPA3-SAE handshake.'
241+
else:
242+
error_msg += f'\nTshark found {len(tshark_stdout.strip().split(chr(10)))} authentication frames in the capture.'
243+
244+
raise ValueError(error_msg)
245+
return hash_file
246+
except Exception:
247+
# Cleanup hash file on any error
248+
if hash_file and os.path.exists(hash_file):
249+
try:
250+
os.remove(hash_file)
251+
if Configuration.verbose > 1:
252+
Color.pl('{!} {O}Cleaned up temporary hash file after error{W}')
253+
except OSError:
254+
pass
255+
raise
222256

223257
@staticmethod
224258
def generate_john_file(handshake, show_command=False):
225-
john_file = Configuration.temp('generated.john')
226-
if os.path.exists(john_file):
227-
os.remove(john_file)
259+
import tempfile
260+
261+
# Create secure temporary file with proper permissions (0600)
262+
# Using NamedTemporaryFile with delete=False to prevent race conditions
263+
with tempfile.NamedTemporaryFile(mode='w', suffix='.john', delete=False, prefix='wifite_john_') as tmp:
264+
john_file = tmp.name
265+
266+
# Verify file permissions are secure (0600)
267+
os.chmod(john_file, 0o600)
228268

229-
command = [
230-
'hcxpcapngtool',
231-
'--john', john_file,
232-
handshake.capfile
233-
]
234-
235-
if show_command:
236-
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
269+
try:
270+
command = [
271+
'hcxpcapngtool',
272+
'--john', john_file,
273+
handshake.capfile
274+
]
237275

238-
process = Process(command)
239-
stdout, stderr = process.get_output()
240-
if not os.path.exists(john_file):
241-
raise ValueError('Failed to generate .john file, output: \n%s\n%s' % (
242-
stdout, stderr))
276+
if show_command:
277+
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
243278

244-
return john_file
279+
process = Process(command)
280+
stdout, stderr = process.get_output()
281+
if not os.path.exists(john_file):
282+
raise ValueError('Failed to generate .john file, output: \n%s\n%s' % (
283+
stdout, stderr))
284+
285+
return john_file
286+
except Exception:
287+
# Cleanup john file on any error
288+
if john_file and os.path.exists(john_file):
289+
try:
290+
os.remove(john_file)
291+
if Configuration.verbose > 1:
292+
Color.pl('{!} {O}Cleaned up temporary john file after error{W}')
293+
except OSError:
294+
pass
295+
raise
245296

246297
def get_pmkid_hash(self, pcapng_file):
247298
if os.path.exists(self.pmkid_file):
@@ -283,11 +334,15 @@ def extract_all_pmkids(pcapng_file):
283334
Returns:
284335
List of dicts: [{'bssid': str, 'essid': str, 'hash': str}, ...]
285336
"""
286-
temp_hash_file = Configuration.temp('all_pmkids.22000')
287-
288-
# Remove temp file if it exists
289-
if os.path.exists(temp_hash_file):
290-
os.remove(temp_hash_file)
337+
import tempfile
338+
339+
# Create secure temporary file with proper permissions (0600)
340+
# Using NamedTemporaryFile with delete=False to prevent race conditions
341+
with tempfile.NamedTemporaryFile(mode='w', suffix='.22000', delete=False, prefix='wifite_pmkids_') as tmp:
342+
temp_hash_file = tmp.name
343+
344+
# Verify file permissions are secure (0600)
345+
os.chmod(temp_hash_file, 0o600)
291346

292347
# Check if pcapng file exists
293348
if not os.path.exists(pcapng_file):
@@ -367,7 +422,10 @@ def extract_all_pmkids(pcapng_file):
367422
if os.path.exists(temp_hash_file):
368423
try:
369424
os.remove(temp_hash_file)
370-
except:
371-
pass
425+
if Configuration.verbose > 1:
426+
Color.pl('{!} {O}Cleaned up temporary PMKID hash file{W}')
427+
except OSError as e:
428+
if Configuration.verbose > 0:
429+
Color.pl('{!} {O}Warning: Could not remove PMKID hash file: %s{W}' % str(e))
372430

373431
return pmkids

wifite/tools/hostapd.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,6 @@ def get_connected_clients(self):
365365

366366
def __del__(self):
367367
"""Cleanup on deletion."""
368-
try:
368+
import contextlib
369+
with contextlib.suppress(Exception):
369370
self.cleanup()
370-
except:
371-
pass

0 commit comments

Comments
 (0)