feat: add OMEN attack as menu option 16

Add OMEN (Ordered Markov ENumerator) as a probability-ordered password
candidate generator. Trains n-gram models on leaked passwords via
createNG, then pipes candidates from enumNG into hashcat.

Also fix a pre-existing bug where ensure_binary() used quit(1) instead
of sys.exit(1) - quit() closes stdin before raising SystemExit, which
caused "ValueError: I/O operation on closed file" when any optional
binary check failed and the program continued to use input().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-02-17 14:01:58 -05:00
parent 5c3eee9f04
commit 0991701024
9 changed files with 299 additions and 9 deletions

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "HashcatRosetta"] [submodule "HashcatRosetta"]
path = HashcatRosetta path = HashcatRosetta
url = https://github.com/bandrel/HashcatRosetta.git url = https://github.com/bandrel/HashcatRosetta.git
[submodule "omen"]
path = omen
url = https://github.com/RUB-SysSec/OMEN.git

View File

@@ -24,6 +24,7 @@ submodules-pre:
@# Ensure required directories exist (whether as submodules or vendored copies). @# Ensure required directories exist (whether as submodules or vendored copies).
@test -d hashcat-utils || { echo "Error: missing required directory: hashcat-utils"; exit 1; } @test -d hashcat-utils || { echo "Error: missing required directory: hashcat-utils"; exit 1; }
@test -d princeprocessor || { echo "Error: missing required directory: princeprocessor"; exit 1; } @test -d princeprocessor || { echo "Error: missing required directory: princeprocessor"; exit 1; }
@test -d omen || { echo "Warning: missing directory: omen (OMEN attacks will not be available)"; }
@# Keep per-length expander sources in sync (expander8.c..expander24.c). @# Keep per-length expander sources in sync (expander8.c..expander24.c).
@# Patch hashcat-utils/src/Makefile so these new expanders are compiled by default. @# Patch hashcat-utils/src/Makefile so these new expanders are compiled by default.
@bases="hashcat-utils hate_crack/hashcat-utils"; for base in $$bases; do src="$$base/src/expander.c"; test -f "$$src" || continue; for i in $$(seq 8 36); do dst="$$base/src/expander$$i.c"; if [ ! -f "$$dst" ]; then cp "$$src" "$$dst"; perl -pi -e "s/#define LEN_MAX 7/#define LEN_MAX $$i/g" "$$dst"; fi; done; mk="$$base/src/Makefile"; test -f "$$mk" || continue; exp_bins=""; exp_exes=""; for i in $$(seq 8 36); do exp_bins="$$exp_bins expander$$i.bin"; exp_exes="$$exp_exes expander$$i.exe"; done; EXP_BINS="$$exp_bins" perl -pi -e 'if(/^native:/ && index($$_, "expander8.bin") < 0){chomp; $$_ .= "$$ENV{EXP_BINS}"; $$_ .= "\n";}' "$$mk"; EXP_EXES="$$exp_exes" perl -pi -e 'if(/^windows:/ && index($$_, "expander8.exe") < 0){chomp; $$_ .= "$$ENV{EXP_EXES}"; $$_ .= "\n";}' "$$mk"; perl -0777 -pi -e 's/\n# Auto-added by hate_crack \\(submodules-pre\\)\n.*\z/\n/s' "$$mk"; printf '%s\n' '' '# Auto-added by hate_crack (submodules-pre)' 'expander%.bin: src/expander%.c' >> "$$mk"; printf '\t%s\n' '$${CC_NATIVE} $${CFLAGS_NATIVE} $${LDFLAGS_NATIVE} -o bin/$$@ $$<' >> "$$mk"; printf '%s\n' '' 'expander%.exe: src/expander%.c' >> "$$mk"; printf '\t%s\n' '$${CC_WINDOWS} $${CFLAGS_WINDOWS} -o bin/$$@ $$<' >> "$$mk"; done @bases="hashcat-utils hate_crack/hashcat-utils"; for base in $$bases; do src="$$base/src/expander.c"; test -f "$$src" || continue; for i in $$(seq 8 36); do dst="$$base/src/expander$$i.c"; if [ ! -f "$$dst" ]; then cp "$$src" "$$dst"; perl -pi -e "s/#define LEN_MAX 7/#define LEN_MAX $$i/g" "$$dst"; fi; done; mk="$$base/src/Makefile"; test -f "$$mk" || continue; exp_bins=""; exp_exes=""; for i in $$(seq 8 36); do exp_bins="$$exp_bins expander$$i.bin"; exp_exes="$$exp_exes expander$$i.exe"; done; EXP_BINS="$$exp_bins" perl -pi -e 'if(/^native:/ && index($$_, "expander8.bin") < 0){chomp; $$_ .= "$$ENV{EXP_BINS}"; $$_ .= "\n";}' "$$mk"; EXP_EXES="$$exp_exes" perl -pi -e 'if(/^windows:/ && index($$_, "expander8.exe") < 0){chomp; $$_ .= "$$ENV{EXP_EXES}"; $$_ .= "\n";}' "$$mk"; perl -0777 -pi -e 's/\n# Auto-added by hate_crack \\(submodules-pre\\)\n.*\z/\n/s' "$$mk"; printf '%s\n' '' '# Auto-added by hate_crack (submodules-pre)' 'expander%.bin: src/expander%.c' >> "$$mk"; printf '\t%s\n' '$${CC_NATIVE} $${CFLAGS_NATIVE} $${LDFLAGS_NATIVE} -o bin/$$@ $$<' >> "$$mk"; printf '%s\n' '' 'expander%.exe: src/expander%.c' >> "$$mk"; printf '\t%s\n' '$${CC_WINDOWS} $${CFLAGS_WINDOWS} -o bin/$$@ $$<' >> "$$mk"; done
@@ -34,14 +35,18 @@ vendor-assets:
exit 1; \ exit 1; \
fi fi
@echo "Syncing assets into package for uv tool install..." @echo "Syncing assets into package for uv tool install..."
@rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor @rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
@cp -R hashcat-utils hate_crack/ @cp -R hashcat-utils hate_crack/
@cp -R princeprocessor hate_crack/ @cp -R princeprocessor hate_crack/
@if [ -d omen ]; then \
cp -R omen hate_crack/; \
rm -rf hate_crack/omen/.git; \
fi
@rm -rf hate_crack/hashcat-utils/.git hate_crack/princeprocessor/.git @rm -rf hate_crack/hashcat-utils/.git hate_crack/princeprocessor/.git
clean-vendor: clean-vendor:
@echo "Cleaning up vendored assets from working tree..." @echo "Cleaning up vendored assets from working tree..."
@rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor @rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
install: submodules vendor-assets install: submodules vendor-assets
@echo "Detecting OS and installing dependencies..." @echo "Detecting OS and installing dependencies..."

View File

@@ -24,5 +24,7 @@
"hashview_api_key": "", "hashview_api_key": "",
"hashmob_api_key": "", "hashmob_api_key": "",
"ollamaModel": "mistral", "ollamaModel": "mistral",
"ollamaNumCtx": 2048 "ollamaNumCtx": 2048,
"omenTrainingList": "rockyou.txt",
"omenMaxCandidates": 1000000
} }

View File

@@ -84,6 +84,7 @@ def get_main_menu_options():
"13": _attacks.bandrel_method, "13": _attacks.bandrel_method,
"14": _attacks.loopback_attack, "14": _attacks.loopback_attack,
"15": _attacks.ollama_attack, "15": _attacks.ollama_attack,
"16": _attacks.omen_attack,
"90": download_hashmob_rules, "90": download_hashmob_rules,
"91": weakpass_wordlist_menu, "91": weakpass_wordlist_menu,
"92": download_hashmob_wordlists, "92": download_hashmob_wordlists,

View File

@@ -499,3 +499,23 @@ def ollama_attack(ctx: Any) -> None:
"location": location, "location": location,
} }
ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "target", target_info) ctx.hcatOllama(ctx.hcatHashType, ctx.hcatHashFile, "target", target_info)
def omen_attack(ctx: Any) -> None:
print("\n\tOMEN Attack (Ordered Markov ENumerator)")
omen_dir = os.path.join(ctx.hate_path, "omen")
model_exists = os.path.isfile(os.path.join(omen_dir, "IP.level"))
if not model_exists:
print("\n\tNo OMEN model found. Training is required before generation.")
training_source = input(
"\n\tTraining source (path to password list, or press Enter for default): "
).strip()
if not training_source:
training_source = ctx.omenTrainingList
ctx.hcatOmenTrain(training_source)
max_candidates = input(
f"\n\tMax candidates to generate ({ctx.omenMaxCandidates}): "
).strip()
if not max_candidates:
max_candidates = str(ctx.omenMaxCandidates)
ctx.hcatOmen(ctx.hcatHashType, ctx.hcatHashFile, int(max_candidates))

View File

@@ -212,19 +212,19 @@ def ensure_binary(binary_path, build_dir=None, name=None):
"\nRun 'make install' from the repository directory to install with assets:" "\nRun 'make install' from the repository directory to install with assets:"
) )
print(" cd /path/to/hate_crack && make install") print(" cd /path/to/hate_crack && make install")
quit(1) sys.exit(1)
# Binary missing - need to build # Binary missing - need to build
print(f"Error: {name or 'binary'} not found at {binary_path}.") print(f"Error: {name or 'binary'} not found at {binary_path}.")
print("\nPlease build the utilities by running:") print("\nPlease build the utilities by running:")
print(f" cd {build_dir} && make") print(f" cd {build_dir} && make")
print("\nEnsure build tools (gcc, make) are installed on your system.") print("\nEnsure build tools (gcc, make) are installed on your system.")
quit(1) sys.exit(1)
else: else:
print( print(
f"Error: {name or binary_path} not found or not executable at {binary_path}." f"Error: {name or binary_path} not found or not executable at {binary_path}."
) )
quit(1) sys.exit(1)
return binary_path return binary_path
@@ -468,10 +468,31 @@ except KeyError as e:
) )
ollamaNumCtx = int(default_config.get("ollamaNumCtx", 2048)) ollamaNumCtx = int(default_config.get("ollamaNumCtx", 2048))
try:
omenTrainingList = config_parser["omenTrainingList"]
except KeyError as e:
print(
"{0} is not defined in config.json using defaults from config.json.example".format(
e
)
)
omenTrainingList = default_config.get("omenTrainingList", "rockyou.txt")
try:
omenMaxCandidates = int(config_parser["omenMaxCandidates"])
except KeyError as e:
print(
"{0} is not defined in config.json using defaults from config.json.example".format(
e
)
)
omenMaxCandidates = int(default_config.get("omenMaxCandidates", 1000000))
hcatExpanderBin = "expander.bin" hcatExpanderBin = "expander.bin"
hcatCombinatorBin = "combinator.bin" hcatCombinatorBin = "combinator.bin"
hcatPrinceBin = "pp64.bin" hcatPrinceBin = "pp64.bin"
hcatHcstat2genBin = "hcstat2gen.bin" hcatHcstat2genBin = "hcstat2gen.bin"
hcatOmenCreateBin = "createNG"
hcatOmenEnumBin = "enumNG"
def _resolve_wordlist_path(wordlist, base_dir): def _resolve_wordlist_path(wordlist, base_dir):
@@ -606,6 +627,7 @@ hcatGoodMeasureBaseList = _normalize_wordlist_setting(
hcatGoodMeasureBaseList, wordlists_dir hcatGoodMeasureBaseList, wordlists_dir
) )
hcatPrinceBaseList = _normalize_wordlist_setting(hcatPrinceBaseList, wordlists_dir) hcatPrinceBaseList = _normalize_wordlist_setting(hcatPrinceBaseList, wordlists_dir)
omenTrainingList = _normalize_wordlist_setting(omenTrainingList, wordlists_dir)
if not SKIP_INIT: if not SKIP_INIT:
# Verify hashcat binary is available # Verify hashcat binary is available
# hcatBin should be in PATH or be an absolute path (resolved from hcatPath + hcatBin if configured) # hcatBin should be in PATH or be an absolute path (resolved from hcatPath + hcatBin if configured)
@@ -615,14 +637,14 @@ if not SKIP_INIT:
print( print(
f"Hashcat binary not found at {hcatBin}. Please check configuration and try again." f"Hashcat binary not found at {hcatBin}. Please check configuration and try again."
) )
quit(1) sys.exit(1)
else: else:
# hcatBin should be in PATH # hcatBin should be in PATH
if shutil.which(hcatBin) is None: if shutil.which(hcatBin) is None:
print( print(
f'Hashcat binary "{hcatBin}" not found in PATH. Please check configuration and try again.' f'Hashcat binary "{hcatBin}" not found in PATH. Please check configuration and try again.'
) )
quit(1) sys.exit(1)
# Verify hashcat-utils binaries exist and work # Verify hashcat-utils binaries exist and work
# Note: hashcat-utils is part of hate_crack repo, not hashcat installation # Note: hashcat-utils is part of hate_crack repo, not hashcat installation
@@ -656,7 +678,7 @@ if not SKIP_INIT:
print(f"Error: {name} binary at {binary_path} failed to execute: {e}") print(f"Error: {name} binary at {binary_path} failed to execute: {e}")
print("The binary may be compiled for the wrong architecture.") print("The binary may be compiled for the wrong architecture.")
print("Try recompiling hashcat-utils for your system.") print("Try recompiling hashcat-utils for your system.")
quit(1) sys.exit(1)
# Verify princeprocessor binary # Verify princeprocessor binary
# Note: princeprocessor is part of hate_crack repo, not hashcat installation # Note: princeprocessor is part of hate_crack repo, not hashcat installation
@@ -682,6 +704,23 @@ if not SKIP_INIT:
except SystemExit: except SystemExit:
print("LLM attacks will not be available.") print("LLM attacks will not be available.")
# Verify OMEN binaries (optional, for OMEN attack)
omen_create_path = os.path.join(hate_path, "omen", hcatOmenCreateBin)
omen_enum_path = os.path.join(hate_path, "omen", hcatOmenEnumBin)
try:
ensure_binary(
omen_create_path,
build_dir=os.path.join(hate_path, "omen"),
name="OMEN createNG",
)
ensure_binary(
omen_enum_path,
build_dir=os.path.join(hate_path, "omen"),
name="OMEN enumNG",
)
except SystemExit:
print("OMEN attacks will not be available.")
except Exception as e: except Exception as e:
print(f"Module initialization error: {e}") print(f"Module initialization error: {e}")
if not shutil.which("hashcat") and not os.path.exists("/usr/bin/hashcat"): if not shutil.which("hashcat") and not os.path.exists("/usr/bin/hashcat"):
@@ -2072,6 +2111,67 @@ def hcatPrince(hcatHashType, hcatHashFile):
prince_proc.kill() prince_proc.kill()
# OMEN Attack - Train model
def hcatOmenTrain(training_file):
omen_dir = os.path.join(hate_path, "omen")
create_bin = os.path.join(omen_dir, hcatOmenCreateBin)
if not os.path.isfile(create_bin):
print(f"Error: OMEN createNG binary not found: {create_bin}")
return
if not os.path.isfile(training_file):
print(f"Error: Training file not found: {training_file}")
return
print(f"Training OMEN model with: {training_file}")
cmd = [create_bin, "--iPwdList", training_file]
print(f"[*] Running: {_format_cmd(cmd)}")
proc = subprocess.Popen(cmd, cwd=omen_dir)
try:
proc.wait()
except KeyboardInterrupt:
print("Killing PID {0}...".format(str(proc.pid)))
proc.kill()
return
if proc.returncode == 0:
print("OMEN model training complete.")
else:
print(f"OMEN training failed with exit code {proc.returncode}")
# OMEN Attack - Generate candidates and pipe to hashcat
def hcatOmen(hcatHashType, hcatHashFile, max_candidates):
global hcatProcess
omen_dir = os.path.join(hate_path, "omen")
enum_bin = os.path.join(omen_dir, hcatOmenEnumBin)
if not os.path.isfile(enum_bin):
print(f"Error: OMEN enumNG binary not found: {enum_bin}")
return
enum_cmd = [enum_bin, "-p", "-m", str(max_candidates)]
hashcat_cmd = [
hcatBin,
"-m",
hcatHashType,
hcatHashFile,
"--session",
generate_session_id(),
"-o",
f"{hcatHashFile}.out",
]
hashcat_cmd.extend(shlex.split(hcatTuning))
_append_potfile_arg(hashcat_cmd)
print(f"[*] Running: {_format_cmd(enum_cmd)} | {_format_cmd(hashcat_cmd)}")
_debug_cmd(hashcat_cmd)
enum_proc = subprocess.Popen(enum_cmd, cwd=omen_dir, stdout=subprocess.PIPE)
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=enum_proc.stdout)
enum_proc.stdout.close()
try:
hcatProcess.wait()
enum_proc.wait()
except KeyboardInterrupt:
print("Killing PID {0}...".format(str(hcatProcess.pid)))
hcatProcess.kill()
enum_proc.kill()
# Extra - Good Measure # Extra - Good Measure
def hcatGoodMeasure(hcatHashType, hcatHashFile): def hcatGoodMeasure(hcatHashType, hcatHashFile):
global hcatExtraCount global hcatExtraCount
@@ -2987,6 +3087,10 @@ def ollama_attack():
return _attacks.ollama_attack(_attack_ctx()) return _attacks.ollama_attack(_attack_ctx())
def omen_attack():
return _attacks.omen_attack(_attack_ctx())
# convert hex words for recycling # convert hex words for recycling
def convert_hex(working_file): def convert_hex(working_file):
processed_words = [] processed_words = []
@@ -3215,6 +3319,7 @@ def get_main_menu_options():
"13": bandrel_method, "13": bandrel_method,
"14": loopback_attack, "14": loopback_attack,
"15": ollama_attack, "15": ollama_attack,
"16": omen_attack,
"90": download_hashmob_rules, "90": download_hashmob_rules,
"91": analyze_rules, "91": analyze_rules,
"92": download_hashmob_wordlists, "92": download_hashmob_wordlists,
@@ -3867,6 +3972,7 @@ def main():
print("\t(13) Bandrel Methodology") print("\t(13) Bandrel Methodology")
print("\t(14) Loopback Attack") print("\t(14) Loopback Attack")
print("\t(15) LLM Attack") print("\t(15) LLM Attack")
print("\t(16) OMEN Attack")
print("\n\t(90) Download rules from Hashmob.net") print("\n\t(90) Download rules from Hashmob.net")
print("\n\t(91) Analyze Hashcat Rules") print("\n\t(91) Analyze Hashcat Rules")
print("\t(92) Download wordlists from Hashmob.net") print("\t(92) Download wordlists from Hashmob.net")

1
omen Submodule

Submodule omen added at 10aa99e30b

151
tests/test_omen_attack.py Normal file
View File

@@ -0,0 +1,151 @@
import os
from unittest.mock import MagicMock, patch
import pytest
@pytest.fixture
def main_module(hc_module):
"""Return the underlying hate_crack.main module for direct patching."""
return hc_module._main
class TestHcatOmenTrain:
def test_builds_correct_command(self, main_module, tmp_path):
training_file = tmp_path / "passwords.txt"
training_file.write_text("password123\nletmein\n")
omen_dir = tmp_path / "omen"
omen_dir.mkdir()
create_bin = omen_dir / "createNG"
create_bin.touch()
create_bin.chmod(0o755)
with patch.object(main_module, "hate_path", str(tmp_path)), patch.object(
main_module, "hcatOmenCreateBin", "createNG"
), patch("hate_crack.main.subprocess.Popen") as mock_popen:
mock_proc = MagicMock()
mock_proc.wait.return_value = None
mock_proc.returncode = 0
mock_popen.return_value = mock_proc
main_module.hcatOmenTrain(str(training_file))
mock_popen.assert_called_once()
cmd = mock_popen.call_args[0][0]
assert cmd[0] == str(create_bin)
assert "--iPwdList" in cmd
assert str(training_file) in cmd
def test_missing_binary(self, main_module, tmp_path, capsys):
training_file = tmp_path / "passwords.txt"
training_file.write_text("test\n")
with patch.object(main_module, "hate_path", str(tmp_path)), patch.object(
main_module, "hcatOmenCreateBin", "createNG"
):
main_module.hcatOmenTrain(str(training_file))
captured = capsys.readouterr()
assert "createNG binary not found" in captured.out
def test_missing_training_file(self, main_module, tmp_path, capsys):
omen_dir = tmp_path / "omen"
omen_dir.mkdir()
create_bin = omen_dir / "createNG"
create_bin.touch()
with patch.object(main_module, "hate_path", str(tmp_path)), patch.object(
main_module, "hcatOmenCreateBin", "createNG"
):
main_module.hcatOmenTrain("/nonexistent/file.txt")
captured = capsys.readouterr()
assert "Training file not found" in captured.out
class TestHcatOmen:
def test_builds_correct_pipe_commands(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)
with patch.object(main_module, "hate_path", str(tmp_path)), patch.object(
main_module, "hcatOmenEnumBin", "enumNG"
), patch.object(main_module, "hcatBin", "hashcat"), patch.object(
main_module, "hcatTuning", "--force"
), patch.object(
main_module, "hcatPotfilePath", ""
), patch.object(
main_module, "hcatHashFile", "/tmp/hashes.txt", create=True
), patch(
"hate_crack.main.subprocess.Popen"
) as mock_popen:
mock_enum_proc = MagicMock()
mock_enum_proc.stdout = MagicMock()
mock_hashcat_proc = MagicMock()
mock_hashcat_proc.wait.return_value = None
mock_enum_proc.wait.return_value = None
mock_popen.side_effect = [mock_enum_proc, mock_hashcat_proc]
main_module.hcatOmen("1000", "/tmp/hashes.txt", 500000)
assert mock_popen.call_count == 2
# First call: enumNG
enum_cmd = mock_popen.call_args_list[0][0][0]
assert enum_cmd[0] == str(enum_bin)
assert "-p" in enum_cmd
assert "-m" in enum_cmd
assert "500000" in enum_cmd
# Second call: hashcat
hashcat_cmd = mock_popen.call_args_list[1][0][0]
assert hashcat_cmd[0] == "hashcat"
assert "1000" in hashcat_cmd
assert "/tmp/hashes.txt" in hashcat_cmd
def test_missing_binary(self, main_module, tmp_path, capsys):
with patch.object(main_module, "hate_path", str(tmp_path)), patch.object(
main_module, "hcatOmenEnumBin", "enumNG"
):
main_module.hcatOmen("1000", "/tmp/hashes.txt", 500000)
captured = capsys.readouterr()
assert "enumNG binary not found" in captured.out
class TestOmenAttackHandler:
def test_prompts_and_calls_hcatOmen(self):
ctx = MagicMock()
ctx.hate_path = "/fake/path"
ctx.omenTrainingList = "/fake/rockyou.txt"
ctx.omenMaxCandidates = 1000000
ctx.hcatHashType = "1000"
ctx.hcatHashFile = "/tmp/hashes.txt"
with patch("os.path.isfile", return_value=True), patch(
"builtins.input", return_value=""
):
from hate_crack.attacks import omen_attack
omen_attack(ctx)
ctx.hcatOmen.assert_called_once_with("1000", "/tmp/hashes.txt", 1000000)
def test_trains_when_no_model(self):
ctx = MagicMock()
ctx.hate_path = "/fake/path"
ctx.omenTrainingList = "/fake/rockyou.txt"
ctx.omenMaxCandidates = 1000000
ctx.hcatHashType = "1000"
ctx.hcatHashFile = "/tmp/hashes.txt"
def fake_isfile(path):
return "IP.level" not in path
with patch("os.path.isfile", side_effect=fake_isfile), patch(
"builtins.input", return_value=""
):
from hate_crack.attacks import omen_attack
omen_attack(ctx)
ctx.hcatOmenTrain.assert_called_once_with("/fake/rockyou.txt")
ctx.hcatOmen.assert_called_once()

View File

@@ -26,6 +26,7 @@ MENU_OPTION_TEST_CASES = [
("13", CLI_MODULE._attacks, "bandrel_method", "bandrel"), ("13", CLI_MODULE._attacks, "bandrel_method", "bandrel"),
("14", CLI_MODULE._attacks, "loopback_attack", "loopback"), ("14", CLI_MODULE._attacks, "loopback_attack", "loopback"),
("15", CLI_MODULE._attacks, "ollama_attack", "ollama"), ("15", CLI_MODULE._attacks, "ollama_attack", "ollama"),
("16", CLI_MODULE._attacks, "omen_attack", "omen"),
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"), ("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"), ("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"), ("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),