fix(ui): route ollama/omen submenus through interactive_menu; re-prompt pickers on invalid input

- ollama_attack: convert generation-mode menu to interactive_menu with
  dynamic items list (option 3 only present when cracked file exists);
  cancel via 99 or Escape; re-prompts in a while loop
- omen_attack: convert existing-model menu to interactive_menu; cancel
  via 99 or Escape (replaces hard-coded "3. Cancel"); re-prompts in loop
- _omen_pick_training_wordlist: replace abort-on-invalid with re-prompt
  loop; add explicit 'q' cancel key so users can exit without being
  trapped; callers already handle None correctly
- _markov_pick_training_source: same re-prompt-loop + 'q' cancel fix;
  callers already handle None correctly
- Update all affected tests to patch interactive_menu at
  hate_crack.attacks.interactive_menu (module-level import path) and
  drive follow-up prompts via builtins.input
- Add new tests: arrow-menu reach, Escape/99 cancel, re-prompt loop
  coverage for both pickers, and conditional option-3 key presence

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-07-24 18:31:04 -04:00
co-authored by Claude
parent 39e0dd956a
commit 07ca6ba7e3
3 changed files with 326 additions and 125 deletions
+102 -87
View File
@@ -512,65 +512,73 @@ def bandrel_method(ctx: Any) -> None:
def ollama_attack(ctx: Any) -> None:
_notify.prompt_notify_for_attack("LLM")
print("\n\tLLM Attack")
print("\t1. Target info (company / industry / location)")
print("\t2. Wordlist (generate basewords from a sample wordlist)")
# Cracked-password mode is only offered when this session actually has
# plaintexts to learn from, matching _markov_pick_training_source.
out_path = f"{ctx.hcatHashFile}.out"
has_cracked = os.path.isfile(out_path) and os.path.getsize(out_path) > 0
if has_cracked:
print("\t3. Cracked passwords (current session)")
choice = input("\n\tSelect generation mode: ").strip()
if choice == "1":
company = input("Company name: ").strip()
industry = input("Industry: ").strip()
location = input("Location: ").strip()
ctx.hcatOllama(
ctx.hcatHashType,
ctx.hcatHashFile,
"target",
{"company": company, "industry": industry, "location": location},
)
elif choice == "2":
path = _omen_pick_training_wordlist(ctx, title="LLM Sample Wordlists")
if not path:
items: list[tuple[str, str]] = [
("1", "Target info (company / industry / location)"),
("2", "Wordlist (generate basewords from a sample wordlist)"),
]
if has_cracked:
items.append(("3", "Cracked passwords (current session)"))
items.append(("99", "Cancel"))
while True:
choice = interactive_menu(items, title="\nLLM Attack", prompt="\n\tSelect generation mode: ")
if choice is None or choice == "99":
return
if choice == "1":
company = input("Company name: ").strip()
industry = input("Industry: ").strip()
location = input("Location: ").strip()
ctx.hcatOllama(
ctx.hcatHashType,
ctx.hcatHashFile,
"target",
{"company": company, "industry": industry, "location": location},
)
return
elif choice == "2":
path = _omen_pick_training_wordlist(ctx, title="LLM Sample Wordlists")
if not path:
return
ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "wordlist", path)
return
elif choice == "3" and has_cracked:
ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "cracked", out_path)
return
ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "wordlist", path)
elif choice == "3" and has_cracked:
ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "cracked", out_path)
elif choice == "3":
print("\t[!] No cracked passwords yet — crack some hashes first.")
else:
print("\t[!] Invalid selection.")
def _omen_pick_training_wordlist(ctx: Any, title: str = "Training Wordlists"):
"""Show wordlist picker. Returns path or None."""
"""Show wordlist picker. Returns path or None (user cancelled with 'q')."""
wordlist_files = ctx.list_wordlist_files(ctx.hcatWordlists)
if wordlist_files:
entries = [f"{i}) {f}" for i, f in enumerate(wordlist_files, start=1)]
max_len = max((len(e) for e in entries), default=24)
print_multicolumn_list(
title,
entries,
min_col_width=max_len,
max_col_width=max_len,
)
print("\tp. Enter a custom path")
sel = input("\n\tSelect wordlist: ").strip()
if sel.lower() == "p":
path = input("\n\tPath to wordlist: ").strip()
return path if path else None
try:
idx = int(sel)
if 1 <= idx <= len(wordlist_files):
return os.path.join(ctx.hcatWordlists, wordlist_files[idx - 1])
except (ValueError, IndexError):
pass
print("\t[!] Invalid selection.")
return None
while True:
if wordlist_files:
entries = [f"{i}) {f}" for i, f in enumerate(wordlist_files, start=1)]
max_len = max((len(e) for e in entries), default=24)
print_multicolumn_list(
title,
entries,
min_col_width=max_len,
max_col_width=max_len,
)
print("\tp. Enter a custom path")
print("\tq. Cancel")
sel = input("\n\tSelect wordlist: ").strip()
if sel.lower() == "q":
return None
if sel.lower() == "p":
path = input("\n\tPath to wordlist: ").strip()
return path if path else None
try:
idx = int(sel)
if 1 <= idx <= len(wordlist_files):
return os.path.join(ctx.hcatWordlists, wordlist_files[idx - 1])
except (ValueError, IndexError):
pass
print("\t[!] Invalid selection.")
def omen_attack(ctx: Any) -> None:
@@ -592,16 +600,20 @@ def omen_attack(ctx: Any) -> None:
info = ctx._omen_model_info(model_dir)
trained_with = info.get("training_file", "unknown") if info else "unknown"
print(f"\n\tOMEN model found (trained with: {trained_with})")
print("\t1. Use existing model")
print("\t2. Train new model (overwrites existing)")
print("\t3. Cancel")
choice = input("\n\tChoice: ").strip()
if choice == "1":
need_training = False
elif choice == "3":
return
elif choice != "2":
return
model_items = [
("1", "Use existing model"),
("2", "Train new model (overwrites existing)"),
("99", "Cancel"),
]
while True:
choice = interactive_menu(model_items, title="\nOMEN Attack (Ordered Markov ENumerator)", prompt="\n\tChoice: ")
if choice is None or choice == "99":
return
if choice == "1":
need_training = False
break
elif choice == "2":
break
else:
print("\n\tNo valid OMEN model found. Training is required.")
@@ -628,38 +640,41 @@ def omen_attack(ctx: Any) -> None:
def _markov_pick_training_source(ctx: Any):
"""Prompt user to select markov training source. Returns file path or None."""
"""Prompt user to select markov training source. Returns file path or None (user cancelled with 'q')."""
out_path = f"{ctx.hcatHashFile}.out"
has_cracked = os.path.isfile(out_path) and os.path.getsize(out_path) > 0
wordlist_files = ctx.list_wordlist_files(ctx.hcatWordlists)
entries = []
if has_cracked:
entries.append("0) Cracked passwords (current session)")
entries.extend([f"{i}) {f}" for i, f in enumerate(wordlist_files, start=1)])
if entries:
max_len = max((len(e) for e in entries), default=24)
print_multicolumn_list(
"Markov Training Source",
entries,
min_col_width=max_len,
max_col_width=max_len,
)
print("\tp. Enter a custom path")
sel = input("\n\tSelect training source: ").strip()
if sel == "0" and has_cracked:
return out_path
if sel.lower() == "p":
path = input("\n\tPath to training file: ").strip()
return path if path else None
try:
idx = int(sel)
if 1 <= idx <= len(wordlist_files):
return os.path.join(ctx.hcatWordlists, wordlist_files[idx - 1])
except (ValueError, IndexError):
pass
print("\t[!] Invalid selection.")
return None
while True:
entries = []
if has_cracked:
entries.append("0) Cracked passwords (current session)")
entries.extend([f"{i}) {f}" for i, f in enumerate(wordlist_files, start=1)])
if entries:
max_len = max((len(e) for e in entries), default=24)
print_multicolumn_list(
"Markov Training Source",
entries,
min_col_width=max_len,
max_col_width=max_len,
)
print("\tp. Enter a custom path")
print("\tq. Cancel")
sel = input("\n\tSelect training source: ").strip()
if sel.lower() == "q":
return None
if sel == "0" and has_cracked:
return out_path
if sel.lower() == "p":
path = input("\n\tPath to training file: ").strip()
return path if path else None
try:
idx = int(sel)
if 1 <= idx <= len(wordlist_files):
return os.path.join(ctx.hcatWordlists, wordlist_files[idx - 1])
except (ValueError, IndexError):
pass
print("\t[!] Invalid selection.")
def adhoc_mask_crack(ctx: Any) -> None:
+184 -24
View File
@@ -329,7 +329,10 @@ class TestOllamaAttack:
def test_calls_hcatOllama_with_context(self) -> None:
ctx = _make_ctx()
with patch("builtins.input", side_effect=["1", "ACME", "tech", "NYC"]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["ACME", "tech", "NYC"]),
):
ollama_attack(ctx)
ctx.hcatOllama.assert_called_once_with(
@@ -342,7 +345,10 @@ class TestOllamaAttack:
def test_passes_hash_type_and_file(self) -> None:
ctx = _make_ctx(hash_type="1800", hash_file="/tmp/sha512.txt")
with patch("builtins.input", side_effect=["1", "Corp", "finance", "London"]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["Corp", "finance", "London"]),
):
ollama_attack(ctx)
call_args = ctx.hcatOllama.call_args[0]
@@ -352,7 +358,10 @@ class TestOllamaAttack:
def test_strips_whitespace_from_inputs(self) -> None:
ctx = _make_ctx()
with patch("builtins.input", side_effect=["1", " ACME ", " tech ", " NYC "]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=[" ACME ", " tech ", " NYC "]),
):
ollama_attack(ctx)
target_info = ctx.hcatOllama.call_args[0][3]
@@ -363,7 +372,10 @@ class TestOllamaAttack:
def test_target_string_is_literal_target(self) -> None:
ctx = _make_ctx()
with patch("builtins.input", side_effect=["1", "X", "Y", "Z"]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["X", "Y", "Z"]),
):
ollama_attack(ctx)
assert ctx.hcatOllama.call_args[0][2] == "target"
@@ -373,30 +385,44 @@ class TestOllamaAttack:
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
ctx.hcatWordlists = "/tmp/wl"
# mode "2", then pick wordlist "1"
with patch("builtins.input", side_effect=["2", "1"]):
# mode "2" from interactive_menu, then pick wordlist "1" via input
with (
patch("hate_crack.attacks.interactive_menu", return_value="2"),
patch("builtins.input", side_effect=["1"]),
):
ollama_attack(ctx)
args = ctx.hcatOllama.call_args[0]
assert args[2] == "wordlist"
assert args[3].endswith("rockyou.txt")
def test_invalid_mode_does_not_call_hcatOllama(self) -> None:
def test_escape_cancels_without_calling_hcatOllama(self) -> None:
"""None from interactive_menu (Escape / 99) cancels the attack."""
ctx = _make_ctx()
with patch("builtins.input", side_effect=["9"]):
with patch("hate_crack.attacks.interactive_menu", return_value=None):
ollama_attack(ctx)
ctx.hcatOllama.assert_not_called()
def test_cancel_key_cancels_without_calling_hcatOllama(self) -> None:
"""Selecting '99' (Cancel) cancels the attack."""
ctx = _make_ctx()
with patch("hate_crack.attacks.interactive_menu", return_value="99"):
ollama_attack(ctx)
ctx.hcatOllama.assert_not_called()
def test_wordlist_mode_aborts_when_no_wordlist_picked(self) -> None:
ctx = _make_ctx()
# mode "2", then an invalid picker selection -> picker returns None
# mode "2" from interactive_menu, then user cancels the file picker
ctx.list_wordlist_files.return_value = []
with patch("builtins.input", side_effect=["2", "nonsense"]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="2"),
patch("builtins.input", side_effect=["q"]),
):
ollama_attack(ctx)
ctx.hcatOllama.assert_not_called()
def test_cracked_mode_offered_when_out_file_has_content(
self, tmp_path: Path, capsys
self, tmp_path: Path
) -> None:
hash_file = tmp_path / "hashes.txt"
hash_file.touch()
@@ -404,43 +430,63 @@ class TestOllamaAttack:
out_file.write_text("hash:Summer2024!\n")
ctx = _make_ctx(hash_file=str(hash_file))
with patch("builtins.input", side_effect=["3"]):
captured_items: list[list[tuple[str, str]]] = []
def capture_menu(items, **kwargs):
captured_items.append(list(items))
return "3"
with patch("hate_crack.attacks.interactive_menu", side_effect=capture_menu):
ollama_attack(ctx)
assert "3. Cracked passwords" in capsys.readouterr().out
# Option 3 must be present in the items list when cracked file exists
keys = [k for k, _ in captured_items[0]]
assert "3" in keys
ctx.hcatOllama.assert_called_once_with(
ctx.hcatHashType, str(hash_file), "cracked", str(out_file)
)
def test_cracked_mode_not_offered_when_out_file_missing(
self, tmp_path: Path, capsys
self, tmp_path: Path
) -> None:
"""Option 3 must NOT appear in items when no cracked file exists."""
hash_file = tmp_path / "hashes.txt"
hash_file.touch()
ctx = _make_ctx(hash_file=str(hash_file))
with patch("builtins.input", side_effect=["3"]):
captured_items: list[list[tuple[str, str]]] = []
def capture_menu(items, **kwargs):
captured_items.append(list(items))
return "99" # cancel
with patch("hate_crack.attacks.interactive_menu", side_effect=capture_menu):
ollama_attack(ctx)
out = capsys.readouterr().out
assert "3. Cracked passwords" not in out
assert "No cracked passwords yet" in out
keys = [k for k, _ in captured_items[0]]
assert "3" not in keys
ctx.hcatOllama.assert_not_called()
def test_cracked_mode_not_offered_when_out_file_empty(
self, tmp_path: Path, capsys
self, tmp_path: Path
) -> None:
"""Option 3 must NOT appear in items when cracked file exists but is empty."""
hash_file = tmp_path / "hashes.txt"
hash_file.touch()
(tmp_path / "hashes.txt.out").touch() # exists but zero bytes
ctx = _make_ctx(hash_file=str(hash_file))
with patch("builtins.input", side_effect=["3"]):
captured_items: list[list[tuple[str, str]]] = []
def capture_menu(items, **kwargs):
captured_items.append(list(items))
return "99" # cancel
with patch("hate_crack.attacks.interactive_menu", side_effect=capture_menu):
ollama_attack(ctx)
out = capsys.readouterr().out
assert "3. Cracked passwords" not in out
assert "No cracked passwords yet" in out
keys = [k for k, _ in captured_items[0]]
assert "3" not in keys
ctx.hcatOllama.assert_not_called()
def test_target_and_wordlist_modes_unaffected_by_cracked_option(
@@ -452,7 +498,121 @@ class TestOllamaAttack:
(tmp_path / "hashes.txt.out").write_text("hash:Summer2024!\n")
ctx = _make_ctx(hash_file=str(hash_file))
with patch("builtins.input", side_effect=["1", "ACME", "tech", "NYC"]):
with (
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["ACME", "tech", "NYC"]),
):
ollama_attack(ctx)
assert ctx.hcatOllama.call_args[0][2] == "target"
def test_arrow_menu_env_reaches_ollama_attack(self) -> None:
"""HATE_CRACK_ARROW_MENU=1 routes through interactive_menu in ollama_attack."""
import os
from hate_crack.attacks import interactive_menu as real_im
ctx = _make_ctx()
calls: list[tuple] = []
def spy_menu(items, **kwargs):
calls.append(tuple(items))
return "99" # cancel immediately
with (
patch("hate_crack.attacks.interactive_menu", side_effect=spy_menu),
):
ollama_attack(ctx)
# interactive_menu was called — confirms the arrow-menu code path is reachable
assert len(calls) == 1
keys = [k for k, _ in calls[0]]
assert "1" in keys
assert "2" in keys
assert "99" in keys
class TestOmenPickTrainingWordlistReprompt:
"""_omen_pick_training_wordlist re-prompts on invalid input instead of aborting."""
def _make_ctx(self, wordlist_files=None):
ctx = MagicMock()
ctx.list_wordlist_files.return_value = wordlist_files or ["rockyou.txt"]
ctx.hcatWordlists = "/tmp/wl"
ctx.hcatHashFile = "/tmp/hashes.txt"
return ctx
def test_invalid_input_reprompts_then_valid_pick(self) -> None:
from hate_crack.attacks import _omen_pick_training_wordlist
ctx = self._make_ctx(["rockyou.txt"])
# First input is invalid, second is valid
with patch("builtins.input", side_effect=["bad", "1"]):
result = _omen_pick_training_wordlist(ctx)
assert result is not None
assert "rockyou.txt" in result
def test_cancel_with_q_returns_none(self) -> None:
from hate_crack.attacks import _omen_pick_training_wordlist
ctx = self._make_ctx(["rockyou.txt"])
with patch("builtins.input", return_value="q"):
result = _omen_pick_training_wordlist(ctx)
assert result is None
def test_multiple_invalid_inputs_then_cancel(self) -> None:
from hate_crack.attacks import _omen_pick_training_wordlist
ctx = self._make_ctx(["rockyou.txt"])
with patch("builtins.input", side_effect=["99", "abc", "q"]):
result = _omen_pick_training_wordlist(ctx)
assert result is None
class TestMarkovPickTrainingSourceReprompt:
"""_markov_pick_training_source re-prompts on invalid input instead of aborting."""
def _make_ctx(self, tmp_path, has_cracked=False, wordlist_files=None):
ctx = MagicMock()
hash_file = str(tmp_path / "hashes.txt")
ctx.hcatHashFile = hash_file
ctx.list_wordlist_files.return_value = wordlist_files or ["rockyou.txt"]
ctx.hcatWordlists = str(tmp_path / "wordlists")
if has_cracked:
(tmp_path / "hashes.txt.out").write_text("cracked_pw\n")
return ctx
def test_invalid_input_reprompts_then_valid_pick(self, tmp_path: Path) -> None:
from hate_crack.attacks import _markov_pick_training_source
ctx = self._make_ctx(tmp_path, wordlist_files=["rockyou.txt"])
with patch("builtins.input", side_effect=["bad", "1"]):
result = _markov_pick_training_source(ctx)
assert result is not None
assert "rockyou.txt" in result
def test_cancel_with_q_returns_none(self, tmp_path: Path) -> None:
from hate_crack.attacks import _markov_pick_training_source
ctx = self._make_ctx(tmp_path)
with patch("builtins.input", return_value="q"):
result = _markov_pick_training_source(ctx)
assert result is None
def test_multiple_invalid_then_cancel(self, tmp_path: Path) -> None:
from hate_crack.attacks import _markov_pick_training_source
ctx = self._make_ctx(tmp_path, wordlist_files=["rockyou.txt"])
with patch("builtins.input", side_effect=["99", "abc", "q"]):
result = _markov_pick_training_source(ctx)
assert result is None
def test_caller_handles_none_correctly(self, tmp_path: Path) -> None:
"""markov_brute_force returns early without crashing when picker returns None."""
from hate_crack.attacks import markov_brute_force
ctx = self._make_ctx(tmp_path, wordlist_files=["rockyou.txt"])
# No .hcstat2 file → goes straight to picker; user cancels
with patch("builtins.input", return_value="q"):
markov_brute_force(ctx)
ctx.hcatMarkovTrain.assert_not_called()
ctx.hcatMarkovBruteForce.assert_not_called()
+40 -14
View File
@@ -277,8 +277,10 @@ class TestOmenAttackHandler:
def test_use_existing_model(self, tmp_path):
ctx = self._make_ctx(tmp_path, model_valid=True)
self._setup_rules_dir(tmp_path)
with patch("os.path.isfile", return_value=True), patch(
"builtins.input", side_effect=["1", "", "0"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["", "0"]),
):
from hate_crack.attacks import omen_attack
@@ -289,8 +291,10 @@ class TestOmenAttackHandler:
def test_train_new_model_with_wordlist_pick(self, tmp_path):
ctx = self._make_ctx(tmp_path, model_valid=True)
self._setup_rules_dir(tmp_path)
with patch("os.path.isfile", return_value=True), patch(
"builtins.input", side_effect=["2", "1", "", "0"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="2"),
patch("builtins.input", side_effect=["1", "", "0"]),
):
from hate_crack.attacks import omen_attack
@@ -302,8 +306,22 @@ class TestOmenAttackHandler:
def test_cancel_aborts(self, tmp_path):
ctx = self._make_ctx(tmp_path, model_valid=True)
with patch("os.path.isfile", return_value=True), patch(
"builtins.input", side_effect=["3"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="99"),
):
from hate_crack.attacks import omen_attack
omen_attack(ctx)
ctx.hcatOmenTrain.assert_not_called()
ctx.hcatOmen.assert_not_called()
def test_escape_cancels(self, tmp_path):
"""None from interactive_menu (Escape) cancels the attack."""
ctx = self._make_ctx(tmp_path, model_valid=True)
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value=None),
):
from hate_crack.attacks import omen_attack
@@ -348,8 +366,10 @@ class TestOmenAttackHandler:
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"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["", "1"]),
):
from hate_crack.attacks import omen_attack
@@ -361,8 +381,10 @@ class TestOmenAttackHandler:
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"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["", "1,2"]),
):
from hate_crack.attacks import omen_attack
@@ -372,8 +394,10 @@ class TestOmenAttackHandler:
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"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["", "99"]),
):
from hate_crack.attacks import omen_attack
@@ -383,8 +407,10 @@ class TestOmenAttackHandler:
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"]
with (
patch("os.path.isfile", return_value=True),
patch("hate_crack.attacks.interactive_menu", return_value="1"),
patch("builtins.input", side_effect=["", "0"]),
):
from hate_crack.attacks import omen_attack