#!/usr/bin/env python3"""auto_crack.pyUsage: auto_crack.py <hash_file> <wordlist>What it does: • Maintains per-<hash,mode_id> tracking in crack_log.json to avoid redundant cracking. • Reads all valid hashes from <hash_file> (ignores empty lines and comments). • Uses `hashcat --show <hash_file>` to enumerate structurally compatible hash modes. • For each candidate mode: – Filters out hashes already attempted with this mode (via log). – Writes remaining hashes to a temporary subset file. – Creates a mode-specific temp potfile and runs Hashcat with: hashcat -m <mode> -a 0 --quiet --potfile-path <pot> <subset_file> <wordlist> – If exit code == 0, reruns Hashcat with `--show` to get cracked results. • Prints “[FOUND]” and the <hash> → '<clear>' for each. • Logs each cracked result with mode, cleartext, and UTC timestamp. – If nothing was cracked, prints “[NOT FOUND]”. – Cleans up temporary potfile and subset file after each mode. • At the end, prints a summary of all newly cracked hashes per mode.Example: ./auto_crack.py hashes.txt rockyou.txt"""import subprocessimport sysimport osimport reimport tempfileimport jsonfrom datetime import datetime, timezoneLOG_FILE = "crack_log.json"def validate_args(): if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} <hash_file> <wordlist>") sys.exit(1) hash_file = sys.argv[1] wordlist = sys.argv[2] if not os.path.isfile(hash_file) or os.path.getsize(hash_file) == 0: print(f"Error: Hash file '{hash_file}' does not exist or is empty.") sys.exit(1) if not os.path.isfile(wordlist) or os.path.getsize(wordlist) == 0: print(f"Error: Wordlist '{wordlist}' does not exist or is empty.") sys.exit(1) return hash_file, wordlistdef load_log_entries(): """ Load existing log entries from crack_log.json, each entry is { "hash": ..., "mode_id": ..., "clear": ..., "timestamp": ... }. Returns a set of (hash, mode_id) tuples, and the full list for appending. """ if os.path.isfile(LOG_FILE): try: with open(LOG_FILE, "r") as f: data = json.load(f) seen = {(entry["hash"], entry["mode_id"]) for entry in data} return seen, data except Exception: pass return set(), []def append_log(entries_list, entry): """ Append a single entry dict to entries_list and write out to LOG_FILE. """ entries_list.append(entry) with open(LOG_FILE, "w") as f: json.dump(entries_list, f, indent=2)def get_all_hashes(hash_file): """ Read all non-empty, non-comment lines from hash_file. """ hashes = [] with open(hash_file, "r") as f: for line in f: h = line.strip() if h and not h.startswith("#"): hashes.append(h) return hashesdef get_candidate_modes(hash_file): """ Runs: hashcat --show <hash_file> Parses its stdout to extract lines like: 1400 | SHA2-256 | Raw Hash 17400 | SHA3-256 | Raw Hash … Returns a list of (mode_id, mode_name) tuples. """ try: proc = subprocess.run( ["hashcat", "--show", hash_file], capture_output=True, text=True ) except FileNotFoundError: print("Error: `hashcat` not found in PATH.") sys.exit(1) output = proc.stdout.splitlines() candidate_modes = [] pattern = re.compile(r'^\s*(\d+)\s*\|\s*(.+?)\s*\|\s*(.+)$') for line in output: match = pattern.match(line) if match: mode_id = match.group(1).strip() mode_name = match.group(2).strip() candidate_modes.append((mode_id, mode_name)) return candidate_modesdef run_hashcat_with_pot(mode, subset_file, wordlist, potpath): """ Runs: hashcat -m <mode> -a 0 --quiet --potfile-path <potpath> <subset_file> <wordlist> Returns True if exit-code == 0 (i.e. cracked ≥1 hash), else False. """ proc = subprocess.run( ["hashcat", "-m", mode, "-a", "0", "--quiet", "--potfile-path", potpath, subset_file, wordlist], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) return (proc.returncode == 0)def extract_cracked_from_pot(mode, subset_file, wordlist, potpath): """ After a successful run, call: hashcat -m <mode> -a 0 --quiet --potfile-path <potpath> --show <subset_file> <wordlist> Returns a list of (hash, cleartext) tuples that this mode cracked. """ show_proc = subprocess.run( ["hashcat", "-m", mode, "-a", "0", "--quiet", "--potfile-path", potpath, "--show", subset_file, wordlist], capture_output=True, text=True ) lines = show_proc.stdout.strip().splitlines() pairs = [] for line in lines: if ":" in line: h, clear = line.split(":", 1) pairs.append((h, clear)) return pairsdef main(): hash_file, wordlist = validate_args() # Load existing log already_cracked, log_entries = load_log_entries() print(f"→ Enumerating possible modes with `hashcat --show {hash_file}` …") candidates = get_candidate_modes(hash_file) if not candidates: print("No candidate modes found. Either your hash_file is invalid or Hashcat's output format changed.") sys.exit(1) # Read all hashes once all_hashes = get_all_hashes(hash_file) print(f"→ Found {len(candidates)} candidate mode(s):") for mode_id, mode_name in candidates: print(f" • {mode_id} ({mode_name})") cracked_summary = [] # list of (mode_id, mode_name, [(hash, clear), ...]) print("\n→ Starting isolated crack attempts, one subset per mode …\n") for mode_id, mode_name in candidates: # Build subset of pending hashes for this mode pending = [h for h in all_hashes if (h, mode_id) not in already_cracked] if not pending: print(f"→ Mode {mode_id:<6} ({mode_name}) … [SKIPPED] no new hashes for this mode. delete crack_log.json if you wish to still run this mode ig??") continue print(f"→ Mode {mode_id:<6} ({mode_name}) … ", end="", flush=True) # Write pending hashes to a temporary subset file tf = tempfile.NamedTemporaryFile(prefix="hc_subset_", delete=False, mode="w") for h in pending: tf.write(h + "\n") tf.flush() tf.close() subset_file = tf.name # 1) Create a unique temp potfile for this mode tmp_pot = tempfile.NamedTemporaryFile(prefix="hc_pot_", delete=False) tmp_pot_path = tmp_pot.name tmp_pot.close() # 2) Run hashcat on the subset cracked = run_hashcat_with_pot(mode_id, subset_file, wordlist, tmp_pot_path) if cracked: # 3) Extract exactly what this mode cracked pairs = extract_cracked_from_pot(mode_id, subset_file, wordlist, tmp_pot_path) print("[FOUND]") for h, clear in pairs: print(f" {h} → '{clear}'") # Log each newly cracked <hash, mode_id, clear, timestamp> entry = { "hash": h, "mode_id": mode_id, "clear": clear, "timestamp": datetime.now(timezone.utc).isoformat() } append_log(log_entries, entry) cracked_summary.append((mode_id, mode_name, pairs)) else: print("[NOT FOUND]") # 4) Cleanup try: os.remove(tmp_pot_path) except OSError: pass try: os.remove(subset_file) except OSError: pass # Final summary print("\n=== Summary of All Modes That Actually Cracked Something ===") if not cracked_summary: print("No modes cracked any new hashes. Check your wordlist and/or hash file.") sys.exit(0) for mode_id, mode_name, pairs in cracked_summary: # Note: pairs may repeat across modes, but we only log newly cracked ones above. print(f"\n→ Mode {mode_id} ({mode_name}) cracked {len(pairs)} hash(es):") for h, clear in pairs: print(f" {h} → '{clear}'") print("\nAll done :D") sys.exit(0)if __name__ == "__main__": main()# . . . . . . . . . + .# . . : . .. :. .___---------___.# . . . . :.:. _".^ .^ ^. '.. :"-_. .# . : . . .:../: . .^ :.:\.# . . :: +. :.:/: . . . . . .:\# . : . . _ :::/: . ^ . . .:\# .. . . . - : :.:./. . .:\# . . . :..|: . . ^. .:|# . . : : ..|| . . . !:|# . . . . ::. ::\( . :)/# . . : . : .:.|. ###### .#######::|# :.. . :- : .: ::|.####### ..########:|# . . . .. . .. :\ ######## :######## :/# . .+ :: : -.:\ ######## . ########.:/# . .+ . . . . :.:\. ####### #######..:/# :: . . . . ::.:..:.\ . . ..:/# . . . .. : -::::.\. | | . .:/# . : . . .-:.":.::.\ ..:/# . -. . . . .: .:::.:.\. .:/# . . . : : ....::_:..:\ ___. :/# . . . .:. .. . .: :.:.:\ :/# + . . : . ::. :.:. .:.|\ .:/|# . + . . ...:: ..| --.:|# . . . . . . . ... :..:.."( ..)"# . . . : . .: ::/ . .::\
usage:
python3 autoCrack.py hash.txt wordlist.txt
github readme.md:
# auto_crack.pyA Python wrapper for automating Hashcat across multiple hash modes with per-hash-mode tracking and zero overlap. i got lazy during a tryhackme room and decided to vibe code to automate all the hashmode testing. have fun :D## Features:- Automatically detects candidate hash modes using `hashcat --show`.- Cracks each mode in isolation using temporary potfiles.- Tracks which `<hash,mode>` pairs have already been tested using `crack_log.json`.- Efficient: skips hashes already cracked or previously attempted with that mode or if theres a saved log for that hash for that specific mode in `crack_log.json`.- Clean, readable output and full summary of cracked hashes.- Self-cleaning: deletes all temporary files after use.---## Requirements- Python 3.6+- Hashcat installed and accessible in your system's `$PATH`.---## Usage`./auto_crack.py <hash_file> <wordlist>`### Example`./auto_crack.py hashes.txt rockyou.txt`---## How It Works1. Validates the hash file and wordlist.2. Uses `hashcat --show <hash_file>` to identify candidate hash modes.3. For each matching mode: - Skips hashes already cracked or attempted (via `crack_log.json`). - Writes remaining hashes to a temporary subset file. - Runs Hashcat with a unique temporary potfile. - If any hashes are cracked: - Displays each `<hash> → '<plaintext>'`. - Logs mode, hash, plaintext, and timestamp to `crack_log.json`.4. Cleans up all temporary files.5. Displays a summary of all successfully cracked hashes.---## 📂 Log FormatAll cracked results are stored in a local JSON file: `crack_log.json`.Each entry looks like:`{ "hash": "5e884898da28047151...", "mode": "1400", "cleartext": "password123", "timestamp": "2025-06-04T14:33:21Z" }`---## 📝 Notes- Hashes starting with `#` or blank lines are ignored.- If no hashes remain for a mode, it will display `[NOT FOUND] (skipped)`.- Script assumes raw unsalted hash formats unless modes indicate otherwise.---## ❗ Troubleshooting- **“No candidate modes found”**: Make sure your `hash_file` contains properly formatted hashes.- **Hashcat not found**: Ensure Hashcat is installed and executable from your shell.---## 📄 LicenseMIT License – do whatever you want, but don't blame me if you break stuff.