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 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-02-13 20:04:11 -05:00
parent 1035287d4e
commit 164a17003c
4 changed files with 25 additions and 68 deletions
-1
View File
@@ -24,6 +24,5 @@
"hashview_api_key": "",
"hashmob_api_key": "",
"ollamaModel": "llama3.2",
"ollamaWordlist": "rockyou.txt",
"ollamaNumCtx": 32768
}
+5 -10
View File
@@ -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
)
+10 -15
View File
@@ -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 = []
+10 -42
View File
@@ -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"