Add directory support for --dict switch#455
Conversation
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
There was a problem hiding this comment.
Pull request overview
Adds support for passing a directory to --dict so Wifite can iterate through multiple wordlists sequentially during cracking, improving usability for users who maintain wordlists as folders.
Changes:
- Extend configuration/CLI handling to accept
--dictas either a file or directory (expanding directories into a sorted list of files). - Update cracking flows (WPA + PMKID + manual crack helper) to try multiple wordlists until a key is found.
- Add optional
wordlistparameter to cracking tool wrappers (hashcat/aircrack/john/cowpatty) so callers can override the active wordlist per attempt.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| wifite/config.py | Adds Configuration.wordlists and expands --dict directory inputs into multiple wordlists. |
| wifite/args.py | Updates --dict help/metavar to reflect file-or-directory behavior. |
| wifite/util/crack.py | Manual cracking mode now accepts a wordlist directory and iterates through expanded files. |
| wifite/attack/wpa.py | WPA cracking now iterates across multiple wordlists with per-wordlist progress/logging. |
| wifite/attack/pmkid.py | PMKID cracking now iterates through multiple wordlists. |
| wifite/attack/pmkid_passive.py | Passive PMKID cracking now iterates through multiple wordlists. |
| wifite/tools/hashcat.py | Adds wordlist override to handshake + PMKID cracking (and propagates to aircrack fallback). |
| wifite/tools/aircrack.py | Adds wordlist override to handshake cracking. |
| wifite/tools/john.py | Adds wordlist override to handshake cracking. |
| wifite/tools/cowpatty.py | Adds wordlist override to handshake cracking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| wep_restart_aircrack = None | ||
| wep_restart_stale_ivs = None | ||
| wordlist = None | ||
| wordlists = [] |
There was a problem hiding this comment.
wordlists is defined as a mutable class attribute ([]). This can lead to surprising shared state across imports/runs in long-lived processes. Prefer initializing it to None at the class level (like most other config fields) and assigning a fresh list in initialize() / arg parsing.
| wordlists = [] | |
| wordlists = None |
| files = sorted([ | ||
| os.path.join(dict_dir, f) | ||
| for f in os.listdir(dict_dir) | ||
| if os.path.isfile(os.path.join(dict_dir, f)) | ||
| ]) | ||
| if files: | ||
| cls.wordlists = files | ||
| cls.wordlist = files[0] | ||
| Color.pl('{+} {C}option:{W} using {G}%d{W} wordlist(s) from directory {G}%s{W} for cracking' | ||
| % (len(files), dict_dir)) | ||
| else: | ||
| cls.wordlist = None | ||
| cls.wordlists = [] | ||
| Color.pl('{+} {C}option:{O} wordlist directory {R}%s{O} contains no files. Wifite will NOT ' | ||
| 'attempt to crack handshakes' % dict_dir) |
There was a problem hiding this comment.
Directory wordlist expansion uses os.listdir(dict_dir) without handling OSError (e.g., permission denied, transient IO issues). That can crash argument parsing even though the path exists. Wrap the directory scan in try/except OSError and emit a user-facing message (and disable cracking) on failure.
| files = sorted([ | |
| os.path.join(dict_dir, f) | |
| for f in os.listdir(dict_dir) | |
| if os.path.isfile(os.path.join(dict_dir, f)) | |
| ]) | |
| if files: | |
| cls.wordlists = files | |
| cls.wordlist = files[0] | |
| Color.pl('{+} {C}option:{W} using {G}%d{W} wordlist(s) from directory {G}%s{W} for cracking' | |
| % (len(files), dict_dir)) | |
| else: | |
| cls.wordlist = None | |
| cls.wordlists = [] | |
| Color.pl('{+} {C}option:{O} wordlist directory {R}%s{O} contains no files. Wifite will NOT ' | |
| 'attempt to crack handshakes' % dict_dir) | |
| try: | |
| files = sorted([ | |
| os.path.join(dict_dir, f) | |
| for f in os.listdir(dict_dir) | |
| if os.path.isfile(os.path.join(dict_dir, f)) | |
| ]) | |
| except OSError as e: | |
| cls.wordlist = None | |
| cls.wordlists = [] | |
| Color.pl('{+} {C}option:{O} could not read wordlist directory {R}%s{O}: %s. Wifite will NOT ' | |
| 'attempt to crack handshakes' % (dict_dir, e)) | |
| else: | |
| if files: | |
| cls.wordlists = files | |
| cls.wordlist = files[0] | |
| Color.pl('{+} {C}option:{W} using {G}%d{W} wordlist(s) from directory {G}%s{W} for cracking' | |
| % (len(files), dict_dir)) | |
| else: | |
| cls.wordlist = None | |
| cls.wordlists = [] | |
| Color.pl('{+} {C}option:{O} wordlist directory {R}%s{O} contains no files. Wifite will NOT ' | |
| 'attempt to crack handshakes' % dict_dir) |
| files = sorted([ | ||
| os.path.join(user_input, f) | ||
| for f in os.listdir(user_input) |
There was a problem hiding this comment.
Directory wordlist expansion uses os.listdir(user_input) without handling OSError (e.g., permission denied). Since this is interactive mode, an unhandled exception will drop the user back to a traceback. Wrap the directory scan in try/except OSError and show a clear error before exiting.
| files = sorted([ | |
| os.path.join(user_input, f) | |
| for f in os.listdir(user_input) | |
| try: | |
| entries = os.listdir(user_input) | |
| except OSError as e: | |
| Color.pl('{!} {R}Failed to read directory {O}%s{R}: %s. Exiting.' % (user_input, e)) | |
| return | |
| files = sorted([ | |
| os.path.join(user_input, f) | |
| for f in entries |
| Color.pl('{!} {O}Not cracking handshake because wordlist {R}%s{O} was not found' % Configuration.wordlist) | ||
|
|
||
| # Get list of wordlists to try | ||
| wordlists_to_try = [wl for wl in Configuration.wordlists if os.path.exists(wl)] |
There was a problem hiding this comment.
wordlists_to_try is derived only from Configuration.wordlists. If Configuration.wordlist is set but Configuration.wordlists is empty (e.g., session restore sets wordlist but not wordlists), this will incorrectly skip cracking with “no valid wordlist files”. Consider falling back to [Configuration.wordlist] when Configuration.wordlists is empty, and filtering with os.path.isfile() (not just exists()).
| wordlists_to_try = [wl for wl in Configuration.wordlists if os.path.exists(wl)] | |
| base_wordlists = Configuration.wordlists if getattr(Configuration, 'wordlists', None) else [Configuration.wordlist] | |
| wordlists_to_try = [wl for wl in base_wordlists if os.path.isfile(wl)] |
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: