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"