diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..421374b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.git +.gitmodules +.gitignore +.DS_Store +.idea +.vscode +.venv +.pytest_cache +.ruff_cache +__pycache__ +*.pyc +build +wordlists +hashcat.pot +left_*.txt +left_*.txt.out +*.out +*.pot diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..f95ab86 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,14 @@ +FROM python:3.13-slim + +WORKDIR /workspace + +RUN python -m pip install -q uv + +COPY . /workspace + +RUN uv tool install . + +ENV PATH="/root/.local/bin:${PATH}" +ENV HATE_CRACK_SKIP_INIT=1 + +CMD ["bash", "-lc", "/root/.local/bin/hate_crack --help >/tmp/hc_help.txt && ./hate_crack.py --help >/tmp/hc_script_help.txt"] diff --git a/README.md b/README.md index c6ee506..21e3f3b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Core logic is now split into modules under `hate_crack/`: - `hate_crack/api.py`: Hashview, Weakpass, and Hashmob integrations (downloads/menus/helpers). - `hate_crack/attacks.py`: menu attack handlers. - `hate_crack/hashmob_wordlist.py`: Hashmob wordlist utilities (thin wrapper; calls into api.py). -- `hate_crack/hate_crack.py`: module shim that loads the top-level script. +- `hate_crack/main.py`: main CLI implementation. The top-level `hate_crack.py` remains the main entry point and orchestrates these modules. @@ -47,12 +47,41 @@ This project depends on and is inspired by a number of external projects and ser ------------------------------------------------------------------- ## Usage -`$ uv run hate_crack` +You can run hate_crack as a tool, as a script, or via `uv run`: ``` -usage: uv run hate_crack.py +uv run hate_crack.py or -usage: uv run hate_crack.py [options] +uv run hate_crack.py [options] +``` + +### Run as a tool (recommended) +Install once from the repo root: + +``` +uv tool install . +hate_crack +``` + +If you run the tool outside the repo, set `HATE_CRACK_HOME` so assets like +`hashcat-utils` can be found: + +``` +HATE_CRACK_HOME=/path/to/hate_crack hate_crack +``` + +### Run as a script +The script uses a `uv` shebang. Make it executable and run: + +``` +chmod +x hate_crack.py +./hate_crack.py +``` + +You can also use Python directly: + +``` +python hate_crack.py ``` Common options: @@ -106,6 +135,29 @@ uv run pytest -v uv run pytest tests/test_hashview.py -v ``` +### Live Hashview Tests + +The live Hashview upload test is skipped by default. To run it, set the +environment variable and provide valid credentials in `config.json`: + +```bash +HATE_CRACK_RUN_LIVE_TESTS=1 uv run pytest tests/test_upload_cracked_hashes.py -v +``` + +### End-to-End Install Tests (Local + Docker) + +Local uv tool install + script execution (uses a temporary HOME): + +```bash +HATE_CRACK_RUN_E2E=1 uv run pytest tests/test_e2e_local_install.py -v +``` + +Docker-based end-to-end install/run (cached via `Dockerfile.test`): + +```bash +HATE_CRACK_RUN_DOCKER_TESTS=1 uv run pytest tests/test_docker_script_install.py -v +``` + ### Test Structure - **tests/test_hashview.py**: Comprehensive test suite for HashviewAPI class with mocked API responses, including: diff --git a/hate_crack.py b/hate_crack.py index 89a157d..d5c212b 100755 --- a/hate_crack.py +++ b/hate_crack.py @@ -1,1941 +1,80 @@ -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 - +#!/usr/bin/env -S uv run --project . import sys -import os -import json -import shutil -import logging -#!/usr/bin/env python3 +from hate_crack import main as _main -try: - import requests - REQUESTS_AVAILABLE = True -except Exception: - requests = None - REQUESTS_AVAILABLE = False +# Re-export symbols for tests and legacy imports. +for _name, _value in _main.__dict__.items(): + if _name.startswith("__") and _name not in {"__all__", "__doc__", "__name__", "__package__", "__loader__", "__spec__"}: + continue + globals().setdefault(_name, _value) -# 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__ +def __getattr__(name): + return getattr(_main, name) -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') +def _sync_globals_to_main(): + # Keep commonly-mutated globals aligned for tests and wrappers. + for name in ( + "hcatHashType", + "pipal_count", + "hcatHashFile", + "hcatHashFileOrig", + "pipalPath", + "debug_mode", + ): + if name in globals(): + setattr(_main, name, globals()[name]) -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) +def _sync_callables_to_main(): + for name in ( + "weakpass_wordlist_menu", + "download_hashmob_wordlists", + "download_hashmob_rules", + "hashview_api", + "export_excel", + "show_results", + "show_readme", + "quit_hc", + ): + if name in globals(): + setattr(_main, name, globals()[name]) -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', '') +def cli_main(): + _sync_globals_to_main() + _sync_callables_to_main() + return _main.main() -SKIP_INIT = os.environ.get("HATE_CRACK_SKIP_INIT") == "1" -logger = logging.getLogger("hate_crack") -if not logger.handlers: - logger.addHandler(logging.NullHandler()) +def main(): + _sync_globals_to_main() + _sync_callables_to_main() + return _main.main() - - -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) + _sync_globals_to_main() + return _main.pipal() 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, + "1": _attacks.quick_crack, + "2": _attacks.extensive_crack, + "3": _attacks.brute_force_crack, + "4": _attacks.top_mask_crack, + "5": _attacks.fingerprint_crack, + "6": _attacks.combinator_crack, + "7": _attacks.hybrid_crack, + "8": _attacks.pathwell_crack, + "9": _attacks.prince_attack, + "10": _attacks.yolo_combination, + "11": _attacks.middle_combinator, + "12": _attacks.thorough_combinator, + "13": _attacks.bandrel_method, "91": download_hashmob_rules, "92": weakpass_wordlist_menu, "93": download_hashmob_wordlists, @@ -1948,273 +87,6 @@ def get_main_menu_options(): "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() +if __name__ == "__main__": + sys.exit(main()) diff --git a/hate_crack/__main__.py b/hate_crack/__main__.py index c9ce9ee..9b5f518 100644 --- a/hate_crack/__main__.py +++ b/hate_crack/__main__.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import sys -from .hate_crack import cli_main +from .main import main as _main def main() -> int: - return cli_main() or 0 + return _main() or 0 if __name__ == "__main__": diff --git a/hate_crack/api.py b/hate_crack/api.py index d4eef6e..5c5b1b0 100644 --- a/hate_crack/api.py +++ b/hate_crack/api.py @@ -1,4 +1,5 @@ import json +import sys import os import threading import time @@ -386,8 +387,19 @@ def weakpass_wordlist_menu(rank=-1): continue return sorted(i for i in indices if 1 <= i <= max_index) + def _safe_input(prompt): + try: + if not sys.stdin or not sys.stdin.isatty(): + return "q" + except Exception: + return "q" + try: + return input(prompt) + except EOFError: + return "q" + try: - sel = input("\nEnter the number(s) to download (e.g. 1,3,5-7) or 'q' to cancel: ") + sel = _safe_input("\nEnter the number(s) to download (e.g. 1,3,5-7) or 'q' to cancel: ") if sel.lower() == 'q': print("Returning to menu...") return @@ -762,13 +774,36 @@ def download_hashes_from_hashview( print_fn: Callable[..., None] = print, ) -> Tuple[str, str]: """Interactive Hashview download flow used by CLI.""" + try: + if not sys.stdin or not sys.stdin.isatty(): + print_fn("\nAvailable Customers:") + raise ValueError("non-interactive") + except ValueError: + raise + except Exception: + # If stdin status can't be determined, continue normally. + pass api_harness = HashviewAPI(hashview_url, hashview_api_key, debug=debug_mode) customers = api_harness.list_customers_with_hashfiles() if customers: api_harness.display_customers_multicolumn(customers) else: print_fn("\nNo customers found with hashfiles.") - customer_id = int(input_fn("\nEnter customer ID: ")) + def _safe_input(prompt): + try: + if not sys.stdin or not sys.stdin.isatty(): + return "q" + except Exception: + return "q" + try: + return input_fn(prompt) + except EOFError: + return "q" + + customer_raw = _safe_input("\nEnter customer ID: ").strip() + if customer_raw.lower() == "q": + raise ValueError("cancelled") + customer_id = int(customer_raw) try: customer_hashfiles = api_harness.get_customer_hashfiles(customer_id) if customer_hashfiles: @@ -790,7 +825,10 @@ def download_hashes_from_hashview( except Exception as exc: print_fn(f"\nWarning: Could not list hashfiles: {exc}") print_fn("You may need to manually find the hashfile ID in the web interface.") - hashfile_id = int(input_fn("\nEnter hashfile ID: ")) + hashfile_raw = _safe_input("\nEnter hashfile ID: ").strip() + if hashfile_raw.lower() == "q": + raise ValueError("cancelled") + hashfile_id = int(hashfile_raw) hcat_hash_type = "1000" output_file = f"left_{customer_id}_{hashfile_id}.txt" download_result = api_harness.download_left_hashes(customer_id, hashfile_id, output_file) @@ -1025,7 +1063,18 @@ def list_and_download_official_wordlists(): file_name = entry.get('file_name', name) print(f"{idx+1}. {name} ({file_name})") print("a. Download ALL files") - sel = input("Enter the number(s) to download (e.g. 1,3,5-7), or 'a' for all, or 'q' to quit: ") + def _safe_input(prompt): + try: + if not sys.stdin or not sys.stdin.isatty(): + return "q" + except Exception: + return "q" + try: + return input(prompt) + except EOFError: + return "q" + + sel = _safe_input("Enter the number(s) to download (e.g. 1,3,5-7), or 'a' for all, or 'q' to quit: ") if sel.lower() == 'q': return if sel.lower() == 'a': @@ -1087,7 +1136,18 @@ def list_and_download_hashmob_rules(): if not rules: return print("a. Download ALL files") - sel = input("Enter the number(s) to download (e.g. 1,3,5-7), or 'a' for all, or 'q' to quit: ") + def _safe_input(prompt): + try: + if not sys.stdin or not sys.stdin.isatty(): + return "q" + except Exception: + return "q" + try: + return input(prompt) + except EOFError: + return "q" + + sel = _safe_input("Enter the number(s) to download (e.g. 1,3,5-7), or 'a' for all, or 'q' to quit: ") if sel.lower() == 'q': return rules_dir = get_rules_dir() diff --git a/hate_crack/config.json.example b/hate_crack/config.json.example new file mode 100644 index 0000000..ed5ee28 --- /dev/null +++ b/hate_crack/config.json.example @@ -0,0 +1,24 @@ +{ + "hcatPath": "", + "hcatBin": "hashcat", + "hcatTuning": "--force --remove", + "hcatWordlists": "/Passwords/wordlists", + "hcatOptimizedWordlists": "/Passwords/optimized_wordlists", + "rules_directory": "/path/to/hashcat/rules", + "hcatDictionaryWordlist": ["rockyou.txt"], + "hcatCombinationWordlist": ["rockyou.txt","rockyou.txt"], + "hcatHybridlist": ["rockyou.txt"], + "hcatMiddleCombinatorMasks": ["2","4"," ","-","_","+",",",".","&"], + "hcatMiddleBaseList": "rockyou.txt", + "hcatThoroughCombinatorMasks": ["0","1","2","3","4","5","6","7","8","9"," ","-","_","+",",","!","#","$","\"","%","&","'","(",")","*",".","/",":",";","<","=",">","?","@","[","\\","]","^","`","{","|","}","~"], + "hcatThoroughBaseList": "rockyou.txt", + "hcatGoodMeasureBaseList": "rockyou.txt", + "hcatPrinceBaseList": "rockyou.txt", + "pipalPath": "/path/to/pipal", + "pipal_count" : 10, + "bandrelmaxruntime": 300, + "bandrel_common_basedwords": "welcome,password,p@ssword,p@$$word,changeme,letmein,summer,winter,spring,springtime,fall,autumn,monday,tuesday,wednesday,thursday,friday,saturday,sunday,january,february,march,april,may,june,july,august,september,october,november,december,christmas,easter,covid19", + "hashview_url": "http://localhost:8443", + "hashview_api_key": "", + "hashmob_api_key": "" +} diff --git a/hate_crack/hate_crack.py b/hate_crack/hate_crack.py deleted file mode 100644 index c8a196a..0000000 --- a/hate_crack/hate_crack.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys - - -def _resolve_root(): - return os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "hate_crack.py")) - - -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}") - import importlib.util - spec = importlib.util.spec_from_file_location("hate_crack_root", root_path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -_ROOT = _load_root_module() -for _name, _value in _ROOT.__dict__.items(): - if _name.startswith("__") and _name not in {"__all__", "__doc__", "__name__", "__package__", "__loader__", "__spec__"}: - continue - globals().setdefault(_name, _value) - - -def cli_main(): - if hasattr(_ROOT, "cli_main"): - return _ROOT.cli_main() - if hasattr(_ROOT, "main"): - return _ROOT.main() - raise AttributeError("Root hate_crack.py has no cli_main or main") - - -if __name__ == "__main__": - sys.exit(cli_main()) diff --git a/hate_crack/_root.py b/hate_crack/main.py similarity index 99% rename from hate_crack/_root.py rename to hate_crack/main.py index 89a157d..f7753c4 100755 --- a/hate_crack/_root.py +++ b/hate_crack/main.py @@ -66,14 +66,40 @@ 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") +_pkg_dir = os.path.dirname(os.path.realpath(__file__)) 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__)) +def _has_hate_crack_assets(path): + if not path: + return False + return ( + os.path.isfile(os.path.join(path, "config.json.example")) + and os.path.isdir(os.path.join(path, "hashcat-utils")) + ) + + +def _resolve_hate_path(package_path): + env_override = os.environ.get("HATE_CRACK_HOME") or os.environ.get("HATE_CRACK_ASSETS") + candidates = [] + if env_override: + candidates.append(env_override) + + cwd = os.getcwd() + candidates.extend([cwd, os.path.abspath(os.path.join(cwd, os.pardir))]) + candidates.append(package_path) + + for candidate in candidates: + if _has_hate_crack_assets(candidate): + return candidate + + return package_path + + +hate_path = _resolve_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') diff --git a/pyproject.toml b/pyproject.toml index caf418c..bca2705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,16 +3,14 @@ requires = ["setuptools>=69"] build-backend = "setuptools.build_meta" [project] -name = "hate-crack" +name = "hate_crack" version = "2.0" description = "Menu driven Python wrapper for hashcat" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "pytest>=8.3.4", "requests>=2.31.0", "beautifulsoup4>=4.12.0", - "ruff>=0.9.4", ] [project.scripts] @@ -20,3 +18,6 @@ hate_crack = "hate_crack.__main__:main" [tool.setuptools.packages.find] include = ["hate_crack*"] + +[tool.setuptools.package-data] +hate_crack = ["config.json.example"] diff --git a/tests/test_docker_script_install.py b/tests/test_docker_script_install.py new file mode 100644 index 0000000..36ec926 --- /dev/null +++ b/tests/test_docker_script_install.py @@ -0,0 +1,48 @@ +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + + +@pytest.mark.skipif( + os.environ.get("HATE_CRACK_RUN_DOCKER_TESTS") != "1", + reason="Set HATE_CRACK_RUN_DOCKER_TESTS=1 to run Docker-based tests.", +) +def test_docker_script_install_and_run(): + if shutil.which("docker") is None: + pytest.skip("docker not available") + + repo_root = Path(__file__).resolve().parents[1] + image_tag = "hate-crack-e2e" + + try: + build = subprocess.run( + ["docker", "build", "-f", "Dockerfile.test", "-t", image_tag, str(repo_root)], + capture_output=True, + text=True, + timeout=600, + ) + except subprocess.TimeoutExpired as exc: + pytest.fail(f"Docker build timed out after {exc.timeout}s") + + assert build.returncode == 0, ( + "Docker build failed. " + f"stdout={build.stdout} stderr={build.stderr}" + ) + + try: + run = subprocess.run( + ["docker", "run", "--rm", image_tag], + capture_output=True, + text=True, + timeout=120, + ) + except subprocess.TimeoutExpired as exc: + pytest.fail(f"Docker run timed out after {exc.timeout}s") + + assert run.returncode == 0, ( + "Docker script install/run failed. " + f"stdout={run.stdout} stderr={run.stderr}" + ) diff --git a/tests/test_e2e_local_install.py b/tests/test_e2e_local_install.py new file mode 100644 index 0000000..e4ca4f6 --- /dev/null +++ b/tests/test_e2e_local_install.py @@ -0,0 +1,84 @@ +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + + +@pytest.mark.skipif( + os.environ.get("HATE_CRACK_RUN_E2E") != "1", + reason="Set HATE_CRACK_RUN_E2E=1 to run local end-to-end install tests.", +) +def test_local_uv_tool_install_and_help(tmp_path): + if shutil.which("uv") is None: + pytest.skip("uv not available") + + repo_root = Path(__file__).resolve().parents[1] + home_dir = tmp_path / "home" + home_dir.mkdir() + + env = os.environ.copy() + env.update( + { + "HOME": str(home_dir), + "PATH": f"{home_dir / '.local' / 'bin'}:{env.get('PATH', '')}", + "HATE_CRACK_SKIP_INIT": "1", + "HATE_CRACK_HOME": str(repo_root), + "XDG_CACHE_HOME": str(tmp_path / "cache"), + "XDG_CONFIG_HOME": str(tmp_path / "config"), + "XDG_DATA_HOME": str(tmp_path / "data"), + } + ) + + install = subprocess.run( + ["uv", "tool", "install", "."], + cwd=repo_root, + env=env, + capture_output=True, + text=True, + ) + assert install.returncode == 0, ( + "uv tool install failed. " + f"stdout={install.stdout} stderr={install.stderr}" + ) + + tool_help = subprocess.run( + ["hate_crack", "--help"], + cwd=repo_root, + env=env, + capture_output=True, + text=True, + ) + assert tool_help.returncode == 0, ( + "hate_crack --help failed. " + f"stdout={tool_help.stdout} stderr={tool_help.stderr}" + ) + + script_help = subprocess.run( + ["./hate_crack.py", "--help"], + cwd=repo_root, + env=env, + capture_output=True, + text=True, + ) + assert script_help.returncode == 0, ( + "./hate_crack.py --help failed. " + f"stdout={script_help.stdout} stderr={script_help.stderr}" + ) + + output = tool_help.stdout + tool_help.stderr + expected_flags = [ + "--download-hashview", + "--hashview", + "--download-torrent", + "--download-all-torrents", + "--weakpass", + "--rank", + "--hashmob", + "--rules", + "--cleanup", + "--debug", + ] + for flag in expected_flags: + assert flag in output, f"Missing {flag} in help output" diff --git a/tests/test_upload_cracked_hashes.py b/tests/test_upload_cracked_hashes.py index b696db7..511e71a 100644 --- a/tests/test_upload_cracked_hashes.py +++ b/tests/test_upload_cracked_hashes.py @@ -13,9 +13,11 @@ def get_hashview_config(): return hashview_url, hashview_api_key +RUN_LIVE = os.environ.get("HATE_CRACK_RUN_LIVE_TESTS") == "1" + @pytest.mark.skipif( - not get_hashview_config()[0] or not get_hashview_config()[1], - reason="Requires hashview_url and hashview_api_key in config.json." + not RUN_LIVE or not get_hashview_config()[0] or not get_hashview_config()[1], + reason="Requires HATE_CRACK_RUN_LIVE_TESTS=1 and hashview_url/hashview_api_key in config.json." ) def test_upload_cracked_hashes_from_file(): hashview_url, hashview_api_key = get_hashview_config()