mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
fix: resolve --rules dest collision, non-blocking dedup prompt, subcommand detection (#17)
- Change quick subparser --rules to dest=rule_files to prevent collision with the top-level --rules=store_true Hashmob download flag - Update run_noninteractive to read args.rule_files instead of args.rules - Move ATTACK_COMMANDS above run_noninteractive (Fix 5) - Fix has_attack_subcommand to scan all argv elements (supports leading global flags like --debug) - Replace raw input() dedup prompt with _auto_input() so non-interactive runs don't block - Update existing quick dispatch tests to use rule_files= namespace key - Add test_main_quick_with_rules_dispatches: proves --rules routes to crack, not download - Add test_main_debug_flag_before_subcommand: proves leading global flags don't break routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e11a15d4c2
commit
1a8bacfbc7
+7
-7
@@ -4964,7 +4964,9 @@ def main():
|
||||
else:
|
||||
argv = argv_temp # Fallback if subcommand not found
|
||||
|
||||
has_attack_subcommand = bool(argv) and argv[0] in _noninteractive.ATTACK_COMMANDS
|
||||
has_attack_subcommand = any(
|
||||
arg in _noninteractive.ATTACK_COMMANDS for arg in argv
|
||||
)
|
||||
use_subcommand_parser = "hashview" in argv or has_attack_subcommand
|
||||
parser, hashview_parser = _build_parser(
|
||||
include_positional=not use_subcommand_parser,
|
||||
@@ -5370,12 +5372,10 @@ def main():
|
||||
f"Detected {duplicates} duplicate account(s) out of"
|
||||
f" {total} total NetNTLM hashes."
|
||||
)
|
||||
dedup_choice = (
|
||||
input(
|
||||
"Would you like to ignore duplicate accounts"
|
||||
" (keep first occurrence only)? (Y) "
|
||||
)
|
||||
or "Y"
|
||||
dedup_choice = _auto_input(
|
||||
"Would you like to ignore duplicate accounts"
|
||||
" (keep first occurrence only)? (Y) ",
|
||||
"Y",
|
||||
)
|
||||
if dedup_choice.upper() == "Y":
|
||||
hcatHashFileOrig = hcatHashFile
|
||||
|
||||
@@ -9,6 +9,8 @@ the same pattern ``attacks.py`` uses). See
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
ATTACK_COMMANDS = ("quick", "dict", "brute", "topmask")
|
||||
|
||||
|
||||
def build_rule_chains(ctx: Any, rule_tokens: list[str] | None) -> list[str]:
|
||||
"""Convert CLI ``--rules`` tokens into hashcat ``-r`` chain strings.
|
||||
@@ -56,7 +58,7 @@ def run_noninteractive(ctx: Any, args: Any) -> int:
|
||||
print(f"Error: wordlist not found: {args.wordlist}")
|
||||
return 1
|
||||
try:
|
||||
chains = build_rule_chains(ctx, args.rules)
|
||||
chains = build_rule_chains(ctx, args.rule_files)
|
||||
except (FileNotFoundError, ValueError) as exc:
|
||||
print(f"Error: invalid --rules value: {exc}")
|
||||
return 1
|
||||
@@ -88,9 +90,6 @@ def run_noninteractive(ctx: Any, args: Any) -> int:
|
||||
return 2
|
||||
|
||||
|
||||
ATTACK_COMMANDS = ("quick", "dict", "brute", "topmask")
|
||||
|
||||
|
||||
def add_attack_subparsers(subparsers) -> None:
|
||||
"""Register the non-interactive attack subcommands on an argparse
|
||||
subparsers object (the same one used for ``hashview``).
|
||||
@@ -112,6 +111,7 @@ def add_attack_subparsers(subparsers) -> None:
|
||||
"--rules",
|
||||
nargs="*",
|
||||
default=[],
|
||||
dest="rule_files",
|
||||
metavar="RULE",
|
||||
help="Rule filename(s) from the rules directory. Chain with '+' "
|
||||
"(e.g. best64.rule+d3ad0ne.rule). Omit to run without rules.",
|
||||
|
||||
@@ -87,7 +87,7 @@ def test_dispatch_quick_calls_quick_dictionary(tmp_path):
|
||||
wl = tmp_path / "rockyou.txt"
|
||||
wl.write_text("password\n")
|
||||
ctx = _spy_ctx(tmp_path)
|
||||
args = SimpleNamespace(command="quick", wordlist=str(wl), rules=["best64.rule"])
|
||||
args = SimpleNamespace(command="quick", wordlist=str(wl), rule_files=["best64.rule"])
|
||||
code = ni.run_noninteractive(ctx, args)
|
||||
assert code == 0
|
||||
assert [c[0] for c in ctx.calls] == ["hcatQuickDictionary"]
|
||||
@@ -101,7 +101,7 @@ def test_dispatch_quick_calls_quick_dictionary(tmp_path):
|
||||
def test_dispatch_quick_missing_wordlist_returns_1(tmp_path):
|
||||
(tmp_path / "rules").mkdir()
|
||||
ctx = _spy_ctx(tmp_path)
|
||||
args = SimpleNamespace(command="quick", wordlist=str(tmp_path / "nope.txt"), rules=[])
|
||||
args = SimpleNamespace(command="quick", wordlist=str(tmp_path / "nope.txt"), rule_files=[])
|
||||
assert ni.run_noninteractive(ctx, args) == 1
|
||||
assert ctx.calls == []
|
||||
|
||||
@@ -111,7 +111,7 @@ def test_dispatch_quick_unknown_rule_returns_1(tmp_path):
|
||||
wl = tmp_path / "rockyou.txt"
|
||||
wl.write_text("password\n")
|
||||
ctx = _spy_ctx(tmp_path)
|
||||
args = SimpleNamespace(command="quick", wordlist=str(wl), rules=["ghost.rule"])
|
||||
args = SimpleNamespace(command="quick", wordlist=str(wl), rule_files=["ghost.rule"])
|
||||
assert ni.run_noninteractive(ctx, args) == 1
|
||||
assert ctx.calls == []
|
||||
|
||||
@@ -154,7 +154,7 @@ def test_dispatch_quick_multiple_rules_runs_each_as_separate_pass(tmp_path):
|
||||
wl.write_text("password\n")
|
||||
ctx = _spy_ctx(tmp_path)
|
||||
args = SimpleNamespace(
|
||||
command="quick", wordlist=str(wl), rules=["best64.rule", "d3ad0ne.rule"]
|
||||
command="quick", wordlist=str(wl), rule_files=["best64.rule", "d3ad0ne.rule"]
|
||||
)
|
||||
assert ni.run_noninteractive(ctx, args) == 0
|
||||
assert len(ctx.calls) == 2
|
||||
@@ -215,3 +215,41 @@ def test_main_quick_bad_hashtype_errors(monkeypatch, tmp_path):
|
||||
monkeypatch, ["quick", str(hf), "notanumber", "--wordlist", str(wl)]
|
||||
)
|
||||
assert code != 0
|
||||
|
||||
|
||||
def test_main_quick_with_rules_dispatches(monkeypatch, tmp_path):
|
||||
hf = _make_ntlm_hashfile(tmp_path)
|
||||
wl = tmp_path / "rockyou.txt"
|
||||
wl.write_text("password\n")
|
||||
rules_dir = tmp_path / "rules"
|
||||
rules_dir.mkdir()
|
||||
(rules_dir / "best64.rule").write_text(":\n")
|
||||
monkeypatch.setattr(hc_main, "rulesDirectory", str(rules_dir))
|
||||
calls = []
|
||||
monkeypatch.setattr(
|
||||
hc_main, "hcatQuickDictionary", lambda *a, **k: calls.append(a)
|
||||
)
|
||||
code = _run_main(
|
||||
monkeypatch,
|
||||
["quick", str(hf), "1000", "--wordlist", str(wl), "--rules", "best64.rule"],
|
||||
)
|
||||
assert code == 0
|
||||
assert len(calls) == 1
|
||||
# the rule chain (4th positional arg) must reference best64.rule
|
||||
assert "best64.rule" in calls[0][2]
|
||||
|
||||
|
||||
def test_main_debug_flag_before_subcommand(monkeypatch, tmp_path):
|
||||
hf = _make_ntlm_hashfile(tmp_path)
|
||||
wl = tmp_path / "w.txt"
|
||||
wl.write_text("x\n")
|
||||
calls = []
|
||||
monkeypatch.setattr(
|
||||
hc_main, "hcatQuickDictionary", lambda *a, **k: calls.append(a)
|
||||
)
|
||||
code = _run_main(
|
||||
monkeypatch,
|
||||
["--debug", "quick", str(hf), "1000", "--wordlist", str(wl)],
|
||||
)
|
||||
assert code == 0
|
||||
assert len(calls) == 1
|
||||
|
||||
Reference in New Issue
Block a user