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"]
|
[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
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -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..."
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
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"),
|
("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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user