From 164a17003c2db46a3e67c6645b307e67c77c75fd Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Fri, 13 Feb 2026 20:04:11 -0500 Subject: [PATCH] refactor: use cracked .out file as sole wordlist source for Ollama attack Remove ollamaWordlist config key and all references. Wordlist mode now requires the cracked hashes .out file to exist and extracts passwords by splitting on the first colon. Detect Ollama refusal responses and abort gracefully. Update tests accordingly. Co-Authored-By: Claude Opus 4.6 --- config.json.example | 1 - hate_crack/attacks.py | 15 ++++------ hate_crack/main.py | 25 +++++++--------- tests/test_pull_ollama_model.py | 52 +++++++-------------------------- 4 files changed, 25 insertions(+), 68 deletions(-) diff --git a/config.json.example b/config.json.example index 9f2f61b..6450a19 100644 --- a/config.json.example +++ b/config.json.example @@ -24,6 +24,5 @@ "hashview_api_key": "", "hashmob_api_key": "", "ollamaModel": "llama3.2", - "ollamaWordlist": "rockyou.txt", "ollamaNumCtx": 32768 } diff --git a/hate_crack/attacks.py b/hate_crack/attacks.py index 161d7e1..3158c42 100644 --- a/hate_crack/attacks.py +++ b/hate_crack/attacks.py @@ -495,16 +495,11 @@ def ollama_attack(ctx: Any) -> None: choice = input("\nSelect generation mode: ").strip() if choice == "1": - default_wl = ctx.ollamaWordlist - if isinstance(default_wl, list): - default_wl = default_wl[0] if default_wl else "" - print(f"\nDefault wordlist: {default_wl}") - use_default = input("Use the default wordlist? [Y/n]: ").strip().lower() - if use_default == "n": - wordlist = ctx.select_file_with_autocomplete("Enter wordlist path") - wordlist = ctx._resolve_wordlist_path(wordlist, ctx.hcatWordlists) - else: - wordlist = default_wl + wordlist = ctx.hcatHashFile + ".out" + if not os.path.isfile(wordlist): + print("Error: No cracked hashes output file found.") + return + print(f"\nUsing wordlist: {wordlist}") ctx.hcatOllama( ctx.hcatHashType, ctx.hcatHashFile, "wordlist", wordlist ) diff --git a/hate_crack/main.py b/hate_crack/main.py index 5716b54..927a976 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -463,15 +463,6 @@ except KeyError as e: ) ) ollamaModel = default_config.get("ollamaModel", "llama3.2") -try: - ollamaWordlist = config_parser["ollamaWordlist"] -except KeyError as e: - print( - "{0} is not defined in config.json using defaults from config.json.example".format( - e - ) - ) - ollamaWordlist = default_config.get("ollamaWordlist", "rockyou.txt") try: ollamaNumCtx = int(config_parser["ollamaNumCtx"]) except KeyError as e: @@ -620,8 +611,6 @@ hcatGoodMeasureBaseList = _normalize_wordlist_setting( hcatGoodMeasureBaseList, wordlists_dir ) hcatPrinceBaseList = _normalize_wordlist_setting(hcatPrinceBaseList, wordlists_dir) -ollamaWordlist = _normalize_wordlist_setting(ollamaWordlist, wordlists_dir) - if not SKIP_INIT: # Verify hashcat binary is available # hcatBin should be in PATH or be an absolute path (resolved from hcatPath + hcatBin if configured) @@ -1551,6 +1540,11 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data): with open(wordlist_path, "r", errors="ignore") as f: for line in f: stripped = line.strip() + if not stripped: + continue + # Use only content after the first colon (e.g. hash:password -> password) + if ":" in stripped: + stripped = stripped.split(":", 1)[1] if stripped: lines.append(stripped) except Exception as e: @@ -1559,10 +1553,8 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data): print(f"Loaded {len(lines)} passwords from wordlist.") wordlist_sample = "\n".join(lines) prompt = ( - "You are a password generation expert. Below is a sample of real passwords. " - "Study the patterns, character choices, and structures. Then generate hashcat rules" \ - " that could transform common base words into similar passwords. Focus on patterns like " \ - "capitalization, leetspeak, suffixes, and common substitutions. Here are the sample passwords:\n" \ + "Generate baseword to be used in a denylist for keeping users from setting their passwords with these basewords." + "Study the patterns, character choices, and structures. Focus on patterns like capitalization, leetspeak, suffixes, and common substitutions. Here are the sample passwords:\n" f"{wordlist_sample}" ) elif mode == "target": @@ -1640,6 +1632,9 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data): return response_text = result.get("response", "") + if "I'm sorry, but I can't help with that" in response_text: + print("Error: Ollama refused the request. Try a different model or adjust your prompt.") + return raw_lines = response_text.strip().split("\n") # Filter out blank lines and lines that look like numbering/explanation candidates = [] diff --git a/tests/test_pull_ollama_model.py b/tests/test_pull_ollama_model.py index 832be9c..8782714 100644 --- a/tests/test_pull_ollama_model.py +++ b/tests/test_pull_ollama_model.py @@ -653,14 +653,13 @@ class TestOllamaAttackHandler: ctx = mock.MagicMock() ctx.hcatHashType = "0" ctx.hcatHashFile = "/tmp/hashes.txt" - ctx.ollamaWordlist = "/tmp/wordlist.txt" ctx.hcatWordlists = "/tmp/wordlists" for k, v in overrides.items(): setattr(ctx, k, v) return ctx - def test_wordlist_mode_default(self, tmp_path): - """Default wordlist is always ollamaWordlist from config.""" + def test_wordlist_mode_uses_cracked_output(self, tmp_path): + """Wordlist mode uses the cracked hashes .out file.""" from hate_crack.attacks import ollama_attack hash_file = str(tmp_path / "hashes.txt") @@ -669,41 +668,24 @@ class TestOllamaAttackHandler: f.write("Password1\nSummer2024\n") ctx = self._make_ctx(hcatHashFile=hash_file) - with mock.patch("builtins.input", side_effect=["1", "n", ""]): + with mock.patch("builtins.input", side_effect=["1"]): ollama_attack(ctx) ctx.hcatOllama.assert_called_once_with( - "0", hash_file, "wordlist", "/tmp/wordlist.txt", + "0", hash_file, "wordlist", cracked_out, ) - def test_wordlist_mode_falls_back_to_config(self): - """When cracked output does not exist, fall back to ollamaWordlist.""" + def test_wordlist_mode_errors_without_cracked_output(self, capsys): + """When cracked output does not exist, error out.""" from hate_crack.attacks import ollama_attack ctx = self._make_ctx(hcatHashFile="/tmp/nonexistent_hashes.txt") - with mock.patch("builtins.input", side_effect=["1", "n", ""]): + with mock.patch("builtins.input", side_effect=["1"]): ollama_attack(ctx) - ctx.hcatOllama.assert_called_once_with( - "0", "/tmp/nonexistent_hashes.txt", "wordlist", "/tmp/wordlist.txt", - ) - - def test_wordlist_mode_custom_path(self): - """Selection '1', override wordlist → resolved path passed to hcatOllama.""" - from hate_crack.attacks import ollama_attack - - ctx = self._make_ctx() - ctx.select_file_with_autocomplete.return_value = "custom.txt" - ctx._resolve_wordlist_path.return_value = "/resolved/custom.txt" - - with mock.patch("builtins.input", side_effect=["1", "y", ""]): - ollama_attack(ctx) - - ctx.select_file_with_autocomplete.assert_called_once() - ctx._resolve_wordlist_path.assert_called_once_with("custom.txt", "/tmp/wordlists") - ctx.hcatOllama.assert_called_once_with( - "0", "/tmp/hashes.txt", "wordlist", "/resolved/custom.txt", - ) + captured = capsys.readouterr() + assert "No cracked hashes output file found" in captured.out + ctx.hcatOllama.assert_not_called() def test_target_mode(self): """Selection '2' → hcatOllama('target', {company, industry, location}).""" @@ -733,17 +715,3 @@ class TestOllamaAttackHandler: assert "Invalid selection" in captured.out ctx.hcatOllama.assert_not_called() - def test_wordlist_list_uses_first(self): - """When ollamaWordlist is a list and no cracked output exists, the first element is used.""" - from hate_crack.attacks import ollama_attack - - ctx = self._make_ctx( - hcatHashFile="/tmp/nonexistent_hashes.txt", - ollamaWordlist=["/tmp/first.txt", "/tmp/second.txt"], - ) - with mock.patch("builtins.input", side_effect=["1", "n", ""]): - ollama_attack(ctx) - - ctx.hcatOllama.assert_called_once() - call_args = ctx.hcatOllama.call_args - assert call_args[0][3] == "/tmp/first.txt"