diff --git a/.gitignore b/.gitignore index c1d57d7..d034f39 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ __pycache__/ hate_crack/__pycache__/ tests/__pycache__/ uv.lock +build/ +hate_crack.egg-info/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 654e327..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin -SCRIPT ?= hate_crack -WRAPPER_PATH := $(BINDIR)/$(SCRIPT) -PROJECT_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) -ENTRYPOINT := $(PROJECT_ROOT)/hate_crack/hate_crack.py -UV ?= uv - -.PHONY: install clean - -install: - @mkdir -p $(BINDIR) - @printf '%s\n' "#!/bin/sh" "" "$(UV) run $(ENTRYPOINT) \"\$$@\"" > $(WRAPPER_PATH) - @chmod +x $(WRAPPER_PATH) - @echo "Installed $(WRAPPER_PATH)" - -clean: - @rm -f $(WRAPPER_PATH) - @echo "Removed $(WRAPPER_PATH)" diff --git a/hate_crack/__main__.py b/hate_crack/__main__.py new file mode 100644 index 0000000..c9ce9ee --- /dev/null +++ b/hate_crack/__main__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +import sys + +from .hate_crack import cli_main + + +def main() -> int: + return cli_main() or 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/hate_crack/_root.py b/hate_crack/_root.py new file mode 100755 index 0000000..89a157d --- /dev/null +++ b/hate_crack/_root.py @@ -0,0 +1,2220 @@ +class HashviewAPI: + # ...existing code... + def upload_wordlist(self): + """Interactive method to upload a custom wordlist to Hashview.""" + print("\n" + "="*60) + print("WORDLIST UPLOAD") + print("="*60) + print("Select a wordlist file to upload to Hashview.") + print("After upload, you'll need to:") + print(" 1. Create a task in Hashview using this wordlist") + print(" 2. Manually add the task to this job via web interface") + print("\nPress TAB to autocomplete file paths.") + print("="*60) + + wordlist_path = select_file_with_autocomplete( + "Enter path to wordlist file" + ) + + uploaded_wordlist_id = None + wordlist_name = None + if wordlist_path and os.path.isfile(wordlist_path): + # Ask for wordlist name + default_name = os.path.basename(wordlist_path) + wordlist_name = input(f"\nEnter wordlist name (default: {default_name}): ").strip() or default_name + try: + # Upload the wordlist + upload_result = self.upload_wordlist(wordlist_path, wordlist_name) + print(f"\n✓ Success: {upload_result.get('msg', 'Wordlist uploaded')}") + if 'wordlist_id' in upload_result: + uploaded_wordlist_id = upload_result['wordlist_id'] + print(f" Wordlist ID: {uploaded_wordlist_id}") + print(f" Wordlist Name: {wordlist_name}") + except Exception as e: + print(f"\n✗ Error uploading wordlist: {str(e)}") + print("Continuing with job creation...") + else: + print("\n✗ No valid wordlist file selected.") + print("Continuing with job creation...") + return uploaded_wordlist_id, wordlist_name +# Methodology provided by Martin Bos (pure_hate) - https://www.trustedsec.com/team/martin-bos/ +# Original script created by Larry Spohn (spoonman) - https://www.trustedsec.com/team/larry-spohn/ +# Python refactoring and general fixing, Justin Bollinger (bandrel) - https://www.trustedsec.com/team/justin-bollinger/ +# Hashview integration by Justin Bollinger (bandrel) and Claude Sonnet 4.5 +# special thanks to hans for all his hard work on hashview and creating APIs for us to use + +# Load config before anything that needs hashview_url/hashview_api_key + +import sys +import os +import json +import shutil +import logging + +#!/usr/bin/env python3 + +try: + import requests + REQUESTS_AVAILABLE = True +except Exception: + requests = None + REQUESTS_AVAILABLE = False + +# Ensure project root is on sys.path so package imports work when loaded via spec. +_root_dir = os.path.dirname(os.path.realpath(__file__)) +if _root_dir not in sys.path: + sys.path.insert(0, _root_dir) + +# Allow submodule imports (hate_crack.*) even when this file is imported as a module. +_pkg_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "hate_crack") +if os.path.isdir(_pkg_dir): + __path__ = [_pkg_dir] + if "__spec__" in globals() and __spec__ is not None: + __spec__.submodule_search_locations = __path__ + + +hate_path = os.path.dirname(os.path.realpath(__file__)) +if not os.path.isfile(hate_path + '/config.json'): + print('Initializing config.json from config.json.example') + shutil.copy(hate_path + '/config.json.example', hate_path + '/config.json') + +with open(hate_path + '/config.json') as config: + config_parser = json.load(config) + +with open(hate_path + '/config.json.example') as defaults: + default_config = json.load(defaults) + +try: + hashview_url = config_parser['hashview_url'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hashview_url = default_config.get('hashview_url', 'https://localhost:8443') + +try: + hashview_api_key = config_parser['hashview_api_key'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hashview_api_key = default_config.get('hashview_api_key', '') + +SKIP_INIT = os.environ.get("HATE_CRACK_SKIP_INIT") == "1" + +logger = logging.getLogger("hate_crack") +if not logger.handlers: + logger.addHandler(logging.NullHandler()) + + + + +def ensure_binary(binary_path, build_dir=None, name=None): + if not os.path.isfile(binary_path) or not os.access(binary_path, os.X_OK): + if build_dir: + print(f'Attempting to build {name or binary_path} via make in {build_dir}...') + try: + subprocess.run(['make'], cwd=build_dir, check=True) + print(f'Successfully ran make in {build_dir}.') + except Exception as e: + print(f'Error running make in {build_dir}: {e}') + print('Please ensure build tools are installed and try again.') + quit(1) + if not os.path.isfile(binary_path) or not os.access(binary_path, os.X_OK): + print(f'Error: {name or binary_path} still not found or not executable at {binary_path} after make.') + quit(1) + else: + print(f'Error: {name or binary_path} not found or not executable at {binary_path}.') + quit(1) + return binary_path + + + +import os +import random +import re +import json +import binascii +import shutil +import readline +import glob +import subprocess +import argparse + +from hate_crack.api import fetch_all_weakpass_wordlists_multithreaded, download_torrent_file, weakpass_wordlist_menu +from hate_crack.api import HashviewAPI +from hate_crack.api import ( + download_all_weakpass_torrents, + download_hashes_from_hashview, + download_hashmob_wordlists, + download_hashmob_rules, + download_weakpass_torrent, + extract_with_7z, +) +from hate_crack.cli import ( + add_common_args, + resolve_path, + setup_logging, +) + +hcatPath = config_parser['hcatPath'] +hcatBin = config_parser['hcatBin'] +hcatTuning = config_parser['hcatTuning'] +hcatWordlists = config_parser['hcatWordlists'] +hcatOptimizedWordlists = config_parser['hcatOptimizedWordlists'] +hcatRules = [] + +try: + rulesDirectory = config_parser['rules_directory'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + rulesDirectory = default_config.get('rules_directory') +if not rulesDirectory: + rulesDirectory = os.path.join(hcatPath, "rules") if hcatPath else os.path.join(hate_path, "rules") +rulesDirectory = os.path.expanduser(rulesDirectory) +if not os.path.isabs(rulesDirectory): + rulesDirectory = os.path.join(hate_path, rulesDirectory) + +try: + maxruntime = config_parser['bandrelmaxruntime'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + maxruntime = default_config['bandrelmaxruntime'] + +try: + bandrelbasewords = config_parser['bandrel_common_basedwords'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + bandrelbasewords = default_config['bandrel_common_basedwords'] + +try: + + pipal_count = config_parser['pipal_count'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + pipal_count = default_config['pipal_count'] + +try: + pipalPath = config_parser['pipalPath'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + pipalPath = default_config['pipalPath'] + +try: + hcatDictionaryWordlist = config_parser['hcatDictionaryWordlist'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatDictionaryWordlist = default_config['hcatDictionaryWordlist'] +try: + hcatHybridlist = config_parser['hcatHybridlist'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatHybridlist = default_config[e.args[0]] +try: + hcatCombinationWordlist = config_parser['hcatCombinationWordlist'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatCombinationWordlist = default_config[e.args[0]] +try: + hcatMiddleCombinatorMasks = config_parser['hcatMiddleCombinatorMasks'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatMiddleCombinatorMasks = default_config[e.args[0]] +try: + hcatMiddleBaseList = config_parser['hcatMiddleBaseList'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatMiddleBaseList = default_config[e.args[0]] +try: + hcatThoroughCombinatorMasks = config_parser['hcatThoroughCombinatorMasks'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatThoroughCombinatorMasks = default_config[e.args[0]] +try: + hcatThoroughBaseList = config_parser['hcatThoroughBaseList'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatThoroughBaseList = default_config[e.args[0]] +try: + hcatPrinceBaseList = config_parser['hcatPrinceBaseList'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatPrinceBaseList = default_config[e.args[0]] +try: + hcatGoodMeasureBaseList = config_parser['hcatGoodMeasureBaseList'] +except KeyError as e: + print('{0} is not defined in config.json using defaults from config.json.example'.format(e)) + hcatGoodMeasureBaseList = default_config[e.args[0]] + +hcatExpanderBin = "expander.bin" +hcatCombinatorBin = "combinator.bin" +hcatPrinceBin = "pp64.bin" + +def verify_wordlist_dir(directory, wordlist): + if os.path.isfile(wordlist): + return wordlist + elif os.path.isfile(directory + '/' + wordlist): + return directory + '/' + wordlist + else: + print('Invalid path for {0}. Please check configuration and try again.'.format(wordlist)) + response = input(f"Wordlist '{wordlist}' not found. Would you like to download rockyou.txt.gz automatically? (Y/n): ").strip().lower() + if response in ('', 'y', 'yes'): + try: + import urllib.request + rockyou_url = "https://weakpass.com/download/90/rockyou.txt.gz" + dest_path = os.path.join(directory, "rockyou.txt.gz") + print(f"Downloading rockyou.txt.gz to {dest_path} ...") + urllib.request.urlretrieve(rockyou_url, dest_path) + print("Download complete.") + return dest_path + except Exception as e: + print(f"Failed to download rockyou.txt.gz: {e}") + return None + else: + print('Exiting. Please check configuration and try again.') + return None + +def get_rule_path(rule_name, fallback_dir=None): + candidates = [] + if rulesDirectory: + candidates.append(os.path.join(rulesDirectory, rule_name)) + if fallback_dir: + candidates.append(os.path.join(fallback_dir, rule_name)) + for candidate in candidates: + if os.path.isfile(candidate): + return candidate + return candidates[0] if candidates else rule_name + +def ensure_toggle_rule(): + """Ensure toggles-lm-ntlm.rule exists in the configured rules directory.""" + if not rulesDirectory: + return None + target_path = os.path.join(rulesDirectory, "toggles-lm-ntlm.rule") + if os.path.isfile(target_path): + return target_path + source_path = os.path.join(hate_path, "rules", "toggles-lm-ntlm.rule") + try: + os.makedirs(rulesDirectory, exist_ok=True) + if os.path.isfile(source_path): + with open(source_path, "r") as src, open(target_path, "w") as dst: + dst.write(src.read()) + else: + with open(target_path, "w") as dst: + dst.write("l\nu\n") + print(f"[i] Created rule file: {target_path}") + except Exception as e: + print(f"[!] Failed to create toggles-lm-ntlm.rule: {e}") + return target_path + +def cleanup_wordlist_artifacts(): + wordlists_dir = hcatWordlists or os.path.join(hate_path, "wordlists") + if not os.path.isabs(wordlists_dir): + wordlists_dir = os.path.join(hate_path, wordlists_dir) + targets = [hate_path, os.getcwd()] + if wordlists_dir not in targets: + targets.append(wordlists_dir) + + for base in targets: + if not os.path.isdir(base): + continue + for name in os.listdir(base): + path = os.path.join(base, name) + if name.endswith(".out"): + try: + os.remove(path) + except Exception as e: + print(f"[!] Failed to remove output file {path}: {e}") + if base == wordlists_dir and name.endswith(".torrent"): + try: + os.remove(path) + except Exception as e: + print(f"[!] Failed to remove torrent file {path}: {e}") + if base == wordlists_dir and name.endswith(".7z"): + ok = extract_with_7z(path) + if not ok: + try: + os.remove(path) + print(f"[!] Removed failed archive: {path}") + except Exception as e: + print(f"[!] Failed to remove archive {path}: {e}") + +if not SKIP_INIT: + # hashcat biniary checks for systems that install hashcat binary in different location than the rest of the hashcat files + if hcatPath: + candidate = hcatPath.rstrip('/') + '/' + hcatBin + if os.path.isfile(candidate): + hcatBin = candidate + elif os.path.isfile(hcatBin): + pass + else: + print('Invalid path for hashcat binary. Please check configuration and try again.') + quit(1) + else: + # No hcatPath set, just use hcatBin (should be in PATH) + if shutil.which(hcatBin) is None: + print('Hashcat binary not found in PATH. Please check configuration and try again.') + quit(1) + + # Verify hashcat-utils binaries exist and work + hashcat_utils_path = hate_path + '/hashcat-utils/bin' + required_binaries = [ + (hcatExpanderBin, 'expander'), + (hcatCombinatorBin, 'combinator'), + ] + + for binary, name in required_binaries: + binary_path = hashcat_utils_path + '/' + binary + ensure_binary(binary_path, build_dir=os.path.join(hate_path, 'hashcat-utils'), name=name) + # Test binary execution + try: + test_result = subprocess.run( + [binary_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=2 + ) + # Binary should show usage and exit with error code (that's expected) + # If we get here without exception, the binary is executable + except subprocess.TimeoutExpired: + # Timeout is fine - means binary is running + pass + except Exception as e: + print(f'Error: {name} binary at {binary_path} failed to execute: {e}') + print('The binary may be compiled for the wrong architecture.') + print('Try recompiling hashcat-utils for your system.') + quit(1) + + # Verify princeprocessor binary + prince_path = hate_path + '/princeprocessor/' + hcatPrinceBin + try: + ensure_binary(prince_path, build_dir=os.path.join(hate_path, 'princeprocessor'), name='PRINCE') + except SystemExit: + print('PRINCE attacks will not be available.') + + #verify and convert wordlists to fully qualified paths + hcatMiddleBaseList = verify_wordlist_dir(hcatWordlists, hcatMiddleBaseList) + hcatThoroughBaseList = verify_wordlist_dir(hcatWordlists, hcatThoroughBaseList) + hcatPrinceBaseList = verify_wordlist_dir(hcatWordlists, hcatPrinceBaseList) + hcatGoodMeasureBaseList = verify_wordlist_dir(hcatWordlists, hcatGoodMeasureBaseList) + for x in range(len(hcatDictionaryWordlist)): + hcatDictionaryWordlist[x] = verify_wordlist_dir(hcatWordlists, hcatDictionaryWordlist[x]) + for x in range(len(hcatHybridlist)): + hcatHybridlist[x] = verify_wordlist_dir(hcatWordlists, hcatHybridlist[x]) + hcatCombinationWordlist[0] = verify_wordlist_dir(hcatWordlists, hcatCombinationWordlist[0]) + hcatCombinationWordlist[1] = verify_wordlist_dir(hcatWordlists, hcatCombinationWordlist[1]) + + +hcatHashCount = 0 +hcatHashCracked = 0 +hcatBruteCount = 0 +hcatDictionaryCount = 0 +hcatMaskCount = 0 +hcatFingerprintCount = 0 +hcatCombinationCount = 0 +hcatHybridCount = 0 +hcatExtraCount = 0 +hcatRecycleCount = 0 +hcatProcess = 0 +debug_mode = False + + +# Sanitize filename for use as hashcat session name +def generate_session_id(): + """Sanitize the hashfile name for use as a hashcat session name + + Hashcat session names can only contain alphanumeric characters, hyphens, and underscores. + This function removes the file extension and replaces problematic characters. + """ + # Get just the filename without path + filename = os.path.basename(hcatHashFile) + # Remove extension + name_without_ext = os.path.splitext(filename)[0] + # Replace any non-alphanumeric chars (except - and _) with underscore + sanitized = re.sub(r'[^a-zA-Z0-9_-]', '_', name_without_ext) + return sanitized + + +# Help +def usage(): + print("usage: python hate_crack.py ") + print("\nThe is attained by running \"{hcatBin} --help\"\n".format(hcatBin=hcatBin)) + print("Example Hashes: http://hashcat.net/wiki/doku.php?id=example_hashes\n") + + +def ascii_art(): + print(r""" + + ___ ___ __ _________ __ + / | \_____ _/ |_ ____ \_ ___ \____________ ____ | | __ +/ ~ \__ \\ __\/ __ \ / \ \/\_ __ \__ \ _/ ___\| |/ / +\ Y // __ \| | \ ___/ \ \____| | \// __ \\ \___| < + \___|_ /(____ /__| \___ >____\______ /|__| (____ /\___ >__|_ \ + \/ \/ \/_____/ \/ \/ \/ \/ + Version 2.0 + """) + + +# File selector with tab autocomplete +def select_file_with_autocomplete(prompt, default=None, allow_multiple=False): + """ + Interactive file selector with tab autocomplete functionality. + + Args: + prompt: The prompt to display to the user + default: Optional default value if user presses Enter + allow_multiple: If True, allows comma-separated file list + + Returns: + String path or list of paths (if allow_multiple=True) + """ + def path_completer(text, state): + """Tab completion function for file paths""" + if not text: + text = './' + + # Expand ~ to home directory + text = os.path.expanduser(text) + + # Handle both absolute and relative paths + if text.startswith('/') or text.startswith('./') or text.startswith('../') or text.startswith('~'): + matches = glob.glob(text + '*') + else: + matches = glob.glob('./' + text + '*') + matches = [m[2:] if m.startswith('./') else m for m in matches] + + # Add trailing slash for directories + matches = [m + '/' if os.path.isdir(m) else m for m in matches] + + try: + return matches[state] + except IndexError: + return None + + # Configure readline for tab completion + readline.set_completer_delims(' \t\n;') + # Disable the "Display all X possibilities?" prompt + try: + readline.parse_and_bind("set completion-query-items -1") + except: + pass + try: + readline.parse_and_bind("tab: complete") + except: + pass + try: + readline.parse_and_bind("bind ^I rl_complete") + except: + pass + readline.set_completer(path_completer) + + # Build prompt + full_prompt = f"\n{prompt}" + if default: + full_prompt += f" (default: {default})" + full_prompt += ": " + + result = input(full_prompt).strip() + + # Handle default + if not result and default: + return default + + # Handle multiple files + if allow_multiple and ',' in result: + files = [f.strip() for f in result.split(',')] + return [os.path.expanduser(f) for f in files if f] + + return os.path.expanduser(result) if result else None + + +# Counts the number of lines in a file +def lineCount(file): + try: + with open(file) as outFile: + count = 0 + for line in outFile: + count = count + 1 + return count + except: + return 0 + +# Brute Force Attack +def hcatBruteForce(hcatHashType, hcatHashFile, hcatMinLen, hcatMaxLen): + global hcatBruteCount + global hcatProcess + hcatProcess = subprocess.Popen( + "{hcbin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out --increment --increment-min={min} " + "--increment-max={max} -a 3 ?a?a?a?a?a?a?a?a?a?a?a?a?a?a {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcbin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + min=hcatMinLen, + max=hcatMaxLen, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatBruteCount = lineCount(hcatHashFile + ".out") + + +# Dictionary Attack +def hcatDictionary(hcatHashType, hcatHashFile): + global hcatDictionaryCount + global hcatProcess + rule_best66 = get_rule_path("best66.rule") + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {optimized_wordlists}/* " + "-r {rule_best66} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hcatHashType=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + optimized_wordlists=hcatOptimizedWordlists, + rule_best66=rule_best66, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + + for wordlist in hcatDictionaryWordlist: + rule_d3ad0ne = get_rule_path("d3ad0ne.rule") + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} " + "-r {rule_d3ad0ne} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hcatHashType=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + hcatWordlist=wordlist, + rule_d3ad0ne=rule_d3ad0ne, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + rule_toxic = get_rule_path("T0XlC.rule") + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} " + "-r {rule_toxic} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hcatHashType=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + hcatWordlist=wordlist, + rule_toxic=rule_toxic, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatDictionaryCount = lineCount(hcatHashFile + ".out") - hcatBruteCount + + +# Quick Dictionary Attack (Optional Chained Rules) +def hcatQuickDictionary(hcatHashType, hcatHashFile, hcatChains, wordlists): + global hcatProcess + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out " + "'{wordlists}' {chains} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hcatHashType=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlists=wordlists, + chains=hcatChains, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + + +# Top Mask Attack +def hcatTopMask(hcatHashType, hcatHashFile, hcatTargetTime): + global hcatMaskCount + global hcatProcess + subprocess.Popen( + "cat {hash_file}.out | cut -d : -f 2 > {hash_file}.working".format( + hash_file=hcatHashFile), shell=True).wait() + hcatProcess = subprocess.Popen( + "{hate_path}/PACK/statsgen.py {hash_file}.working -o {hash_file}.masks".format( + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hate_path}/PACK/maskgen.py {hash_file}.masks --targettime {target_time} --optindex -q --pps 14000000000 " + "--minlength=7 -o {hash_file}.hcmask".format( + hash_file=hcatHashFile, + target_time=hcatTargetTime, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 3 {hash_file}.hcmask {tuning} " + "--potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatMaskCount = lineCount(hcatHashFile + ".out") - hcatHashCracked + + +# Fingerprint Attack +def hcatFingerprint(hcatHashType, hcatHashFile): + global hcatFingerprintCount + global hcatProcess + crackedBefore = lineCount(hcatHashFile + ".out") + crackedAfter = 0 + while crackedBefore != crackedAfter: + crackedBefore = lineCount(hcatHashFile + ".out") + subprocess.Popen("cat {hash_file}.out | cut -d : -f 2 > {hash_file}.working".format( + hash_file=hcatHashFile), shell=True).wait() + hcatProcess = subprocess.Popen( + "{hate_path}/hashcat-utils/bin/{expander_bin} < {hash_file}.working | sort -u > {hash_file}.expanded".format( + expander_bin=hcatExpanderBin, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 {hash_file}.expanded " + "{hash_file}.expanded {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + crackedAfter = lineCount(hcatHashFile + ".out") + hcatFingerprintCount = lineCount(hcatHashFile + ".out") - hcatHashCracked + + +# Combinator Attack +def hcatCombination(hcatHashType, hcatHashFile): + global hcatCombinationCount + global hcatProcess + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 {left} " + "{right} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatCombinationWordlist[0], + right=hcatCombinationWordlist[1], + tuning=hcatTuning, + hate_path=hate_path), + shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatCombinationCount = lineCount(hcatHashFile + ".out") - hcatHashCracked + + +# Hybrid Attack +def hcatHybrid(hcatHashType, hcatHashFile, wordlists=None): + global hcatHybridCount + global hcatProcess + + # Use provided wordlists or fall back to config default + if wordlists is None: + wordlists = hcatHybridlist + + # Ensure wordlists is a list + if not isinstance(wordlists, list): + wordlists = [wordlists] + + for wordlist in wordlists: + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 6 -1 ?s?d {wordlist} ?1?1 " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 6 -1 ?s?d {wordlist} ?1?1?1 " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 6 -1 ?s?d {wordlist} " + "?1?1?1?1 {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 7 -1 ?s?d ?1?1 {wordlist} " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 7 -1 ?s?d ?1?1?1 {wordlist} " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 7 -1 ?s?d ?1?1?1?1 {wordlist} " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + wordlist=wordlist, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatHybridCount = lineCount(hcatHashFile + ".out") - hcatHashCracked + + +# YOLO Combination Attack +def hcatYoloCombination(hcatHashType, hcatHashFile): + global hcatProcess + try: + while 1: + hcatLeft = random.choice(os.listdir(hcatOptimizedWordlists)) + hcatRight = random.choice(os.listdir(hcatOptimizedWordlists)) + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 {optimized_lists}/{left} " + "{optimized_lists}/{right} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + optimized_lists=hcatOptimizedWordlists, + tuning=hcatTuning, + left=hcatLeft, + right=hcatRight, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + raise + except KeyboardInterrupt: + pass + +# Bandrel methodlogy +def hcatBandrel(hcatHashType, hcatHashFile): + global hcatProcess + basewords = [] + while True: + company_name = input('What is the company name (Enter multiples comma separated)? ') + if company_name: + break + for name in company_name.split(','): + basewords.append(name) + for word in bandrelbasewords.split(','): + basewords.append(word) + for name in basewords: + mask1 = '-1{0}{1}'.format(name[0].lower(),name[0].upper()) + mask2 = ' ?1{0}'.format(name[1:]) + for x in range(6): + mask2 += '?a' + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} -a 3 --session {session_name} -o {hash_file}.out " + "{tuning} --potfile-path={hate_path}/hashcat.pot --runtime {maxruntime} -i {hcmask1} {hash_file} {hcmask2}".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hcmask1=mask1, + hcmask2=mask2, + maxruntime=maxruntime, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + print('Checking passwords against pipal for top {0} passwords and basewords'.format(pipal_count)) + pipal_basewords = pipal() + if pipal_basewords: + for word in pipal_basewords: + if word: + mask1 = '-1={0}{1}'.format(word[0].lower(),word[0].upper()) + mask2 = ' ?1{0}'.format(word[1:]) + # ...existing code using mask1, mask2... + else: + continue + else: + pass + for x in range(6): + mask2 += '?a' + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} -a 3 --session {session_name} -o {hash_file}.out " + "{tuning} --potfile-path={hate_path}/hashcat.pot --runtime {maxruntime} -i {hcmask1} {hash_file} {hcmask2}".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hcmask1=mask1, + hcmask2=mask2, + maxruntime=maxruntime, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + +# Middle fast Combinator Attack +def hcatMiddleCombinator(hcatHashType, hcatHashFile): + global hcatProcess + masks = hcatMiddleCombinatorMasks + # Added support for multiple character masks + new_masks = [] + for mask in masks: + tmp = [] + if len(mask) > 1: + for character in mask: + tmp.append(character) + new_masks.append('$' + '$'.join(tmp)) + else: + new_masks.append('$'+mask) + masks = new_masks + + try: + for x in range(len(masks)): + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 -j '${middle_mask}' {left} " + "{right} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatMiddleBaseList, + right=hcatMiddleBaseList, + middle_mask=masks[x], + hate_path=hate_path), + shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + raise + except KeyboardInterrupt: + pass + +# Middle thorough Combinator Attack +def hcatThoroughCombinator(hcatHashType, hcatHashFile): + global hcatProcess + masks = hcatThoroughCombinatorMasks + # Added support for multiple character masks + new_masks = [] + for mask in masks: + tmp = [] + if len(mask) > 1: + for character in mask: + tmp.append(character) + new_masks.append('$' + '$'.join(tmp)) + else: + new_masks.append('$'+mask) + masks = new_masks + + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 {left} " + "{right} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatThoroughBaseList, + right=hcatThoroughBaseList, + tuning=hcatTuning, + hate_path=hate_path), + shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + try: + for x in range(len(masks)): + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 " + "-j '${middle_mask}' {left} {right} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatThoroughBaseList, + right=hcatThoroughBaseList, + middle_mask=masks[x], + hate_path=hate_path), + shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + raise + except KeyboardInterrupt: + pass + try: + for x in range(len(masks)): + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 " + "-k '${end_mask}' {left} {right} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatThoroughBaseList, + right=hcatThoroughBaseList, + tuning=hcatTuning, + end_mask=masks[x], + hate_path=hate_path), + shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + raise + except KeyboardInterrupt: + pass + try: + for x in range(len(masks)): + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 1 " + "-j '${middle_mask}' -k '${end_mask}' {left} {right} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + left=hcatThoroughBaseList, + right=hcatThoroughBaseList, + tuning=hcatTuning, + middle_mask=masks[x], + end_mask=masks[x], + hate_path=hate_path), + shell=True) + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + +# Pathwell Mask Brute Force Attack +def hcatPathwellBruteForce(hcatHashType, hcatHashFile): + global hcatProcess + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -a 3 {hate_path}/masks/pathwell.hcmask " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + +# PRINCE Attack +def hcatPrince(hcatHashType, hcatHashFile): + global hcatProcess + hcatHashCracked = lineCount(hcatHashFile + ".out") + prince_rules_dir = os.path.join(hate_path, "princeprocessor", "rules") + prince_rule = get_rule_path("prince_optimized.rule", fallback_dir=prince_rules_dir) + hcatProcess = subprocess.Popen( + "{hate_path}/princeprocessor/{prince_bin} --case-permute --elem-cnt-min=1 --elem-cnt-max=16 -c < " + "{hcatPrinceBaseList} | {hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out " + "-r {prince_rule} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + prince_bin=hcatPrinceBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + hcatPrinceBaseList=hcatPrinceBaseList, + prince_rule=prince_rule, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + +# Extra - Good Measure +def hcatGoodMeasure(hcatHashType, hcatHashFile): + global hcatExtraCount + global hcatProcess + rule_combinator = get_rule_path("combinator.rule") + rule_insidepro = get_rule_path("InsidePro-PasswordsPro.rule") + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -r {rule_combinator} " + "-r {rule_insidepro} {hcatGoodMeasureBaseList} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + hcatGoodMeasureBaseList=hcatGoodMeasureBaseList, + session_name=generate_session_id(), + rule_combinator=rule_combinator, + rule_insidepro=rule_insidepro, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatExtraCount = lineCount(hcatHashFile + ".out") - hcatHashCracked + + +# LanMan to NT Attack +def hcatLMtoNT(): + global hcatProcess + hcatProcess = subprocess.Popen( + "{hcatBin} --show --potfile-path={hate_path}/hashcat.pot -m 3000 {hash_file}.lm > {hash_file}.lm.cracked".format( + hcatBin=hcatBin, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m 3000 {hash_file}.lm --session {session_name} -o {hash_file}.lm.cracked -1 ?u?d?s --increment -a 3 ?1?1?1?1?1?1?1 " + "{tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_file=hcatHashFile, + session_name=generate_session_id(), + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + hcatProcess.kill() + + subprocess.Popen("cat {hash_file}.lm.cracked | cut -d : -f 2 > {hash_file}.working".format( + hash_file=hcatHashFile), shell=True).wait() + converted = convert_hex("{hash_file}.working".format(hash_file=hcatHashFile)) + with open("{hash_file}.working".format(hash_file=hcatHashFile),mode='w') as working: + working.writelines('\n'.join(converted)) + hcatProcess = subprocess.Popen( + "{hate_path}/hashcat-utils/bin/{combine_bin} {hash_file}.working {hash_file}.working | sort -u > {hash_file}.combined".format( + combine_bin=hcatCombinatorBin, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} --show --potfile-path={hate_path}/hashcat.pot -m 1000 {hash_file}.nt > {hash_file}.nt.out".format( + hcatBin=hcatBin, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + hcatProcess = subprocess.Popen( + "{hcatBin} -m 1000 {hash_file}.nt --session {session_name} -o {hash_file}.nt.out {hash_file}.combined " + "-r {toggle_rule} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_file=hcatHashFile, + session_name=generate_session_id(), + toggle_rule=ensure_toggle_rule() or get_rule_path("toggles-lm-ntlm.rule", fallback_dir=os.path.join(hate_path, "rules")), + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + # toggle-lm-ntlm.rule by Didier Stevens https://blog.didierstevens.com/2016/07/16/tool-to-generate-hashcat-toggle-rules/ + + +# Recycle Cracked Passwords +def hcatRecycle(hcatHashType, hcatHashFile, hcatNewPasswords): + global hcatProcess + working_file = hcatHashFile + '.working' + if hcatNewPasswords > 0: + hcatProcess = subprocess.Popen("cat {hash_file}.out | cut -d : -f 2 > {working_file}".format( + hash_file=hcatHashFile, working_file=working_file), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(hcatProcess.pid))) + hcatProcess.kill() + + converted = convert_hex(working_file) + + # Overwrite working file with updated converted words + with open(working_file, 'w') as f: + f.write("\n".join(converted)) + for rule in hcatRules: + rule_path = get_rule_path(rule) + hcatProcess = subprocess.Popen( + "{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out {hash_file}.working " + "-r {rule_path} {tuning} --potfile-path={hate_path}/hashcat.pot".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + session_name=generate_session_id(), + rule_path=rule_path, + tuning=hcatTuning, + hate_path=hate_path), shell=True) + try: + hcatProcess.wait() + except KeyboardInterrupt: + hcatProcess.kill() + +def check_potfile(): + print("Checking POT file for already cracked hashes...") + subprocess.Popen( + "{hcatBin} --show --potfile-path={hate_path}/hashcat.pot -m {hash_type} {hash_file} > {hash_file}.out".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True) + hcatHashCracked = lineCount(hcatHashFile + ".out") + if hcatHashCracked > 0: + print("Found %d hashes already cracked.\nCopied hashes to %s.out" % (hcatHashCracked, hcatHashFile)) + else: + print("No hashes found in POT file.") + +# creating the combined output for pwdformat + cleartext +def combine_ntlm_output(): + hashes = {} + check_potfile() + if not os.path.isfile(hcatHashFile + ".out"): + print("No hashes found in POT file.") + return + with open(hcatHashFile + ".out", "r") as hcatCrackedFile: + for crackedLine in hcatCrackedFile: + parts = crackedLine.split(':', 1) + if len(parts) != 2: + continue + hash, password = parts + hashes[hash] = password.rstrip() + if not hashes: + print("No hashes found in POT file.") + return + with open(hcatHashFileOrig + ".out", "w+") as hcatCombinedHashes: + with open(hcatHashFileOrig, "r") as hcatOrigFile: + for origLine in hcatOrigFile: + orig_parts = origLine.split(':') + if len(orig_parts) < 4: + continue + ntlm_hash = orig_parts[3] + if ntlm_hash in hashes: + password = hashes[ntlm_hash] + hcatCombinedHashes.write(origLine.strip()+password+'\n') + +# Cleanup Temp Files +def cleanup(): + global pwdump_format + global hcatHashFileOrig + try: + if hcatHashType == "1000" and pwdump_format: + print("\nComparing cracked hashes to original file...") + combine_ntlm_output() + print("\nCracked passwords combined with original hashes in %s" % (hcatHashFileOrig + ".out")) + print('\nCleaning up temporary files...') + if os.path.exists(hcatHashFile + ".masks"): + os.remove(hcatHashFile + ".masks") + if os.path.exists(hcatHashFile + ".working"): + os.remove(hcatHashFile + ".working") + if os.path.exists(hcatHashFile + ".expanded"): + os.remove(hcatHashFile + ".expanded") + if os.path.exists(hcatHashFileOrig + ".combined"): + os.remove(hcatHashFileOrig + ".combined") + if os.path.exists(hcatHashFileOrig + ".lm"): + os.remove(hcatHashFileOrig + ".lm") + if os.path.exists(hcatHashFileOrig + ".lm.cracked"): + os.remove(hcatHashFileOrig + ".lm.cracked") + if os.path.exists(hcatHashFileOrig + ".working"): + os.remove(hcatHashFileOrig + ".working") + if os.path.exists(hcatHashFileOrig + ".passwords"): + os.remove(hcatHashFileOrig + ".passwords") + except KeyboardInterrupt: + #incase someone mashes the Control+C it will still cleanup + cleanup() + + + + +def hashview_api(): + """Download/Upload data to Hashview API""" + global hcatHashFile, hcatHashType + + if not REQUESTS_AVAILABLE: + print("\nError: 'requests' module not found.") + print("Install it with: pip install requests") + return + + + print("\n" + "="*60) + print("Hashview Integration") + print("="*60) + + # Get Hashview connection details from config + if not hashview_api_key: + print("\nError: Hashview API key not configured.") + print("Please set 'hashview_api_key' in config.json") + return + + print(f"\nConnecting to Hashview at: {hashview_url}") + + try: + api_harness = HashviewAPI(hashview_url, hashview_api_key, debug=debug_mode) + + while True: + print("\n" + "="*60) + print("What would you like to do?") + print("="*60) + print("\t(1) Upload Cracked Hashes from current session") + print("\t(2) Upload Wordlist") + print("\t(3) List Customers") + print("\t(4) Create Customer") + print("\t(5) Download Left Hashes") + print("\t(6) Upload Hashfile and Create Job") + print("\t(99) Back to Main Menu") + + choice = input("\nSelect an option: ") + + if choice == '1': + # Upload cracked hashes + print("\n" + "-"*60) + print("Upload Cracked Hashes") + print("-"*60) + + # Check if we're in an active session + cracked_file = None + session_file = None + try: + if 'hcatHashFile' in globals() and hcatHashFile: + potential_file = hcatHashFile + ".out" + if os.path.exists(potential_file): + session_file = potential_file + print(f"Found session file: {session_file}") + elif 'hcatHashFile' in globals() and hcatHashFile: + potential_file = hcatHashFile + "nt.out" + if os.path.exists(potential_file): + session_file = potential_file + print(f"Found session file: {session_file}") + except: + pass + + # Prompt for file + if session_file: + use_session = input("Use this file? (Y/n): ").strip().lower() + if use_session != 'n': + cracked_file = session_file + + if not cracked_file: + cracked_file = select_file_with_autocomplete( + f"Enter path to cracked hashes file (.out format) [hash type: {hcatHashType}] (TAB to autocomplete)" + ) + # select_file_with_autocomplete may return a list if allow_multiple=True, but we expect a string + if isinstance(cracked_file, list): + if cracked_file: + cracked_file = cracked_file[0] # Use the first file + else: + cracked_file = None + if isinstance(cracked_file, str): + cracked_file = cracked_file.strip() + # Validate file exists + if not cracked_file or not isinstance(cracked_file, str) or not os.path.exists(cracked_file): + print(f"✗ Error: File not found: {cracked_file}") + continue + # Show file info + file_size = os.path.getsize(cracked_file) + with open(cracked_file, 'r') as f: + line_count = sum(1 for _ in f) + print(f"File: {cracked_file}") + print(f"Size: {file_size} bytes") + print(f"Lines: {line_count}") + + # Block upload if file is empty + if file_size == 0 or line_count == 0: + print(f"✗ Error: File {cracked_file} is empty. Upload aborted.") + continue + + # Use the same hash type from main menu + hash_type = hcatHashType + + # Upload + print(f"\nUploading to Hashview (hash type: {hash_type})...") + try: + result = api_harness.upload_cracked_hashes(cracked_file, hash_type) + print(f"\n✓ Success: {result.get('msg', 'Cracked hashes uploaded')}") + if 'count' in result: + print(f" Imported: {result['count']} hashes") + except Exception as e: + print(f"\n✗ Error: {str(e)}") + import traceback + print("\nFull error details:") + traceback.print_exc() + + elif choice == '2': + print("\n" + "-"*60) + print("Upload Wordlist") + print("-"*60) + wordlist_path = select_file_with_autocomplete( + "Enter path to wordlist file (TAB to autocomplete)" + ) + if isinstance(wordlist_path, list): + wordlist_path = wordlist_path[0] if wordlist_path else None + if isinstance(wordlist_path, str): + wordlist_path = wordlist_path.strip() + if not wordlist_path or not os.path.isfile(wordlist_path): + print(f"✗ Error: File not found: {wordlist_path}") + continue + default_name = os.path.basename(wordlist_path) + wordlist_name = input(f"Enter wordlist name (default: {default_name}): ").strip() or default_name + try: + result = api_harness.upload_wordlist_file(wordlist_path, wordlist_name) + print(f"\n✓ Success: {result.get('msg', 'Wordlist uploaded')}") + if 'wordlist_id' in result: + print(f" Wordlist ID: {result['wordlist_id']}") + except Exception as e: + print(f"\n✗ Error uploading wordlist: {str(e)}") + + elif choice == '6': + # Upload hashfile and create job + hashfile_path = select_file_with_autocomplete( + "Enter path to hashfile (TAB to autocomplete)" + ) + if not hashfile_path or not os.path.exists(hashfile_path): + print(f"Error: File not found: {hashfile_path}") + continue + + customer_id = int(input("Enter customer ID: ")) + hash_type = int(input(f"Enter hash type (default: {hcatHashType}): ") or hcatHashType) + + print("\nFile formats:") + print(" 0 = pwdump, 1 = NetNTLM, 2 = kerberos") + print(" 3 = shadow, 4 = user:hash, 5 = hash_only") + file_format = int(input("Enter file format (default: 5): ") or 5) + + hashfile_name = input(f"Enter hashfile name (default: {os.path.basename(hashfile_path)}): ") or None + + try: + result = api_harness.upload_hashfile( + hashfile_path, customer_id, hash_type, file_format, hashfile_name + ) + print(f"\n✓ Success: {result.get('msg', 'Hashfile uploaded')}") + if 'hashfile_id' in result: + print(f" Hashfile ID: {result['hashfile_id']}") + # Hash count is not returned by the upload API, so we don't display it + if 'hash_count' in result: + print(f" Hash count: {result['hash_count']}") + if 'instacracked' in result: + print(f" Insta-cracked: {result['instacracked']}") + + # Offer to create a job + create_job = input("\nWould you like to create a job for this hashfile? (Y/n): ") or "Y" + if create_job.upper() == 'Y': + job_name = input("Enter job name: ") + limit_recovered = input("Limit to recovered hashes only? (y/N): ").upper() == 'Y' + notify_email = input("Send email notifications? (Y/n): ").upper() != 'N' + + # Ask if user wants to upload a custom wordlist for this job + upload_wordlist = input("\nUpload a custom wordlist to Hashview? (y/N): ").upper() == 'Y' + uploaded_wordlist_id = None + + if upload_wordlist: + print("\n" + "="*60) + print("WORDLIST UPLOAD") + print("="*60) + print("Select a wordlist file to upload to Hashview.") + print("After upload, you'll need to:") + print(" 1. Create a task in Hashview using this wordlist") + print(" 2. Manually add the task to this job via web interface") + print("\nPress TAB to autocomplete file paths.") + print("="*60) + + wordlist_path = select_file_with_autocomplete( + "Enter path to wordlist file" + ) + + if wordlist_path and os.path.isfile(wordlist_path): + # Ask for wordlist name + default_name = os.path.basename(wordlist_path) + wordlist_name = input(f"\nEnter wordlist name (default: {default_name}): ").strip() or default_name + + try: + # Upload the wordlist + upload_result = api_harness.upload_wordlist(wordlist_path, wordlist_name) + print(f"\n✓ Success: {upload_result.get('msg', 'Wordlist uploaded')}") + if 'wordlist_id' in upload_result: + uploaded_wordlist_id = upload_result['wordlist_id'] + print(f" Wordlist ID: {uploaded_wordlist_id}") + print(f" Wordlist Name: {wordlist_name}") + except Exception as e: + print(f"\n✗ Error uploading wordlist: {str(e)}") + print("Continuing with job creation...") + else: + print("\n✗ No valid wordlist file selected.") + print("Continuing with job creation...") + + try: + job_result = api_harness.create_job( + job_name, result['hashfile_id'], customer_id, + limit_recovered, notify_email + ) + print(f"\n✓ Success: {job_result.get('msg', 'Job created')}") + if 'job_id' in job_result: + print(f" Job ID: {job_result['job_id']}") + print(f"\nNote: Job created with automatically assigned tasks based on") + print(f" historical effectiveness for hash type {hash_type}.") + + if uploaded_wordlist_id: + print(f"\n{'='*60}") + print("NEXT STEPS - Configure Task in Hashview Web Interface:") + print(f"{'='*60}") + print(f"1. Go to: {hashview_url}") + print(f"2. Navigate to Tasks → Create New Task") + print(f"3. Configure task with:") + print(f" - Wordlist ID: {uploaded_wordlist_id} ({wordlist_name})") + print(f" - Rule: (select appropriate rule)") + print(f" - Attack mode: 0 (dictionary)") + print(f"4. Go to Jobs → Job ID {job_result['job_id']}") + print(f"5. Add the new task to this job") + print(f"{'='*60}") + + # Offer to start the job + start_now = input("\nStart the job now? (Y/n): ") or "Y" + if start_now.upper() == 'Y': + start_result = api_harness.start_job(job_result['job_id']) + print(f"\n✓ Success: {start_result.get('msg', 'Job started')}") + except Exception as e: + print(f"\n✗ Error creating job: {str(e)}") + except Exception as e: + print(f"\n✗ Error uploading hashfile: {str(e)}") + + elif choice == '3': + # List customers + try: + customers = api_harness.list_customers_with_hashfiles() + if customers: + api_harness.display_customers_multicolumn(customers) + else: + print("\nNo customers found with hashfiles.") + except Exception as e: + print(f"\n✗ Error fetching customers: {str(e)}") + + elif choice == '4': + # Create customer + customer_name = input("\nEnter customer name: ") + try: + result = api_harness.create_customer(customer_name) + print(f"\n✓ Success: {result.get('msg', 'Customer created')}") + if 'customer_id' in result: + print(f" Customer ID: {result['customer_id']}") + except Exception as e: + print(f"\n✗ Error creating customer: {str(e)}") + + elif choice == '5': + # Download left hashes + try: + # First, list customers to help user select + customers = api_harness.list_customers_with_hashfiles() + if customers: + api_harness.display_customers_multicolumn(customers) + + # Get customer ID and hashfile ID directly + customer_id = int(input("\nEnter customer ID: ")) + + # List hashfiles for the customer + try: + customer_hashfiles = api_harness.get_customer_hashfiles(customer_id) + + if customer_hashfiles: + print("\n" + "="*100) + print(f"Hashfiles for Customer ID {customer_id}:") + print("="*100) + print(f"{'ID':<10} {'Name':<88}") + print("-" * 100) + for hf in customer_hashfiles: + hf_id = hf.get('id', 'N/A') + hf_name = hf.get('name', 'N/A') + # Truncate long names to fit within 100 columns + if len(str(hf_name)) > 88: + hf_name = str(hf_name)[:85] + "..." + print(f"{hf_id:<10} {hf_name:<88}") + print("="*100) + print(f"Total: {len(customer_hashfiles)} hashfile(s)") + else: + print(f"\nNo hashfiles found for customer ID {customer_id}") + except Exception as e: + print(f"\nWarning: Could not list hashfiles: {e}") + print("You may need to manually find the hashfile ID in the web interface.") + + hashfile_id = int(input("\nEnter hashfile ID: ")) + + # Set output filename automatically + output_file = f"left_{customer_id}_{hashfile_id}.txt" + + # Download the left hashes + download_result = api_harness.download_left_hashes( + customer_id, hashfile_id, output_file + ) + print(f"\n✓ Success: Downloaded {download_result['size']} bytes") + print(f" File: {download_result['output_file']}") + + # Ask if user wants to switch to this hashfile + switch = input("\nSwitch to this hashfile for cracking? (Y/n): ").strip().lower() + if switch != 'n': + hcatHashFile = download_result['output_file'] + + print(f"✓ Switched to hashfile: {hcatHashFile}") + print("\nReturning to main menu to start cracking...") + return # Exit hashview menu and return to main menu + + except ValueError: + print("\n✗ Error: Invalid ID entered. Please enter a numeric ID.") + except Exception as e: + print(f"\n✗ Error downloading hashes: {str(e)}") + + elif choice == '99': + break + else: + print("Invalid option. Please try again.") + + except KeyboardInterrupt: + print("\n\nHashview upload canceled.") + except Exception as e: + print(f"\nError connecting to Hashview: {str(e)}") + + +# Attack menu handlers live in hate_crack.attacks +from hate_crack import attacks as _attacks +from types import SimpleNamespace + + +def _attack_ctx(): + ctx = sys.modules.get(__name__) + if ctx is None: + return SimpleNamespace(**globals()) + return ctx + + +def quick_crack(): + return _attacks.quick_crack(_attack_ctx()) + + +def extensive_crack(): + return _attacks.extensive_crack(_attack_ctx()) + + +def brute_force_crack(): + return _attacks.brute_force_crack(_attack_ctx()) + + +def top_mask_crack(): + return _attacks.top_mask_crack(_attack_ctx()) + + +def fingerprint_crack(): + return _attacks.fingerprint_crack(_attack_ctx()) + + +def combinator_crack(): + return _attacks.combinator_crack(_attack_ctx()) + + +def hybrid_crack(): + return _attacks.hybrid_crack(_attack_ctx()) + + +def pathwell_crack(): + return _attacks.pathwell_crack(_attack_ctx()) + + +def prince_attack(): + return _attacks.prince_attack(_attack_ctx()) + + +def yolo_combination(): + return _attacks.yolo_combination(_attack_ctx()) + + +def thorough_combinator(): + return _attacks.thorough_combinator(_attack_ctx()) + + +def middle_combinator(): + return _attacks.middle_combinator(_attack_ctx()) + + +def bandrel_method(): + return _attacks.bandrel_method(_attack_ctx()) + + +# convert hex words for recycling +def convert_hex(working_file): + processed_words = [] + regex = r'^\$HEX\[(\S+)\]' + with open(working_file, 'r') as f: + for line in f: + match = re.search(regex, line.rstrip('\n')) + if match: + try: + processed_words.append(binascii.unhexlify(match.group(1)).decode('iso-8859-9')) + except UnicodeDecodeError: + pass + else: + processed_words.append(line.rstrip('\n')) + + return processed_words + +# Display Cracked Hashes +def show_results(): + if os.path.isfile(hcatHashFile + ".out"): + with open(hcatHashFile + ".out") as hcatOutput: + for cracked_hash in hcatOutput: + print(cracked_hash.strip()) + else: + print("No hashes were cracked :(") + +# Analyze Hashes with Pipal +def pipal(): + hcatHashFilePipal = hcatHashFile + if hcatHashType == "1000": + combine_ntlm_output() + hcatHashFilePipal = hcatHashFileOrig + + if os.path.isfile(pipalPath): + if os.path.isfile(hcatHashFilePipal + ".out"): + pipalFile = open(hcatHashFilePipal + ".passwords", 'w') + with open(hcatHashFilePipal + ".out") as hcatOutput: + for cracked_hash in hcatOutput: + password = cracked_hash.split(':') + clearTextPass = password[-1] + match = re.search(r'^\$HEX\[(\S+)\]', clearTextPass) + if match: + clearTextPass = binascii.unhexlify(match.group(1)).decode('iso-8859-9') + pipalFile.write(clearTextPass) + pipalFile.close() + + pipalProcess = subprocess.Popen( + "{pipal_path} {pipal_file} -t {pipal_count} --output {pipal_out}".format( + pipal_path=pipalPath, + pipal_file=hcatHashFilePipal + ".passwords", + pipal_out=hcatHashFilePipal + ".pipal", + pipal_count=pipal_count), + shell=True) + try: + pipalProcess.wait() + except KeyboardInterrupt: + print('Killing PID {0}...'.format(str(pipalProcess.pid))) + pipalProcess.kill() + print("Pipal file is at " + hcatHashFilePipal + ".pipal\n") + import sys + if not sys.stdin.isatty(): + view_choice = 'y' + else: + view_choice = input("Would you like to view (cat) the pipal output? (Y/n): ").strip().lower() + if view_choice in ('', 'y', 'yes'): + print("\n--- Pipal Output Start ---\n") + with open(hcatHashFilePipal + ".pipal") as pipalfile: + print(pipalfile.read()) + print("\n--- Pipal Output End ---\n") + with open(hcatHashFilePipal + ".pipal") as pipalfile: + pipal_content = pipalfile.readlines() + raw_pipal = '\n'.join(pipal_content) + raw_pipal = re.sub('\n+', '\n', raw_pipal) + raw_regex = r'Top [0-9]+ base words\n' + for word in range(pipal_count): + raw_regex += r'(\S+).*\n' + basewords_re = re.compile(raw_regex) + results = re.search(basewords_re,raw_pipal) + top_basewords = [] + if results: + if results.lastindex is not None: + for i in range(1, results.lastindex + 1): + if i is not None: + top_basewords.append(results.group(i)) + else: + pass + return top_basewords + else: + return [] + else: + print("No hashes were cracked :(") + return [] + else: + print("The path to pipal.rb is either not set, or is incorrect.") + return + + + +# Exports output to excel file +def export_excel(): + + # Check for openyxl dependancy for export + try: + import openpyxl + except ImportError: + sys.stderr.write("You must install openpyxl first using 'pip install openpyxl' or 'pip3 install openpyxl'\n") + return + + if hcatHashType == "1000": + combine_ntlm_output() + output = openpyxl.Workbook() + current_ws = output.create_sheet(title='hate_crack output', index=0) + current_row = 2 + current_ws['A1'] = 'Username' + current_ws['B1'] = 'SID' + current_ws['C1'] = 'LM Hash' + current_ws['D1'] = 'NTLM Hash' + current_ws['E1'] = 'Clear-Text Password' + with open(hcatHashFileOrig+'.out') as input_file: + for line in input_file: + matches = re.match(r'(^[^:]+):([0-9]+):([a-z0-9A-Z]{32}):([a-z0-9A-Z]{32}):::(.*)',line.rstrip('\r\n')) + if not matches: + continue + username = matches.group(1) + sid = matches.group(2) + lm = matches.group(3) + ntlm = matches.group(4) + try: + clear_text = matches.group(5) + match = re.search(r'^\$HEX\[(\S+)\]', clear_text) + if match: + clear_text = binascii.unhexlify(match.group(1)).decode('iso-8859-9') + except: + clear_text = '' + current_ws['A' + str(current_row)] = username + current_ws['B' + str(current_row)] = sid + current_ws['C' + str(current_row)] = lm + current_ws['D' + str(current_row)] = ntlm + current_ws['E' + str(current_row)] = clear_text + current_row += 1 + output.save(hcatHashFile+'.xlsx') + print("Output exported succesfully to {0}".format(hcatHashFile+'.xlsx')) + else: + sys.stderr.write('Excel output only supported for pwdformat for NTLM hashes') + return + + +# Show README +def show_readme(): + with open(hate_path + "/readme.md") as hcatReadme: + print(hcatReadme.read()) + + +# Exit Program +def quit_hc(): + cleanup() + sys.exit(0) + + +def get_main_menu_options(): + """Return the mapping of main menu keys to their handler functions.""" + return { + "1": quick_crack, + "2": extensive_crack, + "3": brute_force_crack, + "4": top_mask_crack, + "5": fingerprint_crack, + "6": combinator_crack, + "7": hybrid_crack, + "8": pathwell_crack, + "9": prince_attack, + "10": yolo_combination, + "11": middle_combinator, + "12": thorough_combinator, + "13": bandrel_method, + "91": download_hashmob_rules, + "92": weakpass_wordlist_menu, + "93": download_hashmob_wordlists, + "94": weakpass_wordlist_menu, + "95": hashview_api, + "96": pipal, + "97": export_excel, + "98": show_results, + "99": show_readme, + "100": quit_hc, + } + +# The Main Guts +def main(): + global pwdump_format + global hcatHashFile + global hcatHashType + global hcatHashFileOrig + global lmHashesFound + global debug_mode + global hashview_url, hashview_api_key + global hcatPath, hcatBin, hcatWordlists, hcatOptimizedWordlists, rulesDirectory + global pipalPath, maxruntime, bandrelbasewords + + + import argparse + + parser = argparse.ArgumentParser(description="hate_crack - Hashcat automation and wordlist management tool") + parser.add_argument('hashfile', nargs='?', default=None, help='Path to hash file to crack (positional, optional)') + parser.add_argument('hashtype', nargs='?', default=None, help='Hashcat hash type (e.g., 1000 for NTLM) (positional, optional)') + parser.add_argument('--download-hashview', action='store_true', help='Download hashes from Hashview (legacy menu)') + parser.add_argument('--hashview', action='store_true', help='Jump directly to Hashview customer/hashfile menu') + parser.add_argument('--download-torrent', metavar='FILENAME', help='Download a specific Weakpass torrent file') + parser.add_argument('--download-all-torrents', action='store_true', help='Download all available Weakpass torrents from cache') + parser.add_argument('--weakpass', action='store_true', help='Download wordlists from Weakpass') + parser.add_argument('--rank', type=int, default=-1, help='Only show wordlists with this rank (use 0 to show all, default: >4)') + parser.add_argument('--hashmob', action='store_true', help='Download wordlists from Hashmob.net') + parser.add_argument('--rules', action='store_true', help='Download rules from Hashmob.net') + parser.add_argument('--cleanup', action='store_true', help='Cleanup .out files, torrents, and extract or remove .7z archives') + parser.add_argument('--debug', action='store_true', help='Enable debug mode') + # Removed add_common_args(parser) since config items are now only set via config file + args = parser.parse_args() + + global debug_mode + debug_mode = args.debug + + setup_logging(logger, hate_path, debug_mode) + + from types import SimpleNamespace + + config = SimpleNamespace( + hashview_url=hashview_url, + hashview_api_key=hashview_api_key, + hcatPath=hcatPath, + hcatBin=hcatBin, + hcatWordlists=hcatWordlists, + hcatOptimizedWordlists=hcatOptimizedWordlists, + rules_directory=rulesDirectory, + pipalPath=pipalPath, + maxruntime=maxruntime, + bandrelbasewords=bandrelbasewords, + ) + + hashview_url = config.hashview_url + hashview_api_key = config.hashview_api_key + hcatPath = config.hcatPath + hcatBin = config.hcatBin + hcatWordlists = config.hcatWordlists + hcatOptimizedWordlists = config.hcatOptimizedWordlists + rulesDirectory = config.rules_directory + pipalPath = config.pipalPath + maxruntime = config.maxruntime + bandrelbasewords = config.bandrelbasewords + + if args.download_torrent: + download_weakpass_torrent( + download_torrent=download_torrent_file, + filename=args.download_torrent, + print_fn=print, + ) + sys.exit(0) + + if args.cleanup: + cleanup_wordlist_artifacts() + sys.exit(0) + + if args.download_all_torrents: + try: + download_all_weakpass_torrents( + fetch_all_wordlists=fetch_all_weakpass_wordlists_multithreaded, + download_torrent=download_torrent_file, + print_fn=print, + ) + except Exception: + sys.exit(1) + sys.exit(0) + + + + if args.hashview: + if not hashview_api_key: + print("\nError: Hashview API key not configured.") + print("Please set 'hashview_api_key' in config.json") + sys.exit(1) + try: + hcatHashFile, hcatHashType = download_hashes_from_hashview( + hashview_url, + hashview_api_key, + debug_mode, + input_fn=input, + print_fn=print, + ) + except ValueError: + print("\n✗ Error: Invalid ID entered. Please enter a numeric ID.") + sys.exit(1) + except Exception as e: + print(f"\n✗ Error downloading hashes: {str(e)}") + sys.exit(1) + sys.exit(0) + + if args.weakpass: + weakpass_wordlist_menu(rank=args.rank) + sys.exit(0) + + if args.hashmob: + download_hashmob_wordlists(print_fn=print) + sys.exit(0) + if args.rules: + download_hashmob_rules(print_fn=print) + sys.exit(0) + + if args.hashfile and args.hashtype: + hcatHashFile = resolve_path(args.hashfile) + hcatHashType = args.hashtype + if not hcatHashFile or not os.path.isfile(hcatHashFile): + print(f"Error: hashfile not found: {args.hashfile}") + sys.exit(1) + if not str(hcatHashType).isdigit(): + print(f"Error: invalid hash type: {hcatHashType}") + sys.exit(1) + else: + ascii_art() + print("\n" + "="*60) + print("No hash file provided. What would you like to do?") + print("="*60) + print("\t(1) Download hashes from Hashview") + print("\t(2) Download wordlists from Weakpass") + print("\t(3) Download wordlists from Hashmob.net") + print("\t(4) Download rules from Hashmob.net") + print("\t(5) Exit") + choice = input("\nSelect an option: ") + if choice == '1' or args.download_hashview: + if not hashview_api_key: + print("\nError: Hashview API key not configured.") + print("Please set 'hashview_api_key' in config.json") + sys.exit(1) + try: + hcatHashFile, hcatHashType = download_hashes_from_hashview( + hashview_url, + hashview_api_key, + debug_mode, + input_fn=input, + print_fn=print, + ) + except ValueError: + print("\n✗ Error: Invalid ID entered. Please enter a numeric ID.") + sys.exit(1) + except Exception as e: + print(f"\n✗ Error downloading hashes: {str(e)}") + sys.exit(1) + elif choice == '2' or args.weakpass: + weakpass_wordlist_menu(rank=args.rank) + sys.exit(0) + elif choice == '3' or args.hashmob: + download_hashmob_wordlists(print_fn=print) + sys.exit(0) + elif choice == '4' or args.rules: + download_hashmob_rules(print_fn=print) + sys.exit(0) + else: + sys.exit(0) + + hcatHashFileOrig = hcatHashFile + ascii_art() + # Get Initial Input Hash Count + hcatHashCount = lineCount(hcatHashFile) + + # If LM or NT Mode Selected and pwdump Format Detected, Prompt For LM to NT Attack + if hcatHashType == "1000": + lmHashesFound = False + pwdump_format = False + hcatHashFileLine = open(hcatHashFile, "r").readline() + if re.search(r"[a-f0-9A-F]{32}:[a-f0-9A-F]{32}:::", hcatHashFileLine): + pwdump_format = True + print("PWDUMP format detected...") + print("Parsing NT hashes...") + subprocess.Popen( + "cat {hash_file} | cut -d : -f 4 |sort -u > {hash_file}.nt".format(hash_file=hcatHashFile), + shell=True).wait() + print("Parsing LM hashes...") + subprocess.Popen("cat {hash_file} | cut -d : -f 3 |sort -u > {hash_file}.lm".format(hash_file=hcatHashFile), + shell=True).wait() + if ((lineCount(hcatHashFile + ".lm") == 1) and ( + hcatHashFileLine.split(":")[2].lower() != "aad3b435b51404eeaad3b435b51404ee")) or ( + lineCount(hcatHashFile + ".lm") > 1): + lmHashesFound = True + lmChoice = input("LM hashes identified. Would you like to brute force the LM hashes first? (Y) ") or "Y" + if lmChoice.upper() == 'Y': + hcatLMtoNT() + hcatHashFileOrig = hcatHashFile + hcatHashFile = hcatHashFile + ".nt" + elif re.search(r"^[a-f0-9A-F]{32}$", hcatHashFileLine): + pwdump_format = False + print("PWDUMP format was not detected...") + print("Hash only detected") + elif re.search(r"^.+:[a-f0-9A-F]{32}$", hcatHashFileLine): + pwdump_format = False + print("PWDUMP format was not detected...") + print("username with Hash detected") + subprocess.Popen( + "cat {hash_file} | cut -d : -f 2 |sort -u > {hash_file}.nt".format(hash_file=hcatHashFile), + shell=True).wait() + hcatHashFileOrig = hcatHashFile + hcatHashFile = hcatHashFile + ".nt" + else: + print("unknown format....does it have usernames?") + exit(1) + # Check POT File for Already Cracked Hashes + if not os.path.isfile(hcatHashFile + ".out"): + hcatOutput = open(hcatHashFile + ".out", "w+") + hcatOutput.close() + print("Checking POT file for already cracked hashes...") + subprocess.Popen( + "{hcatBin} --show --potfile-path={hate_path}/hashcat.pot -m {hash_type} {hash_file} > {hash_file}.out".format( + hcatBin=hcatBin, + hash_type=hcatHashType, + hash_file=hcatHashFile, + hate_path=hate_path), shell=True).wait() + hcatHashCracked = lineCount(hcatHashFile + ".out") + if hcatHashCracked > 0: + print("Found %d hashes already cracked.\nCopied hashes to %s.out" % (hcatHashCracked, hcatHashFile)) + else: + print("No hashes found in POT file.") + + # Display Options + try: + options = get_main_menu_options() + while 1: + print("\n\t(1) Quick Crack") + print("\t(2) Extensive Pure_Hate Methodology Crack") + print("\t(3) Brute Force Attack") + print("\t(4) Top Mask Attack") + print("\t(5) Fingerprint Attack") + print("\t(6) Combinator Attack") + print("\t(7) Hybrid Attack") + print("\t(8) Pathwell Top 100 Mask Brute Force Crack") + print("\t(9) PRINCE Attack") + print("\t(10) YOLO Combinator Attack") + print("\t(11) Middle Combinator Attack") + print("\t(12) Thorough Combinator Attack") + print("\t(13) Bandrel Methodology") + print("\n\t(91) Download rules from Hashmob.net") + print("\n\t(92) Download wordlists from Weakpass") + print("\t(93) Download wordlists from Hashmob.net") + print("\t(94) Weakpass Wordlist Menu") + print("\t(95) Hashview API") + print("\t(96) Analyze hashes with Pipal") + print("\t(97) Export Output to Excel Format") + print("\t(98) Display Cracked Hashes") + print("\t(99) Display README") + print("\t(100) Quit") + try: + task = input("\nSelect a task: ") + options[task]() + except KeyError: + pass + except KeyboardInterrupt: + quit_hc() + +# Boilerplate +if __name__ == '__main__': + main() diff --git a/hate_crack/hate_crack.py b/hate_crack/hate_crack.py index 089f22a..c8a196a 100644 --- a/hate_crack/hate_crack.py +++ b/hate_crack/hate_crack.py @@ -8,6 +8,11 @@ def _resolve_root(): def _load_root_module(): + try: + from . import _root # type: ignore + return _root + except Exception: + pass root_path = _resolve_root() if not os.path.isfile(root_path): raise FileNotFoundError(f"Root hate_crack.py not found at {root_path}") diff --git a/pyproject.toml b/pyproject.toml index de223de..caf418c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + [project] name = "hate-crack" version = "2.0" @@ -10,3 +14,9 @@ dependencies = [ "beautifulsoup4>=4.12.0", "ruff>=0.9.4", ] + +[project.scripts] +hate_crack = "hate_crack.__main__:main" + +[tool.setuptools.packages.find] +include = ["hate_crack*"] diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..fdd44d8 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,95 @@ +import os +import sys +import types +import pytest +from unittest import mock + +import hate_crack.api as api + +@pytest.fixture +def fake_file(tmp_path): + return tmp_path / "testfile.txt" + +@pytest.fixture +def patch_dependencies(tmp_path): + # Patch sanitize_filename to just return the filename + patch1 = mock.patch("hate_crack.api.sanitize_filename", side_effect=lambda x: x) + # Patch get_hcat_wordlists_dir to return tmp_path + patch2 = mock.patch("hate_crack.api.get_hcat_wordlists_dir", return_value=str(tmp_path)) + # Patch extract_with_7z to just return True + patch3 = mock.patch("hate_crack.api.extract_with_7z", return_value=True) + # Patch os.replace to do nothing + patch4 = mock.patch("os.replace") + # Patch os.makedirs to do nothing + patch5 = mock.patch("os.makedirs") + patches = [patch1, patch2, patch3, patch4, patch5] + for p in patches: + p.start() + yield + for p in patches: + p.stop() + +def make_mock_response(content=b"abc", total=3, status_code=200, endswith='.txt'): + mock_resp = mock.MagicMock() + mock_resp.__enter__.return_value = mock_resp + mock_resp.__exit__.return_value = False + mock_resp.iter_content = lambda chunk_size: [content] + mock_resp.headers = {'content-length': str(total)} + mock_resp.status_code = status_code + mock_resp.raise_for_status = mock.Mock() + return mock_resp + +def test_download_success(tmp_path, patch_dependencies): + file_name = "wordlist.txt" + out_path = str(tmp_path / file_name) + mock_resp = make_mock_response(content=b"abc", total=3) + with mock.patch("hate_crack.api.requests.get", return_value=mock_resp): + with mock.patch("builtins.open", mock.mock_open()) as m_open: + result = api.download_official_wordlist(file_name, out_path) + assert result is True + m_open.assert_called() # File was opened for writing + +def test_download_7z_triggers_extract(tmp_path, patch_dependencies): + file_name = "archive.7z" + out_path = str(tmp_path / file_name) + mock_resp = make_mock_response(content=b"abc", total=3) + with mock.patch("hate_crack.api.requests.get", return_value=mock_resp): + with mock.patch("builtins.open", mock.mock_open()): + with mock.patch("hate_crack.api.extract_with_7z") as m_extract: + m_extract.return_value = True + result = api.download_official_wordlist(file_name, out_path) + assert result is True + m_extract.assert_called_once() + +def test_download_keyboard_interrupt(tmp_path, patch_dependencies): + file_name = "wordlist.txt" + out_path = str(tmp_path / file_name) + # Simulate KeyboardInterrupt in requests.get context manager + def raise_keyboard_interrupt(*a, **kw): + raise KeyboardInterrupt() + with mock.patch("hate_crack.api.requests.get", side_effect=raise_keyboard_interrupt): + result = api.download_official_wordlist(file_name, out_path) + assert result is False + +def test_download_exception(tmp_path, patch_dependencies): + file_name = "wordlist.txt" + out_path = str(tmp_path / file_name) + # Simulate generic Exception in requests.get context manager + def raise_exception(*a, **kw): + raise Exception("fail") + with mock.patch("hate_crack.api.requests.get", side_effect=raise_exception): + result = api.download_official_wordlist(file_name, out_path) + assert result is False + +def test_progress_bar_prints(tmp_path, patch_dependencies, capsys): + file_name = "wordlist.txt" + out_path = str(tmp_path / file_name) + # Simulate a file with a known size + content = b"x" * 8192 + mock_resp = make_mock_response(content=content, total=8192) + with mock.patch("hate_crack.api.requests.get", return_value=mock_resp): + with mock.patch("builtins.open", mock.mock_open()): + result = api.download_official_wordlist(file_name, out_path) + assert result is True + captured = capsys.readouterr() + assert "Downloaded" in captured.out or "Downloaded" in captured.err \ No newline at end of file diff --git a/tests/test_ui_menu_options.py b/tests/test_ui_menu_options.py new file mode 100644 index 0000000..d700357 --- /dev/null +++ b/tests/test_ui_menu_options.py @@ -0,0 +1,53 @@ +import importlib.util +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +CLI_SPEC = importlib.util.spec_from_file_location("hate_crack_cli", PROJECT_ROOT / "hate_crack.py") +CLI_MODULE = importlib.util.module_from_spec(CLI_SPEC) +CLI_SPEC.loader.exec_module(CLI_MODULE) + +MENU_OPTION_TEST_CASES = [ + ("1", CLI_MODULE._attacks, "quick_crack", "quick-crack"), + ("2", CLI_MODULE._attacks, "extensive_crack", "extensive-crack"), + ("3", CLI_MODULE._attacks, "brute_force_crack", "brute-force"), + ("4", CLI_MODULE._attacks, "top_mask_crack", "top-mask"), + ("5", CLI_MODULE._attacks, "fingerprint_crack", "fingerprint"), + ("6", CLI_MODULE._attacks, "combinator_crack", "combinator"), + ("7", CLI_MODULE._attacks, "hybrid_crack", "hybrid"), + ("8", CLI_MODULE._attacks, "pathwell_crack", "pathwell"), + ("9", CLI_MODULE._attacks, "prince_attack", "prince"), + ("10", CLI_MODULE._attacks, "yolo_combination", "yolo"), + ("11", CLI_MODULE._attacks, "middle_combinator", "middle"), + ("12", CLI_MODULE._attacks, "thorough_combinator", "thorough"), + ("13", CLI_MODULE._attacks, "bandrel_method", "bandrel"), + ("91", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"), + ("92", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"), + ("93", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"), + ("94", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu-secondary"), + ("95", CLI_MODULE, "hashview_api", "hashview"), + ("96", CLI_MODULE, "pipal", "pipal"), + ("97", CLI_MODULE, "export_excel", "export-excel"), + ("98", CLI_MODULE, "show_results", "show-results"), + ("99", CLI_MODULE, "show_readme", "show-readme"), + ("100", CLI_MODULE, "quit_hc", "quit"), +] + + +@pytest.mark.parametrize( + ("option_key", "target_module", "target_attr", "expected_prefix"), + MENU_OPTION_TEST_CASES, +) +def test_main_menu_option_returns_expected(monkeypatch, option_key, target_module, target_attr, expected_prefix): + sentinel = f"{expected_prefix}-{option_key}" + monkeypatch.setattr( + target_module, + target_attr, + lambda *args, **kwargs: sentinel, + ) + + options = CLI_MODULE.get_main_menu_options() + assert option_key in options, f"Menu option {option_key} must exist" + handler = options[option_key] + assert handler() == sentinel