mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
fix(fingerprint): skip hashcat when expanded wordlist is empty
When no hashes have been cracked yet, the fingerprint attack's expander
pipeline produces an empty {hash}.expanded file. Previously the function
would still launch a hashcat combinator session against two empty
wordlists (and, when invoked via the menu, six more hashcat sessions
from the secondary hybrid pass) - wasting cycles and creating confusing
empty intermediate files.
Add an early-exit guard after the expander pipeline: if .expanded is
empty, print an informative skip message and break out of the loop
before invoking hashcat or hcatHybrid.
Mirrors the existing "if hcatNewPasswords > 0" guard pattern in
hcatRecycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5fc54f4574
commit
6040460919
@@ -1553,6 +1553,12 @@ def hcatFingerprint(
|
||||
print("Killing PID {0}...".format(str(sort_proc.pid)))
|
||||
sort_proc.kill()
|
||||
expander_proc.kill()
|
||||
if lineCount(f"{hcatHashFile}.expanded") == 0:
|
||||
print(
|
||||
"[!] Skipping Fingerprint Attack: no candidates to expand "
|
||||
"(no cracked passwords yet)."
|
||||
)
|
||||
break
|
||||
fingerprint_cmd = [
|
||||
hcatBin,
|
||||
"-m",
|
||||
|
||||
@@ -41,10 +41,11 @@ def test_hcatFingerprint_uses_selected_expander_and_calls_hybrid(monkeypatch, tm
|
||||
out_path = tmp_path / "hashes.txt.out"
|
||||
out_path.write_text("deadbeef:Accordbookkeeping2025!:x\n")
|
||||
|
||||
# Make the loop run exactly one iteration.
|
||||
# Calls: before-loop(1), end-of-iteration(1) == before → break, post-loop(1).
|
||||
counts = iter([1, 1, 1])
|
||||
monkeypatch.setattr(hc_main, "lineCount", lambda _p: next(counts))
|
||||
# Constant lineCount makes the while-loop terminate after one iteration
|
||||
# (crackedAfter == crackedBefore) and keeps the empty-.expanded guard
|
||||
# quiet (1 != 0). Avoids coupling to the exact number of lineCount
|
||||
# call sites inside hcatFingerprint and _run_hcat_cmd.
|
||||
monkeypatch.setattr(hc_main, "lineCount", lambda _p: 1)
|
||||
monkeypatch.setattr(hc_main, "hcatHashCracked", 0)
|
||||
|
||||
# Avoid any filesystem/executable checks in unit test.
|
||||
@@ -102,3 +103,77 @@ def test_hcatFingerprint_uses_selected_expander_and_calls_hybrid(monkeypatch, tm
|
||||
assert seen["hybrid_calls"] == [
|
||||
("1000", str(hashfile), [f"{hashfile}.expanded"]),
|
||||
]
|
||||
|
||||
|
||||
def test_hcatFingerprint_skips_hashcat_and_hybrid_when_expanded_is_empty(
|
||||
monkeypatch, tmp_path
|
||||
):
|
||||
"""If the expander pipeline yields an empty {hash}.expanded file (e.g.
|
||||
nothing has been cracked yet), hcatFingerprint must NOT invoke hashcat
|
||||
and must NOT call the secondary hybrid attack. Otherwise we waste a
|
||||
session running combinator against two empty wordlists."""
|
||||
monkeypatch.setenv("HATE_CRACK_SKIP_INIT", "1")
|
||||
|
||||
import hate_crack.main as hc_main
|
||||
|
||||
importlib.reload(hc_main)
|
||||
|
||||
hashfile = tmp_path / "hashes.txt"
|
||||
# Empty .out simulates "no cracks yet" — the documented trigger for the bug.
|
||||
out_path = tmp_path / "hashes.txt.out"
|
||||
out_path.write_text("")
|
||||
|
||||
monkeypatch.setattr(hc_main, "hcatHashCracked", 0)
|
||||
monkeypatch.setattr(hc_main, "ensure_binary", lambda binary_path, **_k: binary_path)
|
||||
|
||||
seen = {"popen_args": [], "hybrid_calls": []}
|
||||
|
||||
def fake_hybrid(hash_type, hash_file, wordlists=None):
|
||||
seen["hybrid_calls"].append((hash_type, hash_file, wordlists))
|
||||
|
||||
monkeypatch.setattr(hc_main, "hcatHybrid", fake_hybrid)
|
||||
|
||||
class FakePopen:
|
||||
def __init__(self, args, stdin=None, stdout=None, text=False, **_kwargs):
|
||||
self.args = args
|
||||
self.pid = 123
|
||||
self.stdout = None
|
||||
seen["popen_args"].append(args)
|
||||
|
||||
cmd0 = args[0]
|
||||
if cmd0 == "sort":
|
||||
data = stdin.read() if stdin is not None else b""
|
||||
lines = sorted(set(data.splitlines()))
|
||||
for ln in lines:
|
||||
stdout.write(ln + b"\n")
|
||||
stdout.flush()
|
||||
elif isinstance(cmd0, str) and "expander" in cmd0:
|
||||
# Identity passthrough of empty stdin → empty stdout.
|
||||
data = stdin.read() if stdin is not None else b""
|
||||
self.stdout = io.BytesIO(data)
|
||||
else:
|
||||
# hashcat invocation: must never reach here in this test.
|
||||
pass
|
||||
|
||||
def wait(self, timeout=None):
|
||||
return 0
|
||||
|
||||
def kill(self):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(hc_main.subprocess, "Popen", FakePopen)
|
||||
monkeypatch.setattr(hc_main, "hcatHashFile", str(hashfile), raising=False)
|
||||
|
||||
hc_main.hcatFingerprint(
|
||||
"1000", str(hashfile), expander_len=7, run_hybrid_on_expanded=True
|
||||
)
|
||||
|
||||
# No hashcat invocation: identify by the -a flag, which only the hashcat
|
||||
# command carries (expander has 1 arg; sort uses -u).
|
||||
hashcat_invocations = [args for args in seen["popen_args"] if "-a" in args]
|
||||
assert hashcat_invocations == [], (
|
||||
f"hashcat was invoked with empty .expanded: {hashcat_invocations}"
|
||||
)
|
||||
|
||||
# No secondary hybrid pass on an empty wordlist.
|
||||
assert seen["hybrid_calls"] == []
|
||||
|
||||
Reference in New Issue
Block a user