mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-03-12 21:23:05 -07:00
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:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "HashcatRosetta"]
|
||||
path = HashcatRosetta
|
||||
url = https://github.com/bandrel/HashcatRosetta.git
|
||||
[submodule "omen"]
|
||||
path = omen
|
||||
url = https://github.com/RUB-SysSec/OMEN.git
|
||||
|
||||
9
Makefile
9
Makefile
@@ -24,6 +24,7 @@ submodules-pre:
|
||||
@# 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 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).
|
||||
@# 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
|
||||
@@ -34,14 +35,18 @@ vendor-assets:
|
||||
exit 1; \
|
||||
fi
|
||||
@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 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
|
||||
|
||||
clean-vendor:
|
||||
@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
|
||||
@echo "Detecting OS and installing dependencies..."
|
||||
|
||||
@@ -24,5 +24,7 @@
|
||||
"hashview_api_key": "",
|
||||
"hashmob_api_key": "",
|
||||
"ollamaModel": "mistral",
|
||||
"ollamaNumCtx": 2048
|
||||
"ollamaNumCtx": 2048,
|
||||
"omenTrainingList": "rockyou.txt",
|
||||
"omenMaxCandidates": 1000000
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ def get_main_menu_options():
|
||||
"13": _attacks.bandrel_method,
|
||||
"14": _attacks.loopback_attack,
|
||||
"15": _attacks.ollama_attack,
|
||||
"16": _attacks.omen_attack,
|
||||
"90": download_hashmob_rules,
|
||||
"91": weakpass_wordlist_menu,
|
||||
"92": download_hashmob_wordlists,
|
||||
|
||||
@@ -499,3 +499,23 @@ def ollama_attack(ctx: Any) -> None:
|
||||
"location": location,
|
||||
}
|
||||
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))
|
||||
|
||||
@@ -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:"
|
||||
)
|
||||
print(" cd /path/to/hate_crack && make install")
|
||||
quit(1)
|
||||
sys.exit(1)
|
||||
|
||||
# Binary missing - need to build
|
||||
print(f"Error: {name or 'binary'} not found at {binary_path}.")
|
||||
print("\nPlease build the utilities by running:")
|
||||
print(f" cd {build_dir} && make")
|
||||
print("\nEnsure build tools (gcc, make) are installed on your system.")
|
||||
quit(1)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(
|
||||
f"Error: {name or binary_path} not found or not executable at {binary_path}."
|
||||
)
|
||||
quit(1)
|
||||
sys.exit(1)
|
||||
return binary_path
|
||||
|
||||
|
||||
@@ -468,10 +468,31 @@ except KeyError as e:
|
||||
)
|
||||
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"
|
||||
hcatCombinatorBin = "combinator.bin"
|
||||
hcatPrinceBin = "pp64.bin"
|
||||
hcatHcstat2genBin = "hcstat2gen.bin"
|
||||
hcatOmenCreateBin = "createNG"
|
||||
hcatOmenEnumBin = "enumNG"
|
||||
|
||||
|
||||
def _resolve_wordlist_path(wordlist, base_dir):
|
||||
@@ -606,6 +627,7 @@ hcatGoodMeasureBaseList = _normalize_wordlist_setting(
|
||||
hcatGoodMeasureBaseList, wordlists_dir
|
||||
)
|
||||
hcatPrinceBaseList = _normalize_wordlist_setting(hcatPrinceBaseList, wordlists_dir)
|
||||
omenTrainingList = _normalize_wordlist_setting(omenTrainingList, wordlists_dir)
|
||||
if not SKIP_INIT:
|
||||
# Verify hashcat binary is available
|
||||
# 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(
|
||||
f"Hashcat binary not found at {hcatBin}. Please check configuration and try again."
|
||||
)
|
||||
quit(1)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# hcatBin should be in PATH
|
||||
if shutil.which(hcatBin) is None:
|
||||
print(
|
||||
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
|
||||
# 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("The binary may be compiled for the wrong architecture.")
|
||||
print("Try recompiling hashcat-utils for your system.")
|
||||
quit(1)
|
||||
sys.exit(1)
|
||||
|
||||
# Verify princeprocessor binary
|
||||
# Note: princeprocessor is part of hate_crack repo, not hashcat installation
|
||||
@@ -682,6 +704,23 @@ if not SKIP_INIT:
|
||||
except SystemExit:
|
||||
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:
|
||||
print(f"Module initialization error: {e}")
|
||||
if not shutil.which("hashcat") and not os.path.exists("/usr/bin/hashcat"):
|
||||
@@ -2072,6 +2111,67 @@ def hcatPrince(hcatHashType, hcatHashFile):
|
||||
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
|
||||
def hcatGoodMeasure(hcatHashType, hcatHashFile):
|
||||
global hcatExtraCount
|
||||
@@ -2987,6 +3087,10 @@ def ollama_attack():
|
||||
return _attacks.ollama_attack(_attack_ctx())
|
||||
|
||||
|
||||
def omen_attack():
|
||||
return _attacks.omen_attack(_attack_ctx())
|
||||
|
||||
|
||||
# convert hex words for recycling
|
||||
def convert_hex(working_file):
|
||||
processed_words = []
|
||||
@@ -3215,6 +3319,7 @@ def get_main_menu_options():
|
||||
"13": bandrel_method,
|
||||
"14": loopback_attack,
|
||||
"15": ollama_attack,
|
||||
"16": omen_attack,
|
||||
"90": download_hashmob_rules,
|
||||
"91": analyze_rules,
|
||||
"92": download_hashmob_wordlists,
|
||||
@@ -3867,6 +3972,7 @@ def main():
|
||||
print("\t(13) Bandrel Methodology")
|
||||
print("\t(14) Loopback Attack")
|
||||
print("\t(15) LLM Attack")
|
||||
print("\t(16) OMEN Attack")
|
||||
print("\n\t(90) Download rules from Hashmob.net")
|
||||
print("\n\t(91) Analyze Hashcat Rules")
|
||||
print("\t(92) Download wordlists from Hashmob.net")
|
||||
|
||||
1
omen
Submodule
1
omen
Submodule
Submodule omen added at 10aa99e30b
151
tests/test_omen_attack.py
Normal file
151
tests/test_omen_attack.py
Normal 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()
|
||||
@@ -26,6 +26,7 @@ MENU_OPTION_TEST_CASES = [
|
||||
("13", CLI_MODULE._attacks, "bandrel_method", "bandrel"),
|
||||
("14", CLI_MODULE._attacks, "loopback_attack", "loopback"),
|
||||
("15", CLI_MODULE._attacks, "ollama_attack", "ollama"),
|
||||
("16", CLI_MODULE._attacks, "omen_attack", "omen"),
|
||||
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
|
||||
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
|
||||
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
|
||||
|
||||
Reference in New Issue
Block a user