Skip to content

Commit 18c95cb

Browse files
committed
Add directory support for --dict switch
When --dict is given a directory path instead of a file, wifite now collects all files in the directory and uses them as wordlists for cracking. Each wordlist is tried sequentially until a password is found or all wordlists are exhausted. Changes: - config.py: Add wordlists list, handle directory paths in --dict - args.py: Update help text to indicate file/directory support - tools: Add optional wordlist parameter to all cracking tools (hashcat, aircrack, john, cowpatty) - attacks: Iterate through multiple wordlists in WPA, PMKID, and PMKID passive attacks - util/crack.py: Support directory input in manual crack mode https://claude.ai/code/session_01SLvptkig8m8QXCLeBLFcQ3
1 parent 32ce44b commit 18c95cb

File tree

10 files changed

+177
-96
lines changed

10 files changed

+177
-96
lines changed

wifite/args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,10 +759,10 @@ def _add_wpa_args(self, wpa):
759759
wpa.add_argument('--dict',
760760
action='store',
761761
dest='wordlist',
762-
metavar='[file]',
762+
metavar='[file/directory]',
763763
type=str,
764764
help=Color.s(
765-
'File containing passwords for cracking (default: {G}%s{W})') % self.config.wordlist)
765+
'File or directory containing wordlists for cracking (default: {G}%s{W})') % self.config.wordlist)
766766

767767
wpa.add_argument('--wpadt',
768768
action='store',

wifite/attack/pmkid.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,15 +468,25 @@ def crack_pmkid_file(self, pmkid_file):
468468

469469
key = None
470470
else:
471-
log_info('AttackPMKID', f'Using wordlist: {Configuration.wordlist}')
471+
wordlists_to_try = [wl for wl in Configuration.wordlists if os.path.exists(wl)]
472+
if not wordlists_to_try:
473+
wordlists_to_try = [Configuration.wordlist]
474+
475+
log_info('AttackPMKID', f'Using {len(wordlists_to_try)} wordlist(s)')
472476
if self.view:
473477
self.view.set_attack_type("PMKID Crack")
474-
self.view.add_log(f"Starting PMKID crack with wordlist: {Configuration.wordlist}")
478+
self.view.add_log(f"Starting PMKID crack with {len(wordlists_to_try)} wordlist(s)")
475479
self.view.add_log("Running hashcat...")
476480

477-
Color.clear_entire_line()
478-
Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID using {C}%s{W} ...\n' % Configuration.wordlist)
479-
key = Hashcat.crack_pmkid(pmkid_file)
481+
key = None
482+
for wordlist in wordlists_to_try:
483+
wordlist_name = os.path.basename(wordlist)
484+
Color.clear_entire_line()
485+
Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID using {C}%s{W} ...\n' % wordlist_name)
486+
key = Hashcat.crack_pmkid(pmkid_file, wordlist=wordlist)
487+
if key is not None:
488+
break
489+
Color.pl('{!} {O}%s did not contain password{W}' % wordlist_name)
480490

481491
if key is not None:
482492
log_info('AttackPMKID', f'PMKID cracked successfully! Password: {mask_sensitive(key)}')

wifite/attack/pmkid_passive.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -545,35 +545,43 @@ def crack_captured_pmkids(self):
545545

546546
if not Configuration.wordlist:
547547
Color.pl('{!} {O}No wordlist specified, skipping crack{W}')
548-
Color.pl('{!} {O}Use {C}--dict{O} to specify a wordlist{W}')
548+
Color.pl('{!} {O}Use {C}--dict{O} to specify a wordlist or directory{W}')
549549
return
550-
550+
551+
wordlists_to_try = [wl for wl in Configuration.wordlists if os.path.exists(wl)]
552+
if not wordlists_to_try:
553+
wordlists_to_try = [Configuration.wordlist]
554+
551555
cracked_count = 0
552-
556+
553557
for bssid, pmkid_data in self.captured_pmkids.items():
554558
essid = pmkid_data['essid']
555559
pmkid_file = pmkid_data['file']
556-
560+
557561
Color.pl('')
558562
Color.pl('{+} {C}Cracking PMKID for:{W} {G}%s{W} ({C}%s{W})' % (
559563
essid if essid else '<hidden>',
560564
bssid
561565
))
562-
563-
# Attempt to crack
564-
key = Hashcat.crack_pmkid(pmkid_file, verbose=Configuration.verbose > 0)
565-
566+
567+
# Attempt to crack with each wordlist
568+
key = None
569+
for wordlist in wordlists_to_try:
570+
key = Hashcat.crack_pmkid(pmkid_file, verbose=Configuration.verbose > 0, wordlist=wordlist)
571+
if key:
572+
break
573+
566574
if key:
567575
cracked_count += 1
568576
Color.pl('{+} {G}SUCCESS!{W} Password: {G}%s{W}' % key)
569-
577+
570578
# Save result
571579
from ..model.pmkid_result import CrackResultPMKID
572580
result = CrackResultPMKID(bssid, essid, pmkid_file, key)
573581
result.save()
574582
result.dump()
575583
else:
576-
Color.pl('{!} {R}Failed to crack{W} (password not in wordlist)')
584+
Color.pl('{!} {R}Failed to crack{W} (password not in any wordlist)')
577585

578586
Color.pl('')
579587
Color.pl('{+} {C}Cracking complete:{W} {G}%d{W}/{G}%d{W} PMKIDs cracked' % (

wifite/attack/wpa.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,11 @@ def run(self):
368368
return self._handle_attack_failure(
369369
'{!} {O}Not cracking handshake because wordlist ({R}--dict{O}) is not set'
370370
)
371-
elif not os.path.exists(Configuration.wordlist):
372-
Color.pl('{!} {O}Not cracking handshake because wordlist {R}%s{O} was not found' % Configuration.wordlist)
371+
372+
# Get list of wordlists to try
373+
wordlists_to_try = [wl for wl in Configuration.wordlists if os.path.exists(wl)]
374+
if not wordlists_to_try:
375+
Color.pl('{!} {O}Not cracking handshake because no valid wordlist files were found')
373376
self.success = False
374377
return False
375378

@@ -384,34 +387,41 @@ def run(self):
384387
# as Hashcat mode 22000 (hccapx) is generally preferred over aircrack-ng.
385388
# Aircrack.crack_handshake might be removed or kept for WEP only in future.
386389

387-
wordlist_name = os.path.split(Configuration.wordlist)[-1] if Configuration.wordlist else "default wordlist"
388-
crack_msg = f'Cracking {"WPA3-SAE" if target_is_wpa3_sae else "WPA/WPA2"} Handshake: Running {cracker} with {wordlist_name} wordlist'
389-
390-
Color.pl(f'\n{{+}} {{C}}{crack_msg}{{W}}')
391-
392-
# Update TUI view if available
393-
if self.view:
394-
self.view.add_log(crack_msg)
395-
self.view.update_progress({
396-
'status': f'Cracking with {cracker}...',
397-
'metrics': {
398-
'Cracker': cracker,
399-
'Wordlist': wordlist_name,
400-
'Type': 'WPA3-SAE' if target_is_wpa3_sae else 'WPA/WPA2'
401-
}
402-
})
390+
key = None
391+
for idx, wordlist in enumerate(wordlists_to_try):
392+
wordlist_name = os.path.split(wordlist)[-1]
393+
crack_msg = f'Cracking {"WPA3-SAE" if target_is_wpa3_sae else "WPA/WPA2"} Handshake: Running {cracker} with {wordlist_name} wordlist ({idx + 1}/{len(wordlists_to_try)})'
403394

404-
try:
405-
key = Hashcat.crack_handshake(handshake, target_is_wpa3_sae, show_command=Configuration.verbose > 1)
406-
except ValueError as e: # Catch errors from hash file generation (e.g. bad capture)
407-
error_msg = f"Error during hash file generation for cracking: {e}"
408-
Color.pl(f"[!] {error_msg}")
395+
Color.pl(f'\n{{+}} {{C}}{crack_msg}{{W}}')
396+
397+
# Update TUI view if available
409398
if self.view:
410-
self.view.add_log(error_msg)
411-
key = None
399+
self.view.add_log(crack_msg)
400+
self.view.update_progress({
401+
'status': f'Cracking with {cracker}...',
402+
'metrics': {
403+
'Cracker': cracker,
404+
'Wordlist': wordlist_name,
405+
'Type': 'WPA3-SAE' if target_is_wpa3_sae else 'WPA/WPA2'
406+
}
407+
})
408+
409+
try:
410+
key = Hashcat.crack_handshake(handshake, target_is_wpa3_sae, show_command=Configuration.verbose > 1, wordlist=wordlist)
411+
except ValueError as e: # Catch errors from hash file generation (e.g. bad capture)
412+
error_msg = f"Error during hash file generation for cracking: {e}"
413+
Color.pl(f"[!] {error_msg}")
414+
if self.view:
415+
self.view.add_log(error_msg)
416+
key = None
417+
418+
if key is not None:
419+
break
420+
421+
Color.pl(f'{{!}} {{O}}{wordlist_name} did not contain password{{W}}')
412422

413423
if key is None:
414-
fail_msg = f"Failed to crack handshake: {wordlist_name} did not contain password"
424+
fail_msg = 'Failed to crack handshake: password not found in any wordlist'
415425
Color.pl(f"{{!}} {{R}}{fail_msg}{{W}}")
416426
if self.view:
417427
self.view.add_log(fail_msg)

wifite/config.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class Configuration:
8484
wep_restart_aircrack = None
8585
wep_restart_stale_ivs = None
8686
wordlist = None
87+
wordlists = []
8788
wpa_attack_timeout = None
8889
wpa_deauth_timeout = None
8990
wpa_filter = None
@@ -232,7 +233,8 @@ def initialize(cls, load_interface=True):
232233
# Default dictionary for cracking
233234
cls.cracked_file = 'cracked.json'
234235
cls.wordlist = None
235-
wordlists = [
236+
cls.wordlists = []
237+
default_wordlists = [
236238
'./wordlist-probable.txt', # Local file (ran from cloned repo)
237239
'/usr/share/dict/wordlist-probable.txt', # setup.py with prefix=/usr
238240
'/usr/local/share/dict/wordlist-probable.txt', # setup.py with prefix=/usr/local
@@ -241,9 +243,10 @@ def initialize(cls, load_interface=True):
241243
'/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
242244
'/usr/share/wordlists/fern-wifi/common.txt'
243245
]
244-
for wlist in wordlists:
246+
for wlist in default_wordlists:
245247
if os.path.exists(wlist):
246248
cls.wordlist = wlist
249+
cls.wordlists = [wlist]
247250
break
248251

249252
if os.path.isfile('/usr/share/ieee-data/oui.txt'):
@@ -771,15 +774,31 @@ def parse_wpa_args(cls, args):
771774
if args.wordlist:
772775
if not os.path.exists(args.wordlist):
773776
cls.wordlist = None
777+
cls.wordlists = []
774778
Color.pl('{+} {C}option:{O} wordlist {R}%s{O} was not found, wifite will NOT attempt to crack '
775779
'handshakes' % args.wordlist)
776780
elif os.path.isfile(args.wordlist):
777781
cls.wordlist = args.wordlist
782+
cls.wordlists = [args.wordlist]
778783
Color.pl('{+} {C}option:{W} using wordlist {G}%s{W} for cracking' % args.wordlist)
779784
elif os.path.isdir(args.wordlist):
780-
cls.wordlist = None
781-
Color.pl('{+} {C}option:{O} wordlist {R}%s{O} is a directory, not a file. Wifite will NOT attempt to '
782-
'crack handshakes' % args.wordlist)
785+
# Collect all files in the directory as wordlists
786+
dict_dir = args.wordlist
787+
files = sorted([
788+
os.path.join(dict_dir, f)
789+
for f in os.listdir(dict_dir)
790+
if os.path.isfile(os.path.join(dict_dir, f))
791+
])
792+
if files:
793+
cls.wordlists = files
794+
cls.wordlist = files[0]
795+
Color.pl('{+} {C}option:{W} using {G}%d{W} wordlist(s) from directory {G}%s{W} for cracking'
796+
% (len(files), dict_dir))
797+
else:
798+
cls.wordlist = None
799+
cls.wordlists = []
800+
Color.pl('{+} {C}option:{O} wordlist directory {R}%s{O} contains no files. Wifite will NOT '
801+
'attempt to crack handshakes' % dict_dir)
783802

784803
if args.wpa_deauth_timeout:
785804
cls.wpa_deauth_timeout = args.wpa_deauth_timeout

wifite/tools/aircrack.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,17 @@ def __del__(self):
8181
os.remove(self.cracked_file)
8282

8383
@staticmethod
84-
def crack_handshake(handshake, show_command=False):
84+
def crack_handshake(handshake, show_command=False, wordlist=None):
8585
from ..util.color import Color
8686
from ..util.timer import Timer
8787
'''Tries to crack a handshake. Returns WPA key if found, otherwise None.'''
8888

89+
wordlist = wordlist or Configuration.wordlist
8990
key_file = Configuration.temp('wpakey.txt')
9091
command = [
9192
'aircrack-ng',
9293
'-a', '2',
93-
'-w', Configuration.wordlist,
94+
'-w', wordlist,
9495
'--bssid', handshake.bssid,
9596
'-l', key_file,
9697
handshake.capfile

wifite/tools/cowpatty.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ class Cowpatty(Dependency):
1414
dependency_url = 'https://tools.kali.org/wireless-attacks/cowpatty'
1515

1616
@staticmethod
17-
def crack_handshake(handshake, show_command=False):
18-
# Crack john file
17+
def crack_handshake(handshake, show_command=False, wordlist=None):
18+
wordlist = wordlist or Configuration.wordlist
1919
command = [
2020
'cowpatty',
21-
'-f', Configuration.wordlist,
21+
'-f', wordlist,
2222
'-r', handshake.capfile,
2323
'-s', handshake.essid
2424
]

wifite/tools/hashcat.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,22 @@ def should_use_force():
2323
return 'No devices found/left' in stderr or 'Unstable OpenCL driver detected!' in stderr
2424

2525
@staticmethod
26-
def crack_handshake(handshake_obj, target_is_wpa3_sae, show_command=False):
26+
def crack_handshake(handshake_obj, target_is_wpa3_sae, show_command=False, wordlist=None):
2727
"""
2828
Cracks a handshake.
2929
handshake_obj: A Handshake object (should have .capfile attribute)
3030
target_is_wpa3_sae: Boolean indicating if the target uses WPA3-SAE
31+
wordlist: Path to wordlist file (uses Configuration.wordlist if None)
3132
"""
3233
hash_file = HcxPcapngTool.generate_hash_file(handshake_obj, target_is_wpa3_sae, show_command=show_command)
3334

3435
# If hash file generation failed due to capture quality, fall back to aircrack-ng
3536
if hash_file is None:
3637
Color.pl('{!} {O}Falling back to aircrack-ng for cracking{W}')
3738
from .aircrack import Aircrack
38-
return Aircrack.crack_handshake(handshake_obj, show_command=show_command)
39+
return Aircrack.crack_handshake(handshake_obj, show_command=show_command, wordlist=wordlist)
3940

41+
wordlist = wordlist or Configuration.wordlist
4042
key = None
4143
try:
4244
# Mode 22000 supports both WPA/WPA2 and WPA3-SAE (WPA-PBKDF2-PMKID+EAPOL)
@@ -52,7 +54,7 @@ def crack_handshake(handshake_obj, target_is_wpa3_sae, show_command=False):
5254
'--quiet',
5355
'-m', hashcat_mode,
5456
hash_file,
55-
Configuration.wordlist
57+
wordlist
5658
]
5759
if Hashcat.should_use_force():
5860
command.append('--force')
@@ -97,12 +99,13 @@ def crack_handshake(handshake_obj, target_is_wpa3_sae, show_command=False):
9799
Color.pl('{!} {O}Warning: Could not remove hash file: %s{W}' % str(e))
98100

99101
@staticmethod
100-
def crack_pmkid(pmkid_file, verbose=False):
102+
def crack_pmkid(pmkid_file, verbose=False, wordlist=None):
101103
"""
102104
Cracks a given pmkid_file using the PMKID/WPA2 attack (-m 22000)
103105
Returns:
104106
Key (str) if found; `None` if not found.
105107
"""
108+
wordlist = wordlist or Configuration.wordlist
106109

107110
# Run hashcat once normally, then with --show if it failed
108111
# To catch cases where the password is already in the pot file.
@@ -113,7 +116,7 @@ def crack_pmkid(pmkid_file, verbose=False):
113116
'-m', '22000', # WPA-PMKID-PBKDF2
114117
'-a', '0', # Wordlist attack-mode
115118
pmkid_file,
116-
Configuration.wordlist,
119+
wordlist,
117120
'-w', '3'
118121
]
119122
if Hashcat.should_use_force():

wifite/tools/john.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ class John(Dependency):
1717
dependency_url = 'https://www.openwall.com/john/'
1818

1919
@staticmethod
20-
def crack_handshake(handshake, show_command=False):
20+
def crack_handshake(handshake, show_command=False, wordlist=None):
2121
john_file = HcxPcapngTool.generate_john_file(handshake, show_command=show_command)
2222

23+
wordlist = wordlist or Configuration.wordlist
2324
key = None
2425
try:
2526
# Use `john --list=formats` to find if OpenCL or CUDA is supported.
@@ -32,7 +33,7 @@ def crack_handshake(handshake, show_command=False):
3233
john_format = 'wpapsk'
3334

3435
# Crack john file
35-
command = ['john', f'--format={john_format}', f'--wordlist={Configuration.wordlist}', john_file]
36+
command = ['john', f'--format={john_format}', f'--wordlist={wordlist}', john_file]
3637
if show_command:
3738
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
3839
process = Process(command)

0 commit comments

Comments
 (0)