mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
feat: add combinator3 and combinatorX attacks to combinator submenu
Extends the combinator submenu (option 6) with two new attacks using hashcat-utils binaries that were already compiled but unused. - hcatCombinator3: 3-way wordlist combination via combinator3.bin piped to hashcat stdin - hcatCombinatorX: 2-8 wordlist combination via combinatorX.bin with optional --sepFill separator, piped to hashcat stdin - combinator3_crack handler: prompts for 3 comma-separated wordlist paths - combinatorX_crack handler: prompts for 2-8 paths plus optional separator - combinator_submenu updated with options 5 and 6 Closes #84, closes #85
This commit is contained in:
@@ -763,11 +763,13 @@ Uses the Ordered Markov ENumerator (OMEN) to train a statistical password model
|
||||
* Model files and metadata are stored in `~/.hate_crack/omen/` for persistence across sessions
|
||||
|
||||
#### Combinator Attacks Submenu
|
||||
Opens an interactive submenu with four combinator attack variants (formerly at menu keys 10-12). Consolidates related attacks for cleaner menu organization:
|
||||
Opens an interactive submenu with six combinator attack variants (formerly at menu keys 10-12). Consolidates related attacks for cleaner menu organization:
|
||||
- Combinator Attack - combines two wordlists
|
||||
- YOLO Combinator Attack - combines all permutations of multiple wordlists
|
||||
- Middle Combinator Attack - combines wordlists with an extra word in the middle
|
||||
- Thorough Combinator Attack - comprehensive combination of wordlists with rules
|
||||
- Combinator3 Attack - combines exactly 3 wordlists using `combinator3.bin`, generating all `word1+word2+word3` combinations piped to hashcat
|
||||
- CombinatorX Attack - combines 2-8 wordlists using `combinatorX.bin` with optional `--sepFill` separator character between word segments
|
||||
|
||||
#### Ad-hoc Mask Attack
|
||||
Runs hashcat mask attack (mode 3) with a user-specified custom mask string. Allows fine-grained control over character-set brute forcing.
|
||||
|
||||
+111
-2
@@ -5,6 +5,7 @@ from typing import Any
|
||||
|
||||
from hate_crack.api import download_hashmob_rules
|
||||
from hate_crack.formatting import print_multicolumn_list
|
||||
from hate_crack.menu import interactive_menu
|
||||
|
||||
|
||||
def _configure_readline(completer):
|
||||
@@ -427,6 +428,110 @@ def middle_combinator(ctx: Any) -> None:
|
||||
ctx.hcatMiddleCombinator(ctx.hcatHashType, ctx.hcatHashFile)
|
||||
|
||||
|
||||
def combinator3_crack(ctx: Any) -> None:
|
||||
print("\n" + "=" * 60)
|
||||
print("COMBINATOR3 ATTACK")
|
||||
print("=" * 60)
|
||||
print("This attack combines three wordlists to generate candidates.")
|
||||
print("=" * 60)
|
||||
|
||||
use_default = (
|
||||
input("\nUse default combinator wordlists from config? (Y/n): ").strip().lower()
|
||||
)
|
||||
|
||||
if use_default != "n":
|
||||
base = ctx.hcatCombinationWordlist
|
||||
wordlists = base if isinstance(base, list) else [base]
|
||||
if len(wordlists) < 3:
|
||||
print("\n[!] Config does not have 3 wordlists for combinator3.")
|
||||
print("Set hcatCombinationWordlist to a list of 3 paths in config.json.")
|
||||
print("Aborting combinator3 attack.")
|
||||
return
|
||||
else:
|
||||
raw = input(
|
||||
"\nEnter 3 wordlist file paths (comma-separated): "
|
||||
).strip()
|
||||
if not raw:
|
||||
print("No wordlists provided. Aborting combinator3 attack.")
|
||||
return
|
||||
|
||||
entries = [p.strip() for p in raw.split(",") if p.strip()]
|
||||
if len(entries) < 3:
|
||||
print("\n[!] Combinator3 attack requires exactly 3 wordlists.")
|
||||
print("Aborting combinator3 attack.")
|
||||
return
|
||||
|
||||
valid = []
|
||||
for p in entries[:3]:
|
||||
resolved = ctx._resolve_wordlist_path(p, ctx.hcatWordlists)
|
||||
if os.path.isfile(resolved):
|
||||
valid.append(resolved)
|
||||
print(f"Found: {resolved}")
|
||||
else:
|
||||
print(f"Not found: {resolved}")
|
||||
|
||||
if len(valid) < 3:
|
||||
print("\nCould not find 3 valid wordlists. Aborting combinator3 attack.")
|
||||
return
|
||||
|
||||
wordlists = valid
|
||||
|
||||
ctx.hcatCombinator3(ctx.hcatHashType, ctx.hcatHashFile, wordlists)
|
||||
|
||||
|
||||
def combinatorX_crack(ctx: Any) -> None:
|
||||
print("\n" + "=" * 60)
|
||||
print("COMBINATORX ATTACK")
|
||||
print("=" * 60)
|
||||
print("This attack combines 2-8 wordlists with an optional separator.")
|
||||
print("=" * 60)
|
||||
|
||||
use_default = (
|
||||
input("\nUse default combinator wordlists from config? (Y/n): ").strip().lower()
|
||||
)
|
||||
|
||||
if use_default != "n":
|
||||
base = ctx.hcatCombinationWordlist
|
||||
wordlists = base if isinstance(base, list) else [base]
|
||||
if len(wordlists) < 2:
|
||||
print("\n[!] Config does not have at least 2 wordlists for combinatorX.")
|
||||
print("Set hcatCombinationWordlist to a list of 2+ paths in config.json.")
|
||||
print("Aborting combinatorX attack.")
|
||||
return
|
||||
separator = ""
|
||||
else:
|
||||
raw = input(
|
||||
"\nEnter 2-8 wordlist file paths (comma-separated): "
|
||||
).strip()
|
||||
if not raw:
|
||||
print("No wordlists provided. Aborting combinatorX attack.")
|
||||
return
|
||||
|
||||
entries = [p.strip() for p in raw.split(",") if p.strip()]
|
||||
if len(entries) < 2:
|
||||
print("\n[!] CombinatorX attack requires at least 2 wordlists.")
|
||||
print("Aborting combinatorX attack.")
|
||||
return
|
||||
|
||||
valid = []
|
||||
for p in entries[:8]:
|
||||
resolved = ctx._resolve_wordlist_path(p, ctx.hcatWordlists)
|
||||
if os.path.isfile(resolved):
|
||||
valid.append(resolved)
|
||||
print(f"Found: {resolved}")
|
||||
else:
|
||||
print(f"Not found: {resolved}")
|
||||
|
||||
if len(valid) < 2:
|
||||
print("\nCould not find 2 valid wordlists. Aborting combinatorX attack.")
|
||||
return
|
||||
|
||||
wordlists = valid
|
||||
separator = input("\nEnter separator between words (leave blank for none): ").strip()
|
||||
|
||||
ctx.hcatCombinatorX(ctx.hcatHashType, ctx.hcatHashFile, wordlists, separator or None)
|
||||
|
||||
|
||||
def bandrel_method(ctx: Any) -> None:
|
||||
ctx.hcatBandrel(ctx.hcatHashType, ctx.hcatHashFile)
|
||||
|
||||
@@ -621,13 +726,13 @@ def markov_brute_force(ctx: Any) -> None:
|
||||
|
||||
|
||||
def combinator_submenu(ctx: Any) -> None:
|
||||
from hate_crack.menu import interactive_menu
|
||||
|
||||
items = [
|
||||
("1", "Combinator Attack"),
|
||||
("2", "YOLO Combinator Attack"),
|
||||
("3", "Middle Combinator Attack"),
|
||||
("4", "Thorough Combinator Attack"),
|
||||
("5", "Combinator3 Attack (3-way)"),
|
||||
("6", "CombinatorX Attack (N-way, 2-8 wordlists)"),
|
||||
("99", "Back to Main Menu"),
|
||||
]
|
||||
while True:
|
||||
@@ -642,3 +747,7 @@ def combinator_submenu(ctx: Any) -> None:
|
||||
middle_combinator(ctx)
|
||||
elif choice == "4":
|
||||
thorough_combinator(ctx)
|
||||
elif choice == "5":
|
||||
combinator3_crack(ctx)
|
||||
elif choice == "6":
|
||||
combinatorX_crack(ctx)
|
||||
|
||||
@@ -674,6 +674,8 @@ hcatDictionaryCount = 0
|
||||
hcatMaskCount = 0
|
||||
hcatFingerprintCount = 0
|
||||
hcatCombinationCount = 0
|
||||
hcatCombinator3Count = 0
|
||||
hcatCombinatorXCount = 0
|
||||
hcatHybridCount = 0
|
||||
hcatExtraCount = 0
|
||||
hcatRecycleCount = 0
|
||||
@@ -1415,6 +1417,86 @@ def hcatCombination(hcatHashType, hcatHashFile, wordlists=None):
|
||||
hcatCombinationCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||
|
||||
|
||||
# Combinator3 Attack - 3-way combination via combinator3.bin piped to hashcat
|
||||
def hcatCombinator3(hcatHashType, hcatHashFile, wordlists):
|
||||
global hcatCombinator3Count
|
||||
global hcatProcess
|
||||
|
||||
if len(wordlists) < 3:
|
||||
print("[!] Combinator3 attack requires exactly 3 wordlists.")
|
||||
return
|
||||
|
||||
combinator3_bin = os.path.join(hate_path, "hashcat-utils/bin/combinator3.bin")
|
||||
generator_cmd = [combinator3_bin] + list(wordlists[:3])
|
||||
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)
|
||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||
assert generator_proc.stdout is not None
|
||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
||||
generator_proc.stdout.close()
|
||||
try:
|
||||
hcatProcess.wait()
|
||||
generator_proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
||||
hcatProcess.kill()
|
||||
generator_proc.kill()
|
||||
|
||||
hcatCombinator3Count = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||
|
||||
|
||||
# CombinatorX Attack - N-way combination (2-8 wordlists) via combinatorX.bin piped to hashcat
|
||||
def hcatCombinatorX(hcatHashType, hcatHashFile, wordlists, separator=None):
|
||||
global hcatCombinatorXCount
|
||||
global hcatProcess
|
||||
|
||||
if len(wordlists) < 2:
|
||||
print("[!] CombinatorX attack requires at least 2 wordlists.")
|
||||
return
|
||||
|
||||
combinatorX_bin = os.path.join(hate_path, "hashcat-utils/bin/combinatorX.bin")
|
||||
generator_cmd = [combinatorX_bin]
|
||||
for i, f in enumerate(wordlists[:8], start=1):
|
||||
generator_cmd += [f"--file{i}", f]
|
||||
if separator:
|
||||
generator_cmd += ["--sepFill", separator]
|
||||
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)
|
||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||
assert generator_proc.stdout is not None
|
||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
||||
generator_proc.stdout.close()
|
||||
try:
|
||||
hcatProcess.wait()
|
||||
generator_proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
||||
hcatProcess.kill()
|
||||
generator_proc.kill()
|
||||
|
||||
hcatCombinatorXCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||
|
||||
|
||||
# Hybrid Attack
|
||||
def hcatHybrid(hcatHashType, hcatHashFile, wordlists=None):
|
||||
global hcatHybridCount
|
||||
@@ -3301,6 +3383,15 @@ def middle_combinator():
|
||||
return _attacks.middle_combinator(_attack_ctx())
|
||||
|
||||
|
||||
def combinator3_crack():
|
||||
return _attacks.combinator3_crack(_attack_ctx())
|
||||
|
||||
|
||||
def combinatorX_crack():
|
||||
return _attacks.combinatorX_crack(_attack_ctx())
|
||||
|
||||
|
||||
|
||||
def combinator_submenu():
|
||||
return _attacks.combinator_submenu(_attack_ctx())
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from hate_crack.attacks import combinator3_crack, combinator_submenu, combinatorX_crack
|
||||
|
||||
|
||||
def _make_ctx(hash_type="1000", hash_file="/tmp/hashes.txt"):
|
||||
ctx = MagicMock()
|
||||
ctx.hcatHashType = hash_type
|
||||
ctx.hcatHashFile = hash_file
|
||||
return ctx
|
||||
|
||||
|
||||
class TestCombinator3Crack:
|
||||
def test_calls_hcatCombinator3_with_three_wordlists(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
for name in ["a.txt", "b.txt", "c.txt"]:
|
||||
(tmp_path / name).write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/b.txt,{tmp_path}/c.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg]):
|
||||
combinator3_crack(ctx)
|
||||
ctx.hcatCombinator3.assert_called_once()
|
||||
|
||||
def test_aborts_with_fewer_than_3_wordlists(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
(tmp_path / "a.txt").write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/a.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg]):
|
||||
combinator3_crack(ctx)
|
||||
ctx.hcatCombinator3.assert_not_called()
|
||||
|
||||
def test_aborts_when_no_wordlists_provided(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
with patch("builtins.input", side_effect=["n", ""]):
|
||||
combinator3_crack(ctx)
|
||||
ctx.hcatCombinator3.assert_not_called()
|
||||
|
||||
def test_passes_exactly_3_wordlists(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
for name in ["a.txt", "b.txt", "c.txt"]:
|
||||
(tmp_path / name).write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/b.txt,{tmp_path}/c.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg]):
|
||||
combinator3_crack(ctx)
|
||||
call_args = ctx.hcatCombinator3.call_args
|
||||
wordlists = call_args[0][2] if len(call_args[0]) >= 3 else call_args[1].get("wordlists")
|
||||
assert len(wordlists) == 3
|
||||
|
||||
|
||||
class TestCombinatorXCrack:
|
||||
def test_calls_hcatCombinatorX_with_wordlists(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
for name in ["a.txt", "b.txt"]:
|
||||
(tmp_path / name).write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/b.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg, ""]):
|
||||
combinatorX_crack(ctx)
|
||||
ctx.hcatCombinatorX.assert_called_once()
|
||||
|
||||
def test_passes_separator_to_hcatCombinatorX(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
for name in ["a.txt", "b.txt"]:
|
||||
(tmp_path / name).write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/b.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg, "-"]):
|
||||
combinatorX_crack(ctx)
|
||||
call_args = ctx.hcatCombinatorX.call_args
|
||||
# separator may be positional or keyword
|
||||
positional_has_sep = len(call_args[0]) >= 4 and call_args[0][3] == "-"
|
||||
keyword_has_sep = call_args[1].get("separator") == "-"
|
||||
assert positional_has_sep or keyword_has_sep
|
||||
|
||||
def test_aborts_with_fewer_than_2_wordlists(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
(tmp_path / "a.txt").write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg, ""]):
|
||||
combinatorX_crack(ctx)
|
||||
ctx.hcatCombinatorX.assert_not_called()
|
||||
|
||||
def test_no_separator_when_empty_input(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.hcatWordlists = str(tmp_path)
|
||||
for name in ["a.txt", "b.txt"]:
|
||||
(tmp_path / name).write_text("word\n")
|
||||
ctx._resolve_wordlist_path.side_effect = lambda p, base: p
|
||||
wl_arg = f"{tmp_path}/a.txt,{tmp_path}/b.txt"
|
||||
with patch("builtins.input", side_effect=["n", wl_arg, ""]):
|
||||
combinatorX_crack(ctx)
|
||||
call_args = ctx.hcatCombinatorX.call_args
|
||||
positional_sep = call_args[0][3] if len(call_args[0]) >= 4 else None
|
||||
keyword_sep = call_args[1].get("separator")
|
||||
# separator should be None or empty string when nothing entered
|
||||
assert positional_sep in (None, "") and keyword_sep in (None, "")
|
||||
|
||||
|
||||
class TestCombinatorSubmenuUpdated:
|
||||
def test_submenu_has_combinator3_option(self):
|
||||
ctx = _make_ctx()
|
||||
with patch("hate_crack.attacks.combinator3_crack") as mock_c3, patch(
|
||||
"hate_crack.attacks.interactive_menu", side_effect=["5", "99"]
|
||||
):
|
||||
combinator_submenu(ctx)
|
||||
mock_c3.assert_called_once_with(ctx)
|
||||
|
||||
def test_submenu_has_combinatorX_option(self):
|
||||
ctx = _make_ctx()
|
||||
with patch("hate_crack.attacks.combinatorX_crack") as mock_cx, patch(
|
||||
"hate_crack.attacks.interactive_menu", side_effect=["6", "99"]
|
||||
):
|
||||
combinator_submenu(ctx)
|
||||
mock_cx.assert_called_once_with(ctx)
|
||||
|
||||
def test_submenu_items_include_new_attacks(self):
|
||||
"""Verify the submenu item list advertises options 5 and 6."""
|
||||
ctx = _make_ctx()
|
||||
captured_items = []
|
||||
|
||||
def capture_menu(items, **kwargs):
|
||||
captured_items.extend(items)
|
||||
return "99"
|
||||
|
||||
with patch("hate_crack.attacks.interactive_menu", side_effect=capture_menu):
|
||||
combinator_submenu(ctx)
|
||||
|
||||
keys = [item[0] for item in captured_items]
|
||||
assert "5" in keys
|
||||
assert "6" in keys
|
||||
@@ -0,0 +1,300 @@
|
||||
"""Tests for hcatCombinator3 and hcatCombinatorX hashcat wrapper functions."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _make_mock_proc(wait_side_effect=None):
|
||||
proc = MagicMock()
|
||||
proc.stdout = MagicMock()
|
||||
if wait_side_effect is not None:
|
||||
proc.wait.side_effect = wait_side_effect
|
||||
else:
|
||||
proc.wait.return_value = None
|
||||
proc.pid = 12345
|
||||
return proc
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def main_module(hc_module):
|
||||
return hc_module._main
|
||||
|
||||
|
||||
class TestHcatCombinator3:
|
||||
def test_calls_combinator3_bin_with_three_files(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(3):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinator3" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinator3("1000", hash_file, wls)
|
||||
|
||||
calls = mock_popen.call_args_list
|
||||
assert len(calls) == 2
|
||||
combinator_cmd = calls[0][0][0]
|
||||
assert "combinator3" in combinator_cmd[0]
|
||||
assert wls[0] in combinator_cmd
|
||||
assert wls[1] in combinator_cmd
|
||||
assert wls[2] in combinator_cmd
|
||||
|
||||
def test_pipes_stdout_to_hashcat_stdin(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(3):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinator3" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinator3("1000", hash_file, wls)
|
||||
|
||||
calls = mock_popen.call_args_list
|
||||
hashcat_call_kwargs = calls[1][1]
|
||||
assert hashcat_call_kwargs.get("stdin") == combinator_proc.stdout
|
||||
|
||||
def test_aborts_with_fewer_than_3_wordlists(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wl1 = str(tmp_path / "w1.txt")
|
||||
wl2 = str(tmp_path / "w2.txt")
|
||||
for p in [wl1, wl2]:
|
||||
open(p, "w").close()
|
||||
|
||||
with patch("hate_crack.main.subprocess.Popen") as mock_popen:
|
||||
main_module.hcatCombinator3("1000", hash_file, [wl1, wl2])
|
||||
|
||||
mock_popen.assert_not_called()
|
||||
|
||||
def test_keyboard_interrupt_kills_both_processes(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(3):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinator3" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect),
|
||||
):
|
||||
main_module.hcatCombinator3("1000", hash_file, wls)
|
||||
|
||||
hashcat_proc.kill.assert_called_once()
|
||||
combinator_proc.kill.assert_called_once()
|
||||
|
||||
|
||||
class TestHcatCombinatorX:
|
||||
def test_calls_combinatorX_bin_with_file_flags(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(2):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinatorX" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinatorX("1000", hash_file, wls)
|
||||
|
||||
calls = mock_popen.call_args_list
|
||||
assert len(calls) == 2
|
||||
combinator_cmd = calls[0][0][0]
|
||||
assert "combinatorX" in combinator_cmd[0]
|
||||
assert "--file1" in combinator_cmd
|
||||
assert "--file2" in combinator_cmd
|
||||
|
||||
def test_passes_sepfill_when_separator_given(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(2):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinatorX" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinatorX("1000", hash_file, wls, separator="-")
|
||||
|
||||
combinator_cmd = mock_popen.call_args_list[0][0][0]
|
||||
assert "--sepFill" in combinator_cmd
|
||||
sep_idx = combinator_cmd.index("--sepFill")
|
||||
assert combinator_cmd[sep_idx + 1] == "-"
|
||||
|
||||
def test_no_sepfill_when_separator_is_none(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(2):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinatorX" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinatorX("1000", hash_file, wls, separator=None)
|
||||
|
||||
combinator_cmd = mock_popen.call_args_list[0][0][0]
|
||||
assert "--sepFill" not in combinator_cmd
|
||||
|
||||
def test_aborts_with_fewer_than_2_wordlists(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wl1 = str(tmp_path / "w1.txt")
|
||||
open(wl1, "w").close()
|
||||
|
||||
with patch("hate_crack.main.subprocess.Popen") as mock_popen:
|
||||
main_module.hcatCombinatorX("1000", hash_file, [wl1])
|
||||
|
||||
mock_popen.assert_not_called()
|
||||
|
||||
def test_supports_up_to_8_wordlists(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(8):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc()
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinatorX" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect) as mock_popen,
|
||||
):
|
||||
main_module.hcatCombinatorX("1000", hash_file, wls)
|
||||
|
||||
combinator_cmd = mock_popen.call_args_list[0][0][0]
|
||||
for i in range(1, 9):
|
||||
assert f"--file{i}" in combinator_cmd
|
||||
|
||||
def test_keyboard_interrupt_kills_both_processes(self, main_module, tmp_path):
|
||||
hash_file = str(tmp_path / "hashes.txt")
|
||||
wls = []
|
||||
for i in range(2):
|
||||
p = str(tmp_path / f"w{i}.txt")
|
||||
open(p, "w").close()
|
||||
wls.append(p)
|
||||
|
||||
combinator_proc = _make_mock_proc()
|
||||
hashcat_proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||
|
||||
def popen_side_effect(cmd, **kwargs):
|
||||
if "combinatorX" in str(cmd[0]):
|
||||
return combinator_proc
|
||||
return hashcat_proc
|
||||
|
||||
with (
|
||||
patch.object(main_module, "hcatBin", "hashcat"),
|
||||
patch.object(main_module, "hcatTuning", ""),
|
||||
patch.object(main_module, "hcatPotfilePath", ""),
|
||||
patch.object(main_module, "hcatWordlists", str(tmp_path)),
|
||||
patch.object(main_module, "generate_session_id", return_value="sess123"),
|
||||
patch.object(main_module, "lineCount", return_value=0),
|
||||
patch("hate_crack.main.subprocess.Popen", side_effect=popen_side_effect),
|
||||
):
|
||||
main_module.hcatCombinatorX("1000", hash_file, wls)
|
||||
|
||||
hashcat_proc.kill.assert_called_once()
|
||||
combinator_proc.kill.assert_called_once()
|
||||
Reference in New Issue
Block a user