mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 22:51:14 -07:00
The bash shim uses `uv run --directory <install_dir>` which changes the process CWD to the install directory. _ensure_hashfile_in_cwd() and the Hashview download path used os.getcwd() to determine the target directory for output files (.out, .nt, etc.), causing them to land in the install directory instead of where the user ran the command. Add orig_cwd() helper that reads HATE_CRACK_ORIG_CWD (set by the shim) and use it in _ensure_hashfile_in_cwd(), the Hashview download path, and the potfile fallback path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
594 lines
22 KiB
Python
594 lines
22 KiB
Python
"""Tests for utility functions in hate_crack/main.py."""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from hate_crack.main import _dedup_netntlm_by_username
|
|
|
|
|
|
@pytest.fixture
|
|
def main_module(hc_module):
|
|
return hc_module._main
|
|
|
|
|
|
class TestAppendPotfileArg:
|
|
def test_appends_potfile_path(self, main_module):
|
|
with patch.object(main_module, "hcatPotfilePath", "/some/path"):
|
|
cmd = []
|
|
main_module._append_potfile_arg(cmd)
|
|
assert "--potfile-path=/some/path" in cmd
|
|
|
|
def test_no_append_empty_potfile(self, main_module):
|
|
with patch.object(main_module, "hcatPotfilePath", ""):
|
|
cmd = []
|
|
main_module._append_potfile_arg(cmd)
|
|
assert cmd == []
|
|
|
|
def test_disabled_by_flag(self, main_module):
|
|
with patch.object(main_module, "hcatPotfilePath", "/some/path"):
|
|
cmd = []
|
|
main_module._append_potfile_arg(cmd, use_potfile_path=False)
|
|
assert cmd == []
|
|
|
|
def test_explicit_potfile_overrides_global(self, main_module):
|
|
with patch.object(main_module, "hcatPotfilePath", "/global/path"):
|
|
cmd = []
|
|
main_module._append_potfile_arg(cmd, potfile_path="/custom/path")
|
|
assert "--potfile-path=/custom/path" in cmd
|
|
assert "--potfile-path=/global/path" not in cmd
|
|
|
|
def test_explicit_potfile_when_global_empty(self, main_module):
|
|
with patch.object(main_module, "hcatPotfilePath", ""):
|
|
cmd = []
|
|
main_module._append_potfile_arg(cmd, potfile_path="/explicit/path")
|
|
assert "--potfile-path=/explicit/path" in cmd
|
|
|
|
|
|
class TestGenerateSessionId:
|
|
def test_basic_filename(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/tmp/myfile.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
assert result == "myfile"
|
|
|
|
def test_with_hyphens_and_underscores(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/path/to/my-file_v2.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
assert result == "my-file_v2"
|
|
|
|
def test_dots_replaced(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/tmp/file.with.dots.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
assert result == "file_with_dots"
|
|
|
|
def test_spaces_replaced(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/tmp/file with spaces.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
assert result == "file_with_spaces"
|
|
|
|
def test_returns_nonempty_string(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/tmp/somefile.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_only_safe_chars(self, main_module):
|
|
with patch("hate_crack.main.hcatHashFile", "/tmp/f!le@na#me.txt", create=True):
|
|
result = main_module.generate_session_id()
|
|
import re
|
|
assert re.fullmatch(r"[a-zA-Z0-9_-]+", result) is not None
|
|
|
|
|
|
class TestEnsureHashfileInCwd:
|
|
def test_none_returns_none(self, main_module):
|
|
result = main_module._ensure_hashfile_in_cwd(None)
|
|
assert result is None
|
|
|
|
def test_empty_string_returns_empty(self, main_module):
|
|
result = main_module._ensure_hashfile_in_cwd("")
|
|
assert result == ""
|
|
|
|
def test_relative_path_unchanged(self, main_module):
|
|
result = main_module._ensure_hashfile_in_cwd("relative/path.txt")
|
|
assert result == "relative/path.txt"
|
|
|
|
def test_already_in_cwd(self, main_module, tmp_path, monkeypatch):
|
|
target = tmp_path / "hashfile.txt"
|
|
target.write_text("hashes")
|
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
|
|
result = main_module._ensure_hashfile_in_cwd(str(target))
|
|
assert result == str(target)
|
|
|
|
def test_different_dir_existing_file_in_cwd(self, main_module, tmp_path, monkeypatch):
|
|
# File exists in a different directory
|
|
other_dir = tmp_path / "other"
|
|
other_dir.mkdir()
|
|
source_file = other_dir / "hashes.txt"
|
|
source_file.write_text("hashes")
|
|
|
|
# A file with the same name already exists in cwd
|
|
cwd_dir = tmp_path / "cwd"
|
|
cwd_dir.mkdir()
|
|
cwd_copy = cwd_dir / "hashes.txt"
|
|
cwd_copy.write_text("cwd version")
|
|
|
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
|
|
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
|
assert result == str(cwd_copy)
|
|
|
|
def test_different_dir_creates_symlink(self, main_module, tmp_path, monkeypatch):
|
|
# Source file in a different directory, nothing in cwd
|
|
other_dir = tmp_path / "other"
|
|
other_dir.mkdir()
|
|
source_file = other_dir / "hashes.txt"
|
|
source_file.write_text("hashes")
|
|
|
|
cwd_dir = tmp_path / "cwd"
|
|
cwd_dir.mkdir()
|
|
|
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
|
|
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
|
|
|
expected = str(cwd_dir / "hashes.txt")
|
|
assert result == expected
|
|
assert os.path.exists(expected)
|
|
|
|
def test_uses_orig_cwd_not_process_cwd(self, main_module, tmp_path, monkeypatch):
|
|
"""Output files go to HATE_CRACK_ORIG_CWD, not the process CWD."""
|
|
# Simulate: process CWD is the install dir, orig CWD is the user's dir
|
|
install_dir = tmp_path / "install"
|
|
install_dir.mkdir()
|
|
user_dir = tmp_path / "user"
|
|
user_dir.mkdir()
|
|
other_dir = tmp_path / "other"
|
|
other_dir.mkdir()
|
|
source_file = other_dir / "hashes.txt"
|
|
source_file.write_text("hashes")
|
|
|
|
monkeypatch.chdir(install_dir)
|
|
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(user_dir))
|
|
result = main_module._ensure_hashfile_in_cwd(str(source_file))
|
|
|
|
# Should land in user_dir, NOT install_dir
|
|
assert result == str(user_dir / "hashes.txt")
|
|
assert os.path.exists(user_dir / "hashes.txt")
|
|
assert not os.path.exists(install_dir / "hashes.txt")
|
|
|
|
|
|
class TestRunHashcatShow:
|
|
def _make_mock_result(self, stdout_bytes):
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = stdout_bytes
|
|
return mock_result
|
|
|
|
def test_show_flag_present(self, main_module, tmp_path):
|
|
mock_result = self._make_mock_result(b"")
|
|
output = tmp_path / "out.txt"
|
|
captured_cmd = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
captured_cmd.extend(cmd)
|
|
return mock_result
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", side_effect=fake_run),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", ""),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
assert "--show" in captured_cmd
|
|
|
|
def test_valid_lines_written(self, main_module, tmp_path):
|
|
stdout = b"abc123:password\ndeadbeef:hunter2\n"
|
|
mock_result = self._make_mock_result(stdout)
|
|
output = tmp_path / "out.txt"
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", return_value=mock_result),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", ""),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
lines = output.read_text().splitlines()
|
|
assert "abc123:password" in lines
|
|
assert "deadbeef:hunter2" in lines
|
|
|
|
def test_hash_parsing_error_excluded(self, main_module, tmp_path):
|
|
stdout = b"abc123:password\nHash parsing error: bad line\n"
|
|
mock_result = self._make_mock_result(stdout)
|
|
output = tmp_path / "out.txt"
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", return_value=mock_result),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", ""),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
content = output.read_text()
|
|
assert "Hash parsing error" not in content
|
|
assert "abc123:password" in content
|
|
|
|
def test_star_prefix_excluded(self, main_module, tmp_path):
|
|
stdout = b"abc123:password\n* Device #1: ...\n"
|
|
mock_result = self._make_mock_result(stdout)
|
|
output = tmp_path / "out.txt"
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", return_value=mock_result),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", ""),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
content = output.read_text()
|
|
assert "* Device" not in content
|
|
|
|
def test_lines_without_colon_excluded(self, main_module, tmp_path):
|
|
stdout = b"abc123:password\nlinewithoutseparator\n"
|
|
mock_result = self._make_mock_result(stdout)
|
|
output = tmp_path / "out.txt"
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", return_value=mock_result),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", ""),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
content = output.read_text()
|
|
assert "linewithoutseparator" not in content
|
|
assert "abc123:password" in content
|
|
|
|
def test_potfile_path_included_when_set(self, main_module, tmp_path):
|
|
mock_result = self._make_mock_result(b"")
|
|
output = tmp_path / "out.txt"
|
|
captured_cmd = []
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
captured_cmd.extend(cmd)
|
|
return mock_result
|
|
|
|
with (
|
|
patch("hate_crack.main.subprocess.run", side_effect=fake_run),
|
|
patch.object(main_module, "hcatBin", "hashcat"),
|
|
patch.object(main_module, "hcatPotfilePath", "/my/potfile"),
|
|
):
|
|
main_module._run_hashcat_show("1000", "/tmp/h.txt", str(output))
|
|
|
|
assert any("--potfile-path=/my/potfile" in arg for arg in captured_cmd)
|
|
|
|
|
|
class TestDedupNetntlmByUsername:
|
|
def test_no_duplicates_no_output_file(self, tmp_path):
|
|
input_file = tmp_path / "hashes.txt"
|
|
input_file.write_text("user1::domain:challenge:response:blob\nuser2::domain:challenge:response:blob\n")
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 2
|
|
assert dupes == 0
|
|
assert not output_file.exists()
|
|
|
|
def test_duplicates_removed(self, tmp_path):
|
|
input_file = tmp_path / "hashes.txt"
|
|
input_file.write_text(
|
|
"alice::domain:aaa:bbb:ccc\n"
|
|
"bob::domain:ddd:eee:fff\n"
|
|
"alice::domain:111:222:333\n"
|
|
)
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 3
|
|
assert dupes == 1
|
|
assert output_file.exists()
|
|
lines = output_file.read_text().splitlines()
|
|
assert len(lines) == 2
|
|
assert any("alice" in line for line in lines)
|
|
assert any("bob" in line for line in lines)
|
|
|
|
def test_only_first_occurrence_kept(self, tmp_path):
|
|
input_file = tmp_path / "hashes.txt"
|
|
input_file.write_text(
|
|
"alice::domain:first:aaa:bbb\n"
|
|
"alice::domain:second:ccc:ddd\n"
|
|
)
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
_dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
content = output_file.read_text()
|
|
assert "first" in content
|
|
assert "second" not in content
|
|
|
|
def test_empty_file(self, tmp_path):
|
|
input_file = tmp_path / "empty.txt"
|
|
input_file.write_text("")
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 0
|
|
assert dupes == 0
|
|
assert not output_file.exists()
|
|
|
|
def test_missing_input_file(self, tmp_path):
|
|
input_file = tmp_path / "nonexistent.txt"
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 0
|
|
assert dupes == 0
|
|
|
|
def test_lines_without_delimiter(self, tmp_path):
|
|
input_file = tmp_path / "hashes.txt"
|
|
input_file.write_text("nodeilimiter\nnodeilimiter\n")
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
# Should not raise; whole line treated as username
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 2
|
|
assert dupes == 1
|
|
|
|
def test_case_insensitive_username_dedup(self, tmp_path):
|
|
input_file = tmp_path / "hashes.txt"
|
|
input_file.write_text("Alice::domain:aaa:bbb:ccc\nalice::domain:ddd:eee:fff\n")
|
|
output_file = tmp_path / "deduped.txt"
|
|
|
|
total, dupes = _dedup_netntlm_by_username(str(input_file), str(output_file))
|
|
|
|
assert total == 2
|
|
assert dupes == 1
|
|
|
|
|
|
class TestResolveWordlistPath:
|
|
def test_absolute_existing_file(self, main_module, tmp_path):
|
|
wordlist = tmp_path / "words.txt"
|
|
wordlist.write_text("word1\nword2\n")
|
|
|
|
result = main_module._resolve_wordlist_path(str(wordlist), str(tmp_path))
|
|
|
|
assert result == str(wordlist)
|
|
|
|
def test_relative_found_in_base_dir(self, main_module, tmp_path):
|
|
wordlist = tmp_path / "words.txt"
|
|
wordlist.write_text("word1\nword2\n")
|
|
|
|
result = main_module._resolve_wordlist_path("words.txt", str(tmp_path))
|
|
|
|
assert result == str(wordlist)
|
|
|
|
def test_not_found_returns_path_anyway(self, main_module, tmp_path):
|
|
# When file not found, returns abspath of first candidate - does not raise
|
|
result = main_module._resolve_wordlist_path("missing.txt", str(tmp_path))
|
|
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_empty_string_returns_empty(self, main_module):
|
|
result = main_module._resolve_wordlist_path("", "/some/dir")
|
|
|
|
assert result == ""
|
|
|
|
def test_none_returns_none(self, main_module):
|
|
result = main_module._resolve_wordlist_path(None, "/some/dir")
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestGetRulePath:
|
|
def test_found_in_rules_directory(self, main_module, tmp_path):
|
|
rules_dir = tmp_path / "rules"
|
|
rules_dir.mkdir()
|
|
rule_file = rules_dir / "best64.rule"
|
|
rule_file.write_text("rule content")
|
|
|
|
with patch.object(main_module, "rulesDirectory", str(rules_dir)):
|
|
result = main_module.get_rule_path("best64.rule")
|
|
|
|
assert result == str(rule_file)
|
|
|
|
def test_found_in_fallback_dir(self, main_module, tmp_path):
|
|
# rulesDirectory has no such file, fallback does
|
|
empty_rules_dir = tmp_path / "empty_rules"
|
|
empty_rules_dir.mkdir()
|
|
fallback_dir = tmp_path / "fallback"
|
|
fallback_dir.mkdir()
|
|
rule_file = fallback_dir / "custom.rule"
|
|
rule_file.write_text("rule content")
|
|
|
|
with patch.object(main_module, "rulesDirectory", str(empty_rules_dir)):
|
|
result = main_module.get_rule_path("custom.rule", fallback_dir=str(fallback_dir))
|
|
|
|
assert result == str(rule_file)
|
|
|
|
def test_not_found_returns_first_candidate(self, main_module, tmp_path):
|
|
rules_dir = tmp_path / "rules"
|
|
rules_dir.mkdir()
|
|
|
|
with patch.object(main_module, "rulesDirectory", str(rules_dir)):
|
|
result = main_module.get_rule_path("nonexistent.rule")
|
|
|
|
assert result == str(rules_dir / "nonexistent.rule")
|
|
|
|
def test_no_rules_directory_no_fallback_returns_rule_name(self, main_module):
|
|
with patch.object(main_module, "rulesDirectory", ""):
|
|
result = main_module.get_rule_path("some.rule")
|
|
|
|
assert result == "some.rule"
|
|
|
|
def test_fallback_checked_after_rules_directory(self, main_module, tmp_path):
|
|
# Both directories have the rule; rules_directory takes priority
|
|
rules_dir = tmp_path / "rules"
|
|
rules_dir.mkdir()
|
|
fallback_dir = tmp_path / "fallback"
|
|
fallback_dir.mkdir()
|
|
(rules_dir / "priority.rule").write_text("rules version")
|
|
(fallback_dir / "priority.rule").write_text("fallback version")
|
|
|
|
with patch.object(main_module, "rulesDirectory", str(rules_dir)):
|
|
result = main_module.get_rule_path("priority.rule", fallback_dir=str(fallback_dir))
|
|
|
|
assert result == str(rules_dir / "priority.rule")
|
|
|
|
|
|
class TestCleanupWordlistArtifacts:
|
|
def test_removes_out_files_from_cwd(self, main_module, tmp_path):
|
|
artifact = tmp_path / "cracked.out"
|
|
artifact.write_text("cracked passwords")
|
|
|
|
with (
|
|
patch("os.getcwd", return_value=str(tmp_path)),
|
|
patch.object(main_module, "hate_path", str(tmp_path)),
|
|
patch.object(main_module, "hcatWordlists", str(tmp_path / "wordlists")),
|
|
):
|
|
main_module.cleanup_wordlist_artifacts()
|
|
|
|
assert not artifact.exists()
|
|
|
|
def test_preserves_non_artifact_files(self, main_module, tmp_path):
|
|
keeper = tmp_path / "important.txt"
|
|
keeper.write_text("keep me")
|
|
artifact = tmp_path / "remove.out"
|
|
artifact.write_text("remove me")
|
|
|
|
with (
|
|
patch("os.getcwd", return_value=str(tmp_path)),
|
|
patch.object(main_module, "hate_path", str(tmp_path)),
|
|
patch.object(main_module, "hcatWordlists", str(tmp_path / "wordlists")),
|
|
):
|
|
main_module.cleanup_wordlist_artifacts()
|
|
|
|
assert keeper.exists()
|
|
assert not artifact.exists()
|
|
|
|
def test_removes_out_files_from_hate_path(self, main_module, tmp_path):
|
|
hate_dir = tmp_path / "hate_crack"
|
|
hate_dir.mkdir()
|
|
cwd_dir = tmp_path / "cwd"
|
|
cwd_dir.mkdir()
|
|
artifact = hate_dir / "session.out"
|
|
artifact.write_text("output")
|
|
|
|
with (
|
|
patch("os.getcwd", return_value=str(cwd_dir)),
|
|
patch.object(main_module, "hate_path", str(hate_dir)),
|
|
patch.object(main_module, "hcatWordlists", str(tmp_path / "wordlists")),
|
|
):
|
|
main_module.cleanup_wordlist_artifacts()
|
|
|
|
assert not artifact.exists()
|
|
|
|
def test_missing_directory_does_not_raise(self, main_module, tmp_path):
|
|
nonexistent = tmp_path / "nonexistent"
|
|
cwd_dir = tmp_path / "cwd"
|
|
cwd_dir.mkdir()
|
|
|
|
with (
|
|
patch("os.getcwd", return_value=str(cwd_dir)),
|
|
patch.object(main_module, "hate_path", str(nonexistent)),
|
|
patch.object(main_module, "hcatWordlists", str(tmp_path / "wordlists")),
|
|
):
|
|
# Should not raise even when directories don't exist
|
|
main_module.cleanup_wordlist_artifacts()
|
|
|
|
|
|
class TestListWordlistFiles:
|
|
@pytest.mark.parametrize(
|
|
"filename, included",
|
|
[
|
|
("rockyou.txt", True),
|
|
("wordlist.lst", True),
|
|
("custom.dict", True),
|
|
("compressed.gz", True),
|
|
("archive.7z", False),
|
|
("file.torrent", False),
|
|
("hashes.out", False),
|
|
(".DS_Store", False),
|
|
],
|
|
)
|
|
def test_filters_excluded_extensions(self, tmp_path, main_module, filename, included):
|
|
(tmp_path / filename).touch()
|
|
result = main_module.list_wordlist_files(str(tmp_path))
|
|
if included:
|
|
assert filename in result
|
|
else:
|
|
assert filename not in result
|
|
|
|
def test_returns_sorted(self, tmp_path, main_module):
|
|
for name in ["zebra.txt", "alpha.txt", "middle.txt"]:
|
|
(tmp_path / name).touch()
|
|
result = main_module.list_wordlist_files(str(tmp_path))
|
|
assert result == ["alpha.txt", "middle.txt", "zebra.txt"]
|
|
|
|
|
|
class TestOptimizedKernel:
|
|
@pytest.mark.parametrize(
|
|
"attack_name",
|
|
[
|
|
"hcatDictionary",
|
|
"hcatQuickDictionary",
|
|
"hcatFingerprint",
|
|
"hcatCombination",
|
|
"hcatCombinator3",
|
|
"hcatCombinatorX",
|
|
"hcatHybrid",
|
|
"hcatYoloCombination",
|
|
"hcatMiddleCombinator",
|
|
"hcatThoroughCombinator",
|
|
"hcatCombipow",
|
|
"hcatPrince",
|
|
"hcatPermute",
|
|
"hcatBandrel",
|
|
"hcatGoodMeasure",
|
|
"hcatRecycle",
|
|
"hcatBruteForce",
|
|
"hcatTopMask",
|
|
"hcatPathwellBruteForce",
|
|
],
|
|
)
|
|
def test_optimized_attacks_return_true(self, main_module, attack_name):
|
|
assert main_module._should_use_optimized_kernel(attack_name) is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"attack_name",
|
|
[
|
|
"hcatOmen",
|
|
"hcatLMtoNT",
|
|
],
|
|
)
|
|
def test_non_optimized_attacks_return_false(self, main_module, attack_name):
|
|
assert main_module._should_use_optimized_kernel(attack_name) is False
|
|
|
|
def test_insert_optimized_flag_adds_O(self, main_module):
|
|
cmd = ["hashcat", "-m", "1000"]
|
|
main_module._insert_optimized_flag(cmd)
|
|
assert "-O" in cmd
|
|
|
|
def test_insert_optimized_flag_no_duplicate(self, main_module):
|
|
cmd = ["hashcat", "-m", "1000", "-O"]
|
|
main_module._insert_optimized_flag(cmd)
|
|
assert cmd.count("-O") == 1
|
|
|
|
def test_insert_optimized_flag_respects_long_form(self, main_module):
|
|
cmd = ["hashcat", "-m", "1000", "--optimized-kernel-enable"]
|
|
main_module._insert_optimized_flag(cmd)
|
|
assert "-O" not in cmd
|
|
|
|
def test_config_override(self, main_module):
|
|
original = main_module._optimized_kernel_attacks
|
|
try:
|
|
main_module._optimized_kernel_attacks = frozenset({"hcatCustom"})
|
|
assert main_module._should_use_optimized_kernel("hcatCustom") is True
|
|
assert main_module._should_use_optimized_kernel("hcatBruteForce") is False
|
|
finally:
|
|
main_module._optimized_kernel_attacks = original
|