mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 12:03:11 -07:00
237 lines
9.2 KiB
Python
237 lines
9.2 KiB
Python
"""End-to-end tests for markov brute force attack flow."""
|
|
|
|
import gzip
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestMarkovE2E:
|
|
"""End-to-end tests for complete markov attack workflow."""
|
|
|
|
def test_markov_training_plain_text(self, tmp_path: Path) -> None:
|
|
"""Test markov training with plain text wordlist."""
|
|
from hate_crack import main
|
|
|
|
# Setup paths
|
|
main.hate_path = Path(__file__).resolve().parents[1]
|
|
main.hcatHcstat2genBin = "hcstat2gen.bin"
|
|
bin_path = main.hate_path / "hashcat-utils" / "bin" / "hcstat2gen.bin"
|
|
if not bin_path.is_file():
|
|
pytest.skip(f"hcstat2gen.bin not compiled: {bin_path}")
|
|
|
|
# Create test wordlist
|
|
wordlist = tmp_path / "wordlist.txt"
|
|
wordlist.write_text("\n".join(["password", "123456", "admin", "letmein", "qwerty"]))
|
|
|
|
# Create test hash file
|
|
hash_file = tmp_path / "hashes.txt"
|
|
hash_file.write_text("dummy")
|
|
|
|
# Run markov training
|
|
result = main.hcatMarkovTrain(str(wordlist), str(hash_file))
|
|
|
|
# Verify result
|
|
assert result is True, "Markov training should succeed"
|
|
|
|
hcstat2_path = Path(str(hash_file) + ".hcstat2")
|
|
assert hcstat2_path.exists(), ".hcstat2 file should be created"
|
|
assert hcstat2_path.stat().st_size > 0, ".hcstat2 file should not be empty"
|
|
|
|
def test_markov_training_gzipped(self, tmp_path: Path) -> None:
|
|
"""Test markov training with gzipped wordlist."""
|
|
from hate_crack import main
|
|
|
|
# Setup paths
|
|
main.hate_path = Path(__file__).resolve().parents[1]
|
|
main.hcatHcstat2genBin = "hcstat2gen.bin"
|
|
bin_path = main.hate_path / "hashcat-utils" / "bin" / "hcstat2gen.bin"
|
|
if not bin_path.is_file():
|
|
pytest.skip(f"hcstat2gen.bin not compiled: {bin_path}")
|
|
|
|
# Create test wordlist (gzipped)
|
|
wordlist_plain = tmp_path / "wordlist.txt"
|
|
wordlist_plain.write_text("\n".join(["password", "123456", "admin", "letmein", "qwerty"]))
|
|
|
|
wordlist_gz = tmp_path / "wordlist.txt.gz"
|
|
with open(wordlist_plain, "rb") as f_in:
|
|
with gzip.open(wordlist_gz, "wb") as f_out:
|
|
f_out.write(f_in.read())
|
|
|
|
# Create test hash file
|
|
hash_file = tmp_path / "hashes.txt"
|
|
hash_file.write_text("dummy")
|
|
|
|
# Run markov training with gzipped wordlist
|
|
result = main.hcatMarkovTrain(str(wordlist_gz), str(hash_file))
|
|
|
|
# Verify result
|
|
assert result is True, "Markov training with gzipped input should succeed"
|
|
|
|
hcstat2_path = Path(str(hash_file) + ".hcstat2")
|
|
assert hcstat2_path.exists(), ".hcstat2 file should be created from gzipped wordlist"
|
|
assert hcstat2_path.stat().st_size > 0, ".hcstat2 file should not be empty"
|
|
|
|
def test_markov_brute_force_handler_use_existing_table(self, tmp_path: Path) -> None:
|
|
"""Test handler when .hcstat2 table already exists."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
|
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
|
|
|
# Create existing .hcstat2 file
|
|
hcstat2_path = f"{hash_file}.hcstat2"
|
|
Path(hcstat2_path).write_text("mock_hcstat2_data")
|
|
|
|
# User chooses to use existing table
|
|
with patch("builtins.input", side_effect=["1", "1", "7"]):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify training was NOT called (using existing)
|
|
ctx.hcatMarkovTrain.assert_not_called()
|
|
# Verify brute force WAS called
|
|
ctx.hcatMarkovBruteForce.assert_called_once()
|
|
args = ctx.hcatMarkovBruteForce.call_args[0]
|
|
assert args[0] == "1000" # hash type
|
|
assert args[1] == hash_file # hash file
|
|
assert args[2] == 1 # min length
|
|
assert args[3] == 7 # max length
|
|
|
|
def test_markov_brute_force_handler_generate_new(self, tmp_path: Path) -> None:
|
|
"""Test handler when user chooses to generate new table."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
ctx.hcatMarkovTrain.return_value = True
|
|
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
|
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
|
|
|
# Create existing .hcstat2 file
|
|
hcstat2_path = f"{hash_file}.hcstat2"
|
|
Path(hcstat2_path).write_text("old_data")
|
|
|
|
# User chooses to regenerate and select first wordlist
|
|
with patch("builtins.input", side_effect=["2", "1", "2", "8"]):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify training WAS called
|
|
ctx.hcatMarkovTrain.assert_called_once()
|
|
# Verify brute force WAS called
|
|
ctx.hcatMarkovBruteForce.assert_called_once()
|
|
args = ctx.hcatMarkovBruteForce.call_args[0]
|
|
assert args[0] == "1000"
|
|
assert args[2] == 2 # min length
|
|
assert args[3] == 8 # max length
|
|
|
|
def test_markov_brute_force_handler_use_cracked_passwords(self, tmp_path: Path) -> None:
|
|
"""Test handler when using cracked passwords as training source."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
ctx.hcatMarkovTrain.return_value = True
|
|
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
|
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
|
|
|
# Create .out file with cracked passwords
|
|
out_path = f"{hash_file}.out"
|
|
Path(out_path).write_text("password123\nadmin\nletmein\n")
|
|
|
|
# No .hcstat2 table exists, so it goes straight to source picker
|
|
# User selects option 0 (cracked passwords), then min=1, max=6
|
|
with patch("builtins.input", side_effect=["0", "1", "6"]):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify training was called with .out file
|
|
ctx.hcatMarkovTrain.assert_called_once_with(out_path, hash_file)
|
|
# Verify brute force was called
|
|
ctx.hcatMarkovBruteForce.assert_called_once()
|
|
|
|
def test_markov_brute_force_handler_cancel(self, tmp_path: Path) -> None:
|
|
"""Test handler when user cancels from table menu."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
|
|
# Create existing .hcstat2 file
|
|
hcstat2_path = f"{hash_file}.hcstat2"
|
|
Path(hcstat2_path).write_text("mock_data")
|
|
|
|
# User chooses to cancel
|
|
with patch("builtins.input", return_value="3"):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify nothing was called
|
|
ctx.hcatMarkovTrain.assert_not_called()
|
|
ctx.hcatMarkovBruteForce.assert_not_called()
|
|
|
|
def test_markov_brute_force_handler_no_table_requires_training(self, tmp_path: Path) -> None:
|
|
"""Test handler when no table exists - training is required."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
ctx.hcatMarkovTrain.return_value = True
|
|
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
|
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
|
|
|
# No .hcstat2 file exists
|
|
hcstat2_path = f"{hash_file}.hcstat2"
|
|
assert not Path(hcstat2_path).exists()
|
|
|
|
# No .out file exists, so only wordlists shown
|
|
# User selects first wordlist (option 1), min=3, max=9
|
|
with patch("builtins.input", side_effect=["1", "3", "9"]):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify training was called
|
|
ctx.hcatMarkovTrain.assert_called_once()
|
|
# Verify brute force was called with correct parameters
|
|
ctx.hcatMarkovBruteForce.assert_called_once()
|
|
args = ctx.hcatMarkovBruteForce.call_args[0]
|
|
assert args[0] == "1000"
|
|
assert args[2] == 3 # min length
|
|
assert args[3] == 9 # max length
|
|
|
|
def test_markov_brute_force_handler_training_fails(self, tmp_path: Path) -> None:
|
|
"""Test handler when training fails."""
|
|
from hate_crack.attacks import markov_brute_force
|
|
|
|
# Setup context
|
|
ctx = MagicMock()
|
|
hash_file = str(tmp_path / "hashes.txt")
|
|
ctx.hcatHashFile = hash_file
|
|
ctx.hcatHashType = "1000"
|
|
ctx.hcatMarkovTrain.return_value = False # Training fails
|
|
ctx.list_wordlist_files.return_value = ["rockyou.txt"]
|
|
ctx.hcatWordlists = str(tmp_path / "wordlists")
|
|
|
|
# No existing table
|
|
with patch("builtins.input", side_effect=["1"]):
|
|
markov_brute_force(ctx)
|
|
|
|
# Verify training was called
|
|
ctx.hcatMarkovTrain.assert_called_once()
|
|
# Verify brute force was NOT called (training failed)
|
|
ctx.hcatMarkovBruteForce.assert_not_called()
|