From b75d897f4299e5560b57373e8dd3396709f62025 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Wed, 22 Apr 2026 15:18:31 -0400 Subject: [PATCH] fix: propagate --username to hashcat --show potfile checks `_run_hashcat_show` is called during the initial potfile check (main preprocessing) and by `combine_ntlm_output` via `check_potfile()`. It invokes `hashcat --show` via `subprocess.run` and previously did not go through `_append_potfile_arg`, so it never received `--username`. Without `--username`, hashcat treats the first colon of a `user:hash` line as part of the hash and fails to match any potfile entries. This caused the initial "already cracked" check to report zero hits for legitimately cracked `user:hash` input files. Route the command build through `_maybe_append_username_flag` before invoking subprocess, mirroring the `_append_potfile_arg` pattern for normal attack commands. Adds `TestUsernameInjectionIntoShow` covering both flag-set and flag-unset code paths. Refs #107 Co-Authored-By: Claude Opus 4.7 (1M context) --- hate_crack/main.py | 23 ++++++++++++++--------- tests/test_username_detect.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/hate_crack/main.py b/hate_crack/main.py index 9e63089..dc413d6 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -1154,16 +1154,21 @@ def _dedup_netntlm_by_username( def _run_hashcat_show(hash_type, hash_file, output_path): + cmd = [ + hcatBin, + "--show", + # Use hashcat's built-in potfile unless configured otherwise. + *([f"--potfile-path={hcatPotfilePath}"] if hcatPotfilePath else []), + "-m", + str(hash_type), + hash_file, + ] + # If username:hash format was detected, --show also needs --username + # to parse the input correctly; otherwise it treats "user:hash" as a + # literal hash and finds no matches in the potfile. + _maybe_append_username_flag(cmd) result = subprocess.run( - [ - hcatBin, - "--show", - # Use hashcat's built-in potfile unless configured otherwise. - *([f"--potfile-path={hcatPotfilePath}"] if hcatPotfilePath else []), - "-m", - str(hash_type), - hash_file, - ], + cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False, diff --git a/tests/test_username_detect.py b/tests/test_username_detect.py index f2d347c..d7df7e3 100644 --- a/tests/test_username_detect.py +++ b/tests/test_username_detect.py @@ -332,3 +332,36 @@ class TestUsernameInjectionIntoBruteForce: main_module.hcatBruteForce("0", hash_file, 1, 7) cmd = mp.call_args[0][0] assert cmd.count("--username") == 1 + + +class TestUsernameInjectionIntoShow: + """_run_hashcat_show should also honor hcatUsernamePrefix so the initial + potfile check and combine_ntlm_output correctly parse user:hash files.""" + + def test_show_contains_username_when_flag_set(self, main_module, tmp_path): + hash_file = str(tmp_path / "hashes.txt") + output_path = str(tmp_path / "hashes.out") + mock_result = MagicMock() + mock_result.stdout = b"" + + with patch.object(main_module, "hcatBin", "hashcat"), \ + patch.object(main_module, "hcatPotfilePath", ""), \ + patch.object(main_module, "hcatUsernamePrefix", True), \ + patch("hate_crack.main.subprocess.run", return_value=mock_result) as mr: + main_module._run_hashcat_show("0", hash_file, output_path) + cmd = mr.call_args[0][0] + assert "--username" in cmd + + def test_show_no_username_when_flag_unset(self, main_module, tmp_path): + hash_file = str(tmp_path / "hashes.txt") + output_path = str(tmp_path / "hashes.out") + mock_result = MagicMock() + mock_result.stdout = b"" + + with patch.object(main_module, "hcatBin", "hashcat"), \ + patch.object(main_module, "hcatPotfilePath", ""), \ + patch.object(main_module, "hcatUsernamePrefix", False), \ + patch("hate_crack.main.subprocess.run", return_value=mock_result) as mr: + main_module._run_hashcat_show("0", hash_file, output_path) + cmd = mr.call_args[0][0] + assert "--username" not in cmd