mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
Add Wordlist Tools submenu (key 80) with 7 preprocessing utilities backed by hashcat-utils binaries: - Filter by length (len.bin) - #90 - Require/exclude character classes (req-include.bin, req-exclude.bin) - #90 - Extract substring (cutb.bin) - #90 - Remove matching lines (rli.bin, rli2.bin) - #91 - Split by length (splitlen.bin) - #92 - Shard wordlist (gate.bin) - #94 Three-layer pattern: - main.py: low-level wrappers (wordlist_filter_len, wordlist_filter_req_include, wordlist_filter_req_exclude, wordlist_cutb, wordlist_splitlen, wordlist_subtract, wordlist_subtract_single, wordlist_gate) return bool for testability - attacks.py: UI handlers with input validation and the wordlist_tools_submenu dispatcher - hate_crack.py + main.py: menu item "80" wired in both get_main_menu_items and get_main_menu_options Move interactive_menu import to attacks.py module level (was local import in combinator_submenu) to support patching in tests.
178 lines
6.5 KiB
Python
178 lines
6.5 KiB
Python
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch, call
|
|
import importlib.util
|
|
|
|
import pytest
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
CLI_SPEC = importlib.util.spec_from_file_location(
|
|
"hate_crack_cli", PROJECT_ROOT / "hate_crack.py"
|
|
)
|
|
CLI_MODULE = importlib.util.module_from_spec(CLI_SPEC)
|
|
CLI_SPEC.loader.exec_module(CLI_MODULE)
|
|
|
|
|
|
def _get_main():
|
|
import hate_crack.main as m
|
|
return m
|
|
|
|
|
|
class TestWordlistFilterLenWrapper:
|
|
def test_calls_len_bin_with_correct_args(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_filter_len(str(infile), str(outfile), 4, 8)
|
|
assert result is True
|
|
mock_run.assert_called_once()
|
|
cmd = mock_run.call_args[0][0]
|
|
assert cmd[1] == "4"
|
|
assert cmd[2] == "8"
|
|
assert "len.bin" in cmd[0]
|
|
|
|
def test_returns_false_on_nonzero_returncode(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=1)
|
|
result = m.wordlist_filter_len(str(infile), str(outfile), 4, 8)
|
|
assert result is False
|
|
|
|
|
|
class TestWordlistFilterReqIncludeWrapper:
|
|
def test_calls_req_include_bin(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_filter_req_include(str(infile), str(outfile), 7)
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "req-include.bin" in cmd[0]
|
|
assert cmd[1] == "7"
|
|
|
|
|
|
class TestWordlistFilterReqExcludeWrapper:
|
|
def test_calls_req_exclude_bin(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_filter_req_exclude(str(infile), str(outfile), 8)
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "req-exclude.bin" in cmd[0]
|
|
assert cmd[1] == "8"
|
|
|
|
|
|
class TestWordlistCutbWrapper:
|
|
def test_calls_cutb_bin_with_offset_and_length(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_cutb(str(infile), str(outfile), 2, 4)
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "cutb.bin" in cmd[0]
|
|
assert cmd[1] == "2"
|
|
assert cmd[2] == "4"
|
|
|
|
def test_calls_cutb_bin_without_length(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_cutb(str(infile), str(outfile), 2, None)
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "cutb.bin" in cmd[0]
|
|
assert cmd[1] == "2"
|
|
assert len(cmd) == 2 # only binary + offset, no length arg
|
|
|
|
|
|
class TestWordlistSplitlenWrapper:
|
|
def test_calls_splitlen_bin(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("test\n")
|
|
outdir = tmp_path / "split"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_splitlen(str(infile), str(outdir))
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "splitlen.bin" in cmd[0]
|
|
assert cmd[1] == str(outdir)
|
|
|
|
|
|
class TestWordlistSubtractWrapper:
|
|
def test_calls_rli_bin_with_multiple_files(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\n")
|
|
outfile = tmp_path / "out.txt"
|
|
remove1 = tmp_path / "r1.txt"
|
|
remove1.write_text("word1\n")
|
|
remove2 = tmp_path / "r2.txt"
|
|
remove2.write_text("word2\n")
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_subtract(str(infile), str(outfile), str(remove1), str(remove2))
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "rli.bin" in cmd[0]
|
|
assert cmd[1] == str(infile)
|
|
assert cmd[2] == str(outfile)
|
|
assert str(remove1) in cmd
|
|
assert str(remove2) in cmd
|
|
|
|
|
|
class TestWordlistSubtractSingleWrapper:
|
|
def test_calls_rli2_bin(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\n")
|
|
removefile = tmp_path / "remove.txt"
|
|
removefile.write_text("word1\n")
|
|
outfile = tmp_path / "out.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_subtract_single(str(infile), str(removefile), str(outfile))
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "rli2.bin" in cmd[0]
|
|
assert cmd[1] == str(infile)
|
|
assert cmd[2] == str(removefile)
|
|
|
|
|
|
class TestWordlistGateWrapper:
|
|
def test_calls_gate_bin(self, tmp_path):
|
|
m = _get_main()
|
|
infile = tmp_path / "in.txt"
|
|
infile.write_text("word1\nword2\nword3\n")
|
|
outfile = tmp_path / "shard.txt"
|
|
with patch("hate_crack.main.subprocess.run") as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = m.wordlist_gate(str(infile), str(outfile), 3, 0)
|
|
assert result is True
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "gate.bin" in cmd[0]
|
|
assert cmd[1] == "3"
|
|
assert cmd[2] == "0"
|