mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
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>
180 lines
6.2 KiB
Python
180 lines
6.2 KiB
Python
import builtins
|
|
import importlib
|
|
import io
|
|
from types import SimpleNamespace
|
|
|
|
|
|
def test_fingerprint_crack_prompts_for_expander_len_and_enables_hybrid(monkeypatch):
|
|
from hate_crack import attacks
|
|
|
|
seen = {}
|
|
|
|
def fake_hcatFingerprint(
|
|
hash_type, hash_file, expander_len, run_hybrid_on_expanded=False
|
|
):
|
|
seen["hash_type"] = hash_type
|
|
seen["hash_file"] = hash_file
|
|
seen["expander_len"] = expander_len
|
|
seen["run_hybrid_on_expanded"] = run_hybrid_on_expanded
|
|
|
|
ctx = SimpleNamespace(
|
|
hcatHashType="1000",
|
|
hcatHashFile="dummy.hash",
|
|
hcatFingerprint=fake_hcatFingerprint,
|
|
)
|
|
|
|
monkeypatch.setattr(builtins, "input", lambda _prompt="": "24")
|
|
attacks.fingerprint_crack(ctx)
|
|
|
|
assert seen["expander_len"] == 24
|
|
assert seen["run_hybrid_on_expanded"] is True
|
|
|
|
|
|
def test_hcatFingerprint_uses_selected_expander_and_calls_hybrid(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("HATE_CRACK_SKIP_INIT", "1")
|
|
|
|
import hate_crack.main as hc_main
|
|
|
|
importlib.reload(hc_main)
|
|
|
|
hashfile = tmp_path / "hashes.txt"
|
|
out_path = tmp_path / "hashes.txt.out"
|
|
out_path.write_text("deadbeef:Accordbookkeeping2025!:x\n")
|
|
|
|
# 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.
|
|
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:
|
|
data = stdin.read() if stdin is not None else b""
|
|
# Identity "expansion" is enough for this test; we just need the
|
|
# pipeline to create the .expanded file and complete.
|
|
self.stdout = io.BytesIO(data)
|
|
else:
|
|
# hashcat invocation: do nothing
|
|
pass
|
|
|
|
def wait(self, timeout=None):
|
|
return 0
|
|
|
|
def kill(self):
|
|
return None
|
|
|
|
monkeypatch.setattr(hc_main.subprocess, "Popen", FakePopen)
|
|
|
|
# Run with expander24 and ensure secondary hybrid gets the expanded file.
|
|
monkeypatch.setattr(hc_main, "hcatHashFile", str(hashfile), raising=False)
|
|
hc_main.hcatFingerprint(
|
|
"1000", str(hashfile), expander_len=24, run_hybrid_on_expanded=True
|
|
)
|
|
|
|
assert any(
|
|
isinstance(args[0], str) and args[0].endswith("expander24.bin")
|
|
for args in seen["popen_args"]
|
|
)
|
|
|
|
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"] == []
|