mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
Output files now land next to the original hashfile. resolve_path() already resolves relative paths against HATE_CRACK_ORIG_CWD, so relocating the hashfile into CWD was unnecessary and created confusing symlinks in the working directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
587 lines
21 KiB
Python
587 lines
21 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_returns_original_path(self, main_module, tmp_path, monkeypatch):
|
|
"""File in different dir is returned as-is (no symlink/copy)."""
|
|
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))
|
|
assert result == str(source_file)
|
|
|
|
def test_different_dir_no_symlink(self, main_module, tmp_path, monkeypatch):
|
|
"""File in different dir does NOT create a symlink 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))
|
|
assert result == str(source_file)
|
|
assert not (cwd_dir / "hashes.txt").exists()
|
|
|
|
def test_uses_orig_cwd_not_process_cwd(self, main_module, tmp_path, monkeypatch):
|
|
"""Returns original path as-is; no files created in any cwd."""
|
|
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))
|
|
|
|
assert result == str(source_file)
|
|
assert not (user_dir / "hashes.txt").exists()
|
|
assert not (install_dir / "hashes.txt").exists()
|
|
|
|
|
|
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
|