mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
feat: add rule support to OMEN attack and fix relative path resolution
Extract _select_rules() helper from quick_crack/loopback_attack and wire it into omen_attack so OMEN can run with rule chains. Extend hcatOmen() to accept and apply an hcatChains argument including debug mode injection. Fix resolve_path() to honour HATE_CRACK_ORIG_CWD (set by the install shim) so relative hash/wordlist paths resolve against the caller's working directory instead of the repo root. Increase default omenMaxCandidates to 50M.
This commit is contained in:
@@ -99,7 +99,7 @@ install: submodules
|
|||||||
@UV_BIN=$$(command -v uv 2>/dev/null || echo "$$HOME/.local/bin/uv"); \
|
@UV_BIN=$$(command -v uv 2>/dev/null || echo "$$HOME/.local/bin/uv"); \
|
||||||
"$$UV_BIN" sync
|
"$$UV_BIN" sync
|
||||||
@mkdir -p "$${XDG_BIN_HOME:-$$HOME/.local/bin}"
|
@mkdir -p "$${XDG_BIN_HOME:-$$HOME/.local/bin}"
|
||||||
@printf '#!/usr/bin/env bash\nset -euo pipefail\nexec uv run --directory %s python -m hate_crack "$$@"\n' "$(CURDIR)" \
|
@printf '#!/usr/bin/env bash\nset -euo pipefail\nexport HATE_CRACK_ORIG_CWD="$$PWD"\nexec uv run --directory %s python -m hate_crack "$$@"\n' "$(CURDIR)" \
|
||||||
> "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
> "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
@chmod +x "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
@chmod +x "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
@echo "Installed hate_crack shim to $${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
@echo "Installed hate_crack shim to $${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
@@ -107,7 +107,7 @@ install: submodules
|
|||||||
update: submodules
|
update: submodules
|
||||||
@uv sync
|
@uv sync
|
||||||
@mkdir -p "$${XDG_BIN_HOME:-$$HOME/.local/bin}"
|
@mkdir -p "$${XDG_BIN_HOME:-$$HOME/.local/bin}"
|
||||||
@printf '#!/usr/bin/env bash\nset -euo pipefail\nexec uv run --directory %s python -m hate_crack "$$@"\n' "$(CURDIR)" \
|
@printf '#!/usr/bin/env bash\nset -euo pipefail\nexport HATE_CRACK_ORIG_CWD="$$PWD"\nexec uv run --directory %s python -m hate_crack "$$@"\n' "$(CURDIR)" \
|
||||||
> "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
> "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
@chmod +x "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
@chmod +x "$${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
@echo "Updated hate_crack shim at $${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
@echo "Updated hate_crack shim at $${XDG_BIN_HOME:-$$HOME/.local/bin}/hate_crack"
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
"ollamaModel": "mistral",
|
"ollamaModel": "mistral",
|
||||||
"ollamaNumCtx": 2048,
|
"ollamaNumCtx": 2048,
|
||||||
"omenTrainingList": "rockyou.txt",
|
"omenTrainingList": "rockyou.txt",
|
||||||
"omenMaxCandidates": 1000000,
|
"omenMaxCandidates": 50000000,
|
||||||
"check_for_updates": true,
|
"check_for_updates": true,
|
||||||
"optimizedKernelAttacks": ["hcatDictionary", "hcatQuickDictionary", "hcatBandrel", "hcatGoodMeasure", "hcatRecycle", "hcatBruteForce", "hcatTopMask", "hcatPathwellBruteForce"]
|
"optimizedKernelAttacks": ["hcatDictionary", "hcatQuickDictionary", "hcatBandrel", "hcatGoodMeasure", "hcatRecycle", "hcatBruteForce", "hcatTopMask", "hcatPathwellBruteForce"]
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-152
@@ -24,10 +24,88 @@ def _configure_readline(completer):
|
|||||||
readline.set_completer(completer)
|
readline.set_completer(completer)
|
||||||
|
|
||||||
|
|
||||||
|
def _select_rules(ctx) -> list[str] | None:
|
||||||
|
"""Prompt user to select rules. Returns list of rule chain strings, or None if cancelled."""
|
||||||
|
rule_choice = None
|
||||||
|
selected_rules = []
|
||||||
|
|
||||||
|
rules_dir = ctx.rulesDirectory
|
||||||
|
rule_files = sorted(f for f in os.listdir(rules_dir) if f != ".DS_Store")
|
||||||
|
if not rule_files:
|
||||||
|
download_rules = (
|
||||||
|
input("\nNo rules found. Download rules from Hashmob now? (Y/n): ")
|
||||||
|
.strip()
|
||||||
|
.lower()
|
||||||
|
)
|
||||||
|
if download_rules in ("", "y", "yes"):
|
||||||
|
download_hashmob_rules(print_fn=print, rules_dir=rules_dir)
|
||||||
|
rule_files = sorted(os.listdir(rules_dir))
|
||||||
|
|
||||||
|
if not rule_files:
|
||||||
|
print("No rules available. Proceeding without rules.")
|
||||||
|
return [""]
|
||||||
|
|
||||||
|
print("\nWhich rule(s) would you like to run?")
|
||||||
|
rule_entries = ["0. To run without any rules"]
|
||||||
|
rule_entries.extend([f"{i}. {file}" for i, file in enumerate(rule_files, start=1)])
|
||||||
|
rule_entries.append("98. YOLO...run all of the rules")
|
||||||
|
rule_entries.append("99. Back to Main Menu")
|
||||||
|
max_rule_len = max((len(e) for e in rule_entries), default=26)
|
||||||
|
print_multicolumn_list(
|
||||||
|
"Available Rules",
|
||||||
|
rule_entries,
|
||||||
|
min_col_width=max_rule_len,
|
||||||
|
max_col_width=max_rule_len,
|
||||||
|
)
|
||||||
|
|
||||||
|
example_line = ""
|
||||||
|
if len(rule_files) >= 2:
|
||||||
|
example_line = f"For example 1+1 will run {rule_files[0]} chained twice and 1,2 would run {rule_files[0]} and then {rule_files[1]} sequentially.\n"
|
||||||
|
elif len(rule_files) == 1:
|
||||||
|
example_line = f"For example 1+1 will run {rule_files[0]} chained twice.\n"
|
||||||
|
|
||||||
|
while rule_choice is None:
|
||||||
|
raw_choice = input(
|
||||||
|
"Enter Comma separated list of rules you would like to run. To run rules chained use the + symbol.\n"
|
||||||
|
f"{example_line}"
|
||||||
|
"Choose wisely: "
|
||||||
|
)
|
||||||
|
if raw_choice.strip() == "99":
|
||||||
|
return None
|
||||||
|
if raw_choice != "":
|
||||||
|
rule_choice = raw_choice.split(",")
|
||||||
|
|
||||||
|
if "99" in rule_choice:
|
||||||
|
return None
|
||||||
|
if "98" in rule_choice:
|
||||||
|
for rule in rule_files:
|
||||||
|
selected_rules.append(f"-r {os.path.join(rules_dir, rule)}")
|
||||||
|
elif "0" in rule_choice:
|
||||||
|
selected_rules = [""]
|
||||||
|
else:
|
||||||
|
for choice in rule_choice:
|
||||||
|
if "+" in choice:
|
||||||
|
combined_choice = ""
|
||||||
|
choices = choice.split("+")
|
||||||
|
for rule in choices:
|
||||||
|
try:
|
||||||
|
rule_path = os.path.join(rules_dir, rule_files[int(rule) - 1])
|
||||||
|
combined_choice = f"{combined_choice} -r {rule_path}"
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
selected_rules.append(combined_choice)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
rule_path = os.path.join(rules_dir, rule_files[int(choice) - 1])
|
||||||
|
selected_rules.append(f"-r {rule_path}")
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return selected_rules
|
||||||
|
|
||||||
|
|
||||||
def quick_crack(ctx: Any) -> None:
|
def quick_crack(ctx: Any) -> None:
|
||||||
wordlist_choice = None
|
wordlist_choice = None
|
||||||
rule_choice = None
|
|
||||||
selected_hcatRules = []
|
|
||||||
|
|
||||||
wordlist_files = ctx.list_wordlist_files(ctx.hcatWordlists)
|
wordlist_files = ctx.list_wordlist_files(ctx.hcatWordlists)
|
||||||
wordlist_entries = [
|
wordlist_entries = [
|
||||||
@@ -85,81 +163,11 @@ def quick_crack(ctx: Any) -> None:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
print("Please enter a valid number.")
|
print("Please enter a valid number.")
|
||||||
|
|
||||||
rules_dir = ctx.rulesDirectory
|
selected_rules = _select_rules(ctx)
|
||||||
rule_files = sorted(f for f in os.listdir(rules_dir) if f != ".DS_Store")
|
if selected_rules is None:
|
||||||
if not rule_files:
|
|
||||||
download_rules = (
|
|
||||||
input("\nNo rules found. Download rules from Hashmob now? (Y/n): ")
|
|
||||||
.strip()
|
|
||||||
.lower()
|
|
||||||
)
|
|
||||||
if download_rules in ("", "y", "yes"):
|
|
||||||
download_hashmob_rules(print_fn=print, rules_dir=rules_dir)
|
|
||||||
rule_files = sorted(os.listdir(rules_dir))
|
|
||||||
|
|
||||||
if not rule_files:
|
|
||||||
print("No rules available. Proceeding without rules.")
|
|
||||||
rule_choice = ["0"]
|
|
||||||
else:
|
|
||||||
print("\nWhich rule(s) would you like to run?")
|
|
||||||
rule_entries = ["0. To run without any rules"]
|
|
||||||
rule_entries.extend(
|
|
||||||
[f"{i}. {file}" for i, file in enumerate(rule_files, start=1)]
|
|
||||||
)
|
|
||||||
rule_entries.append("98. YOLO...run all of the rules")
|
|
||||||
rule_entries.append("99. Back to Main Menu")
|
|
||||||
max_rule_len = max((len(e) for e in rule_entries), default=26)
|
|
||||||
print_multicolumn_list(
|
|
||||||
"Available Rules",
|
|
||||||
rule_entries,
|
|
||||||
min_col_width=max_rule_len,
|
|
||||||
max_col_width=max_rule_len,
|
|
||||||
)
|
|
||||||
|
|
||||||
example_line = ""
|
|
||||||
if len(rule_files) >= 2:
|
|
||||||
example_line = f"For example 1+1 will run {rule_files[0]} chained twice and 1,2 would run {rule_files[0]} and then {rule_files[1]} sequentially.\n"
|
|
||||||
elif len(rule_files) == 1:
|
|
||||||
example_line = f"For example 1+1 will run {rule_files[0]} chained twice.\n"
|
|
||||||
|
|
||||||
while rule_choice is None:
|
|
||||||
raw_choice = input(
|
|
||||||
"Enter Comma separated list of rules you would like to run. To run rules chained use the + symbol.\n"
|
|
||||||
f"{example_line}"
|
|
||||||
"Choose wisely: "
|
|
||||||
)
|
|
||||||
if raw_choice.strip() == "99":
|
|
||||||
return
|
|
||||||
if raw_choice != "":
|
|
||||||
rule_choice = raw_choice.split(",")
|
|
||||||
|
|
||||||
if "99" in rule_choice:
|
|
||||||
return
|
return
|
||||||
if "98" in rule_choice:
|
|
||||||
for rule in rule_files:
|
|
||||||
selected_hcatRules.append(f"-r {os.path.join(rules_dir, rule)}")
|
|
||||||
elif "0" in rule_choice:
|
|
||||||
selected_hcatRules = [""]
|
|
||||||
else:
|
|
||||||
for choice in rule_choice:
|
|
||||||
if "+" in choice:
|
|
||||||
combined_choice = ""
|
|
||||||
choices = choice.split("+")
|
|
||||||
for rule in choices:
|
|
||||||
try:
|
|
||||||
rule_path = os.path.join(rules_dir, rule_files[int(rule) - 1])
|
|
||||||
combined_choice = f"{combined_choice} -r {rule_path}"
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
selected_hcatRules.append(combined_choice)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
rule_path = os.path.join(rules_dir, rule_files[int(choice) - 1])
|
|
||||||
selected_hcatRules.append(f"-r {rule_path}")
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for chain in selected_hcatRules:
|
for chain in selected_rules:
|
||||||
ctx.hcatQuickDictionary(
|
ctx.hcatQuickDictionary(
|
||||||
ctx.hcatHashType, ctx.hcatHashFile, chain, wordlist_choice
|
ctx.hcatHashType, ctx.hcatHashFile, chain, wordlist_choice
|
||||||
)
|
)
|
||||||
@@ -174,84 +182,11 @@ def loopback_attack(ctx: Any) -> None:
|
|||||||
|
|
||||||
print(f"\nUsing loopback attack with wordlist: {empty_wordlist}")
|
print(f"\nUsing loopback attack with wordlist: {empty_wordlist}")
|
||||||
|
|
||||||
rule_choice = None
|
selected_rules = _select_rules(ctx)
|
||||||
selected_hcatRules = []
|
if selected_rules is None:
|
||||||
|
|
||||||
rules_dir = ctx.rulesDirectory
|
|
||||||
rule_files = sorted(f for f in os.listdir(rules_dir) if f != ".DS_Store")
|
|
||||||
if not rule_files:
|
|
||||||
download_rules = (
|
|
||||||
input("\nNo rules found. Download rules from Hashmob now? (Y/n): ")
|
|
||||||
.strip()
|
|
||||||
.lower()
|
|
||||||
)
|
|
||||||
if download_rules in ("", "y", "yes"):
|
|
||||||
download_hashmob_rules(print_fn=print, rules_dir=rules_dir)
|
|
||||||
rule_files = sorted(os.listdir(rules_dir))
|
|
||||||
|
|
||||||
if not rule_files:
|
|
||||||
print("No rules available. Proceeding without rules.")
|
|
||||||
rule_choice = ["0"]
|
|
||||||
else:
|
|
||||||
print("\nWhich rule(s) would you like to run?")
|
|
||||||
rule_entries = ["0. To run without any rules"]
|
|
||||||
rule_entries.extend(
|
|
||||||
[f"{i}. {file}" for i, file in enumerate(rule_files, start=1)]
|
|
||||||
)
|
|
||||||
rule_entries.append("98. YOLO...run all of the rules")
|
|
||||||
rule_entries.append("99. Back to Main Menu")
|
|
||||||
max_rule_len = max((len(e) for e in rule_entries), default=26)
|
|
||||||
print_multicolumn_list(
|
|
||||||
"Available Rules",
|
|
||||||
rule_entries,
|
|
||||||
min_col_width=max_rule_len,
|
|
||||||
max_col_width=max_rule_len,
|
|
||||||
)
|
|
||||||
|
|
||||||
example_line = ""
|
|
||||||
if len(rule_files) >= 2:
|
|
||||||
example_line = f"For example 1+1 will run {rule_files[0]} chained twice and 1,2 would run {rule_files[0]} and then {rule_files[1]} sequentially.\n"
|
|
||||||
elif len(rule_files) == 1:
|
|
||||||
example_line = f"For example 1+1 will run {rule_files[0]} chained twice.\n"
|
|
||||||
|
|
||||||
while rule_choice is None:
|
|
||||||
raw_choice = input(
|
|
||||||
"Enter Comma separated list of rules you would like to run. To run rules chained use the + symbol.\n"
|
|
||||||
f"{example_line}"
|
|
||||||
"Choose wisely: "
|
|
||||||
)
|
|
||||||
if raw_choice.strip() == "99":
|
|
||||||
return
|
|
||||||
if raw_choice != "":
|
|
||||||
rule_choice = raw_choice.split(",")
|
|
||||||
|
|
||||||
if "99" in rule_choice:
|
|
||||||
return
|
return
|
||||||
if "98" in rule_choice:
|
|
||||||
for rule in rule_files:
|
|
||||||
selected_hcatRules.append(f"-r {os.path.join(rules_dir, rule)}")
|
|
||||||
elif "0" in rule_choice:
|
|
||||||
selected_hcatRules = [""]
|
|
||||||
else:
|
|
||||||
for choice in rule_choice:
|
|
||||||
if "+" in choice:
|
|
||||||
combined_choice = ""
|
|
||||||
choices = choice.split("+")
|
|
||||||
for rule in choices:
|
|
||||||
try:
|
|
||||||
rule_path = os.path.join(rules_dir, rule_files[int(rule) - 1])
|
|
||||||
combined_choice = f"{combined_choice} -r {rule_path}"
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
selected_hcatRules.append(combined_choice)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
rule_path = os.path.join(rules_dir, rule_files[int(choice) - 1])
|
|
||||||
selected_hcatRules.append(f"-r {rule_path}")
|
|
||||||
except IndexError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for chain in selected_hcatRules:
|
for chain in selected_rules:
|
||||||
ctx.hcatQuickDictionary(
|
ctx.hcatQuickDictionary(
|
||||||
ctx.hcatHashType,
|
ctx.hcatHashType,
|
||||||
ctx.hcatHashFile,
|
ctx.hcatHashFile,
|
||||||
@@ -580,4 +515,10 @@ def omen_attack(ctx: Any) -> None:
|
|||||||
).strip()
|
).strip()
|
||||||
if not max_candidates:
|
if not max_candidates:
|
||||||
max_candidates = str(ctx.omenMaxCandidates)
|
max_candidates = str(ctx.omenMaxCandidates)
|
||||||
ctx.hcatOmen(ctx.hcatHashType, ctx.hcatHashFile, int(max_candidates))
|
|
||||||
|
selected_rules = _select_rules(ctx)
|
||||||
|
if selected_rules is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for chain in selected_rules:
|
||||||
|
ctx.hcatOmen(ctx.hcatHashType, ctx.hcatHashFile, int(max_candidates), chain)
|
||||||
|
|||||||
+14
-2
@@ -5,10 +5,22 @@ from typing import Optional
|
|||||||
|
|
||||||
|
|
||||||
def resolve_path(value: Optional[str]) -> Optional[str]:
|
def resolve_path(value: Optional[str]) -> Optional[str]:
|
||||||
"""Expand user and return an absolute path, or None."""
|
"""Expand user and return an absolute path, or None.
|
||||||
|
|
||||||
|
When invoked via the bash shim (``uv run --directory``), the working
|
||||||
|
directory is changed to the repo root before Python starts.
|
||||||
|
``HATE_CRACK_ORIG_CWD`` preserves the caller's real working directory
|
||||||
|
so that relative paths on the command line resolve correctly.
|
||||||
|
"""
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
return os.path.abspath(os.path.expanduser(value))
|
value = os.path.expanduser(value)
|
||||||
|
if os.path.isabs(value):
|
||||||
|
return value
|
||||||
|
orig_cwd = os.environ.get("HATE_CRACK_ORIG_CWD")
|
||||||
|
if orig_cwd:
|
||||||
|
return os.path.normpath(os.path.join(orig_cwd, value))
|
||||||
|
return os.path.abspath(value)
|
||||||
|
|
||||||
|
|
||||||
def add_common_args(parser) -> None:
|
def add_common_args(parser) -> None:
|
||||||
|
|||||||
+4
-1
@@ -2181,7 +2181,7 @@ def hcatOmenTrain(training_file):
|
|||||||
|
|
||||||
|
|
||||||
# OMEN Attack - Generate candidates and pipe to hashcat
|
# OMEN Attack - Generate candidates and pipe to hashcat
|
||||||
def hcatOmen(hcatHashType, hcatHashFile, max_candidates):
|
def hcatOmen(hcatHashType, hcatHashFile, max_candidates, hcatChains=""):
|
||||||
global hcatProcess
|
global hcatProcess
|
||||||
omen_dir = _omen_dir
|
omen_dir = _omen_dir
|
||||||
enum_bin = os.path.join(omen_dir, hcatOmenEnumBin)
|
enum_bin = os.path.join(omen_dir, hcatOmenEnumBin)
|
||||||
@@ -2205,8 +2205,11 @@ def hcatOmen(hcatHashType, hcatHashFile, max_candidates):
|
|||||||
"-o",
|
"-o",
|
||||||
f"{hcatHashFile}.out",
|
f"{hcatHashFile}.out",
|
||||||
]
|
]
|
||||||
|
if hcatChains:
|
||||||
|
hashcat_cmd.extend(shlex.split(hcatChains))
|
||||||
hashcat_cmd.extend(shlex.split(hcatTuning))
|
hashcat_cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(hashcat_cmd)
|
_append_potfile_arg(hashcat_cmd)
|
||||||
|
hashcat_cmd = _add_debug_mode_for_rules(hashcat_cmd)
|
||||||
print(f"[*] Running: {_format_cmd(enum_cmd)} | {_format_cmd(hashcat_cmd)}")
|
print(f"[*] Running: {_format_cmd(enum_cmd)} | {_format_cmd(hashcat_cmd)}")
|
||||||
_debug_cmd(hashcat_cmd)
|
_debug_cmd(hashcat_cmd)
|
||||||
enum_proc = subprocess.Popen(
|
enum_proc = subprocess.Popen(
|
||||||
|
|||||||
+169
-5
@@ -252,10 +252,11 @@ class TestOmenAttackHandler:
|
|||||||
ctx.hcatOmenCreateBin = "createNG"
|
ctx.hcatOmenCreateBin = "createNG"
|
||||||
ctx.hcatOmenEnumBin = "enumNG"
|
ctx.hcatOmenEnumBin = "enumNG"
|
||||||
ctx.omenTrainingList = "/default/rockyou.txt"
|
ctx.omenTrainingList = "/default/rockyou.txt"
|
||||||
ctx.omenMaxCandidates = 1000000
|
ctx.omenMaxCandidates = 50000000
|
||||||
ctx.hcatHashType = "1000"
|
ctx.hcatHashType = "1000"
|
||||||
ctx.hcatHashFile = "/tmp/hashes.txt"
|
ctx.hcatHashFile = "/tmp/hashes.txt"
|
||||||
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
||||||
|
ctx.rulesDirectory = str(tmp_path / "rules")
|
||||||
ctx._omen_model_is_valid.return_value = model_valid
|
ctx._omen_model_is_valid.return_value = model_valid
|
||||||
ctx._omen_model_info.return_value = (
|
ctx._omen_model_info.return_value = (
|
||||||
{"training_file": "/old/rockyou.txt"} if model_valid else None
|
{"training_file": "/old/rockyou.txt"} if model_valid else None
|
||||||
@@ -265,10 +266,19 @@ class TestOmenAttackHandler:
|
|||||||
ctx.list_wordlist_files.return_value = ["rockyou.txt", "custom.txt"]
|
ctx.list_wordlist_files.return_value = ["rockyou.txt", "custom.txt"]
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
def _setup_rules_dir(self, tmp_path, rule_names=None):
|
||||||
|
rules_dir = tmp_path / "rules"
|
||||||
|
rules_dir.mkdir(exist_ok=True)
|
||||||
|
if rule_names:
|
||||||
|
for name in rule_names:
|
||||||
|
(rules_dir / name).write_text(":")
|
||||||
|
return rules_dir
|
||||||
|
|
||||||
def test_use_existing_model(self, tmp_path):
|
def test_use_existing_model(self, tmp_path):
|
||||||
ctx = self._make_ctx(tmp_path, model_valid=True)
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path)
|
||||||
with patch("os.path.isfile", return_value=True), patch(
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
"builtins.input", side_effect=["1", ""]
|
"builtins.input", side_effect=["1", "", "0"]
|
||||||
):
|
):
|
||||||
from hate_crack.attacks import omen_attack
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
@@ -278,8 +288,9 @@ class TestOmenAttackHandler:
|
|||||||
|
|
||||||
def test_train_new_model_with_wordlist_pick(self, tmp_path):
|
def test_train_new_model_with_wordlist_pick(self, tmp_path):
|
||||||
ctx = self._make_ctx(tmp_path, model_valid=True)
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path)
|
||||||
with patch("os.path.isfile", return_value=True), patch(
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
"builtins.input", side_effect=["2", "1", ""]
|
"builtins.input", side_effect=["2", "1", "", "0"]
|
||||||
):
|
):
|
||||||
from hate_crack.attacks import omen_attack
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
@@ -302,8 +313,9 @@ class TestOmenAttackHandler:
|
|||||||
|
|
||||||
def test_no_model_goes_straight_to_training(self, tmp_path):
|
def test_no_model_goes_straight_to_training(self, tmp_path):
|
||||||
ctx = self._make_ctx(tmp_path, model_valid=False)
|
ctx = self._make_ctx(tmp_path, model_valid=False)
|
||||||
|
self._setup_rules_dir(tmp_path)
|
||||||
with patch("os.path.isfile", return_value=True), patch(
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
"builtins.input", side_effect=["1", ""]
|
"builtins.input", side_effect=["1", "", "0"]
|
||||||
):
|
):
|
||||||
from hate_crack.attacks import omen_attack
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
@@ -324,14 +336,166 @@ class TestOmenAttackHandler:
|
|||||||
|
|
||||||
def test_custom_path_for_training(self, tmp_path):
|
def test_custom_path_for_training(self, tmp_path):
|
||||||
ctx = self._make_ctx(tmp_path, model_valid=False)
|
ctx = self._make_ctx(tmp_path, model_valid=False)
|
||||||
|
self._setup_rules_dir(tmp_path)
|
||||||
with patch("os.path.isfile", return_value=True), patch(
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
"builtins.input", side_effect=["p", "/custom/wordlist.txt", ""]
|
"builtins.input", side_effect=["p", "/custom/wordlist.txt", "", "0"]
|
||||||
):
|
):
|
||||||
from hate_crack.attacks import omen_attack
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
omen_attack(ctx)
|
omen_attack(ctx)
|
||||||
ctx.hcatOmenTrain.assert_called_once_with("/custom/wordlist.txt")
|
ctx.hcatOmenTrain.assert_called_once_with("/custom/wordlist.txt")
|
||||||
|
|
||||||
|
def test_rules_passed_to_hcatOmen(self, tmp_path):
|
||||||
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path, ["best64.rule"])
|
||||||
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
|
"builtins.input", side_effect=["1", "", "1"]
|
||||||
|
):
|
||||||
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
|
omen_attack(ctx)
|
||||||
|
call_args = ctx.hcatOmen.call_args
|
||||||
|
assert "-r" in call_args[0][3]
|
||||||
|
assert "best64.rule" in call_args[0][3]
|
||||||
|
|
||||||
|
def test_multiple_rule_chains_spawn_multiple_calls(self, tmp_path):
|
||||||
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path, ["best64.rule", "dive.rule"])
|
||||||
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
|
"builtins.input", side_effect=["1", "", "1,2"]
|
||||||
|
):
|
||||||
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
|
omen_attack(ctx)
|
||||||
|
assert ctx.hcatOmen.call_count == 2
|
||||||
|
|
||||||
|
def test_cancel_from_rules_aborts(self, tmp_path):
|
||||||
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path, ["best64.rule"])
|
||||||
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
|
"builtins.input", side_effect=["1", "", "99"]
|
||||||
|
):
|
||||||
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
|
omen_attack(ctx)
|
||||||
|
ctx.hcatOmen.assert_not_called()
|
||||||
|
|
||||||
|
def test_no_rules_passes_empty_chain(self, tmp_path):
|
||||||
|
ctx = self._make_ctx(tmp_path, model_valid=True)
|
||||||
|
self._setup_rules_dir(tmp_path, ["best64.rule"])
|
||||||
|
with patch("os.path.isfile", return_value=True), patch(
|
||||||
|
"builtins.input", side_effect=["1", "", "0"]
|
||||||
|
):
|
||||||
|
from hate_crack.attacks import omen_attack
|
||||||
|
|
||||||
|
omen_attack(ctx)
|
||||||
|
ctx.hcatOmen.assert_called_once()
|
||||||
|
assert ctx.hcatOmen.call_args[0][3] == ""
|
||||||
|
|
||||||
|
|
||||||
|
class TestHcatOmenWithRules:
|
||||||
|
def test_rule_flags_appear_in_hashcat_command(self, main_module, tmp_path):
|
||||||
|
omen_dir = tmp_path / "omen"
|
||||||
|
omen_dir.mkdir()
|
||||||
|
enum_bin = omen_dir / "enumNG"
|
||||||
|
enum_bin.touch()
|
||||||
|
enum_bin.chmod(0o755)
|
||||||
|
model_dir = tmp_path / "model"
|
||||||
|
model_dir.mkdir()
|
||||||
|
(model_dir / "createConfig").write_text("# test config\n")
|
||||||
|
|
||||||
|
with patch.object(main_module, "_omen_dir", str(omen_dir)), \
|
||||||
|
patch.object(main_module, "hcatOmenEnumBin", "enumNG"), \
|
||||||
|
patch.object(main_module, "hcatBin", "hashcat"), \
|
||||||
|
patch.object(main_module, "hcatTuning", ""), \
|
||||||
|
patch.object(main_module, "hcatPotfilePath", ""), \
|
||||||
|
patch.object(main_module, "hcatDebugLogPath", str(tmp_path / "debug")), \
|
||||||
|
patch.object(main_module, "hcatHashFile", "/tmp/hashes.txt", create=True), \
|
||||||
|
patch("hate_crack.main._omen_model_dir", return_value=str(model_dir)), \
|
||||||
|
patch("hate_crack.main.subprocess.Popen") as mock_popen:
|
||||||
|
mock_enum_proc = MagicMock()
|
||||||
|
mock_enum_proc.stdout = MagicMock()
|
||||||
|
mock_enum_proc.stderr = MagicMock()
|
||||||
|
mock_enum_proc.stderr.read.return_value = b""
|
||||||
|
mock_enum_proc.returncode = 0
|
||||||
|
mock_enum_proc.wait.return_value = None
|
||||||
|
mock_hashcat_proc = MagicMock()
|
||||||
|
mock_hashcat_proc.wait.return_value = None
|
||||||
|
mock_popen.side_effect = [mock_enum_proc, mock_hashcat_proc]
|
||||||
|
|
||||||
|
main_module.hcatOmen("1000", "/tmp/hashes.txt", 500000, "-r /tmp/best64.rule")
|
||||||
|
|
||||||
|
hashcat_cmd = mock_popen.call_args_list[1][0][0]
|
||||||
|
assert "-r" in hashcat_cmd
|
||||||
|
assert "/tmp/best64.rule" in hashcat_cmd
|
||||||
|
|
||||||
|
def test_debug_mode_added_when_rules_present(self, main_module, tmp_path):
|
||||||
|
omen_dir = tmp_path / "omen"
|
||||||
|
omen_dir.mkdir()
|
||||||
|
enum_bin = omen_dir / "enumNG"
|
||||||
|
enum_bin.touch()
|
||||||
|
enum_bin.chmod(0o755)
|
||||||
|
model_dir = tmp_path / "model"
|
||||||
|
model_dir.mkdir()
|
||||||
|
(model_dir / "createConfig").write_text("# test config\n")
|
||||||
|
|
||||||
|
with patch.object(main_module, "_omen_dir", str(omen_dir)), \
|
||||||
|
patch.object(main_module, "hcatOmenEnumBin", "enumNG"), \
|
||||||
|
patch.object(main_module, "hcatBin", "hashcat"), \
|
||||||
|
patch.object(main_module, "hcatTuning", ""), \
|
||||||
|
patch.object(main_module, "hcatPotfilePath", ""), \
|
||||||
|
patch.object(main_module, "hcatDebugLogPath", str(tmp_path / "debug")), \
|
||||||
|
patch.object(main_module, "hcatHashFile", "/tmp/hashes.txt", create=True), \
|
||||||
|
patch("hate_crack.main._omen_model_dir", return_value=str(model_dir)), \
|
||||||
|
patch("hate_crack.main.subprocess.Popen") as mock_popen:
|
||||||
|
mock_enum_proc = MagicMock()
|
||||||
|
mock_enum_proc.stdout = MagicMock()
|
||||||
|
mock_enum_proc.stderr = MagicMock()
|
||||||
|
mock_enum_proc.stderr.read.return_value = b""
|
||||||
|
mock_enum_proc.returncode = 0
|
||||||
|
mock_enum_proc.wait.return_value = None
|
||||||
|
mock_hashcat_proc = MagicMock()
|
||||||
|
mock_hashcat_proc.wait.return_value = None
|
||||||
|
mock_popen.side_effect = [mock_enum_proc, mock_hashcat_proc]
|
||||||
|
|
||||||
|
main_module.hcatOmen("1000", "/tmp/hashes.txt", 500000, "-r /tmp/best64.rule")
|
||||||
|
|
||||||
|
hashcat_cmd = mock_popen.call_args_list[1][0][0]
|
||||||
|
assert "--debug-mode" in hashcat_cmd
|
||||||
|
|
||||||
|
def test_no_rules_no_debug_mode(self, main_module, tmp_path):
|
||||||
|
omen_dir = tmp_path / "omen"
|
||||||
|
omen_dir.mkdir()
|
||||||
|
enum_bin = omen_dir / "enumNG"
|
||||||
|
enum_bin.touch()
|
||||||
|
enum_bin.chmod(0o755)
|
||||||
|
model_dir = tmp_path / "model"
|
||||||
|
model_dir.mkdir()
|
||||||
|
(model_dir / "createConfig").write_text("# test config\n")
|
||||||
|
|
||||||
|
with patch.object(main_module, "_omen_dir", str(omen_dir)), \
|
||||||
|
patch.object(main_module, "hcatOmenEnumBin", "enumNG"), \
|
||||||
|
patch.object(main_module, "hcatBin", "hashcat"), \
|
||||||
|
patch.object(main_module, "hcatTuning", ""), \
|
||||||
|
patch.object(main_module, "hcatPotfilePath", ""), \
|
||||||
|
patch.object(main_module, "hcatHashFile", "/tmp/hashes.txt", create=True), \
|
||||||
|
patch("hate_crack.main._omen_model_dir", return_value=str(model_dir)), \
|
||||||
|
patch("hate_crack.main.subprocess.Popen") as mock_popen:
|
||||||
|
mock_enum_proc = MagicMock()
|
||||||
|
mock_enum_proc.stdout = MagicMock()
|
||||||
|
mock_enum_proc.stderr = MagicMock()
|
||||||
|
mock_enum_proc.stderr.read.return_value = b""
|
||||||
|
mock_enum_proc.returncode = 0
|
||||||
|
mock_enum_proc.wait.return_value = None
|
||||||
|
mock_hashcat_proc = MagicMock()
|
||||||
|
mock_hashcat_proc.wait.return_value = None
|
||||||
|
mock_popen.side_effect = [mock_enum_proc, mock_hashcat_proc]
|
||||||
|
|
||||||
|
main_module.hcatOmen("1000", "/tmp/hashes.txt", 500000)
|
||||||
|
|
||||||
|
hashcat_cmd = mock_popen.call_args_list[1][0][0]
|
||||||
|
assert "--debug-mode" not in hashcat_cmd
|
||||||
|
|
||||||
|
|
||||||
class TestOmenModelValidation:
|
class TestOmenModelValidation:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -15,6 +15,27 @@ def test_resolve_path_none_and_expand():
|
|||||||
assert os.path.isabs(resolved)
|
assert os.path.isabs(resolved)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_path_uses_orig_cwd_for_relative_paths(monkeypatch, tmp_path):
|
||||||
|
"""When HATE_CRACK_ORIG_CWD is set, relative paths resolve against it."""
|
||||||
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
||||||
|
result = cli.resolve_path("hashes.txt")
|
||||||
|
assert result == os.path.join(str(tmp_path), "hashes.txt")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_path_ignores_orig_cwd_for_absolute_paths(monkeypatch, tmp_path):
|
||||||
|
"""Absolute paths are returned as-is regardless of HATE_CRACK_ORIG_CWD."""
|
||||||
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
||||||
|
result = cli.resolve_path("/absolute/path/hashes.txt")
|
||||||
|
assert result == "/absolute/path/hashes.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_path_without_orig_cwd_uses_abspath(monkeypatch):
|
||||||
|
"""Without HATE_CRACK_ORIG_CWD, falls back to os.path.abspath."""
|
||||||
|
monkeypatch.delenv("HATE_CRACK_ORIG_CWD", raising=False)
|
||||||
|
result = cli.resolve_path("hashes.txt")
|
||||||
|
assert result == os.path.abspath("hashes.txt")
|
||||||
|
|
||||||
|
|
||||||
def test_setup_logging_adds_single_streamhandler(tmp_path):
|
def test_setup_logging_adds_single_streamhandler(tmp_path):
|
||||||
logger = logging.getLogger("hate_crack_test")
|
logger = logging.getLogger("hate_crack_test")
|
||||||
logger.handlers.clear()
|
logger.handlers.clear()
|
||||||
|
|||||||
Reference in New Issue
Block a user