mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-04-28 03:53:10 -07:00
feat(notify): wire Pushover notifications into attacks and config (WIP)
Adds notify_* keys to both config.json.example files, threads notification calls through hashcat invocations in main.py, and exposes menu/attack hooks. Pushed for manual testing — verification and PR still pending. Refs #106 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,5 +37,13 @@
|
|||||||
"hcatCombinator3", "hcatCombinatorX", "hcatHybrid", "hcatYoloCombination",
|
"hcatCombinator3", "hcatCombinatorX", "hcatHybrid", "hcatYoloCombination",
|
||||||
"hcatMiddleCombinator", "hcatThoroughCombinator", "hcatCombipow", "hcatPrince",
|
"hcatMiddleCombinator", "hcatThoroughCombinator", "hcatCombipow", "hcatPrince",
|
||||||
"hcatPermute"
|
"hcatPermute"
|
||||||
]
|
],
|
||||||
|
"notify_enabled": false,
|
||||||
|
"notify_pushover_token": "",
|
||||||
|
"notify_pushover_user": "",
|
||||||
|
"notify_per_crack_enabled": false,
|
||||||
|
"notify_attack_allowlist": [],
|
||||||
|
"notify_suppress_in_orchestrators": true,
|
||||||
|
"notify_max_cracks_per_burst": 5,
|
||||||
|
"notify_poll_interval_seconds": 5.0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ def get_main_menu_options():
|
|||||||
"22": _attacks.combipow_crack,
|
"22": _attacks.combipow_crack,
|
||||||
"80": _attacks.wordlist_tools_submenu,
|
"80": _attacks.wordlist_tools_submenu,
|
||||||
"81": _attacks.rule_tools_submenu,
|
"81": _attacks.rule_tools_submenu,
|
||||||
|
"83": toggle_notifications,
|
||||||
"90": download_hashmob_rules,
|
"90": download_hashmob_rules,
|
||||||
"91": weakpass_wordlist_menu,
|
"91": weakpass_wordlist_menu,
|
||||||
"92": download_hashmob_wordlists,
|
"92": download_hashmob_wordlists,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import readline
|
import readline
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from hate_crack import notify as _notify
|
||||||
from hate_crack.api import download_hashmob_rules
|
from hate_crack.api import download_hashmob_rules
|
||||||
from hate_crack.formatting import print_multicolumn_list
|
from hate_crack.formatting import print_multicolumn_list
|
||||||
from hate_crack.menu import interactive_menu
|
from hate_crack.menu import interactive_menu
|
||||||
@@ -107,6 +108,7 @@ def _select_rules(ctx) -> list[str] | None:
|
|||||||
|
|
||||||
|
|
||||||
def quick_crack(ctx: Any) -> None:
|
def quick_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Quick Crack")
|
||||||
wordlist_choice = None
|
wordlist_choice = None
|
||||||
default_dir = ctx.hcatOptimizedWordlists
|
default_dir = ctx.hcatOptimizedWordlists
|
||||||
|
|
||||||
@@ -177,6 +179,7 @@ def quick_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def loopback_attack(ctx: Any) -> None:
|
def loopback_attack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Loopback")
|
||||||
empty_wordlist = os.path.join(ctx.hcatWordlists, "empty.txt")
|
empty_wordlist = os.path.join(ctx.hcatWordlists, "empty.txt")
|
||||||
os.makedirs(ctx.hcatWordlists, exist_ok=True)
|
os.makedirs(ctx.hcatWordlists, exist_ok=True)
|
||||||
if not os.path.exists(empty_wordlist):
|
if not os.path.exists(empty_wordlist):
|
||||||
@@ -200,26 +203,43 @@ def loopback_attack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def extensive_crack(ctx: Any) -> None:
|
def extensive_crack(ctx: Any) -> None:
|
||||||
ctx.hcatBruteForce(ctx.hcatHashType, ctx.hcatHashFile, "1", "7")
|
# Orchestrator attack: chains ~14 primitives. We suppress each primitive's
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatBruteCount)
|
# own notifications and fire exactly one "Extensive Crack complete" at the
|
||||||
ctx.hcatDictionary(ctx.hcatHashType, ctx.hcatHashFile)
|
# end with the aggregate delta. This both prevents notification spam and
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatDictionaryCount)
|
# gives the user an actually-useful summary.
|
||||||
hcatTargetTime = 4 * 60 * 60
|
_notify.prompt_notify_for_attack("Extensive Crack")
|
||||||
ctx.hcatTopMask(ctx.hcatHashType, ctx.hcatHashFile, hcatTargetTime)
|
out_path = ctx.hcatHashFile + ".out"
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatMaskCount)
|
cracked_before = ctx.lineCount(out_path) if os.path.exists(out_path) else 0
|
||||||
ctx.hcatFingerprint(
|
with _notify.suppressed_notifications():
|
||||||
ctx.hcatHashType, ctx.hcatHashFile, 7, run_hybrid_on_expanded=False
|
ctx.hcatBruteForce(ctx.hcatHashType, ctx.hcatHashFile, "1", "7")
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatBruteCount)
|
||||||
|
ctx.hcatDictionary(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatDictionaryCount)
|
||||||
|
hcatTargetTime = 4 * 60 * 60
|
||||||
|
ctx.hcatTopMask(ctx.hcatHashType, ctx.hcatHashFile, hcatTargetTime)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatMaskCount)
|
||||||
|
ctx.hcatFingerprint(
|
||||||
|
ctx.hcatHashType, ctx.hcatHashFile, 7, run_hybrid_on_expanded=False
|
||||||
|
)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatFingerprintCount)
|
||||||
|
ctx.hcatCombination(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatCombinationCount)
|
||||||
|
ctx.hcatHybrid(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatHybridCount)
|
||||||
|
ctx.hcatGoodMeasure(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatExtraCount)
|
||||||
|
cracked_after = ctx.lineCount(out_path) if os.path.exists(out_path) else 0
|
||||||
|
_notify.notify_job_done(
|
||||||
|
"Extensive Crack", cracked_after, ctx.hcatHashFile
|
||||||
)
|
)
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatFingerprintCount)
|
# Note: ``cracked_before`` is tracked for potential future per-orchestrator
|
||||||
ctx.hcatCombination(ctx.hcatHashType, ctx.hcatHashFile)
|
# delta reporting, but today the notify message uses the absolute count
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatCombinationCount)
|
# because that matches what single-attack notifications already report.
|
||||||
ctx.hcatHybrid(ctx.hcatHashType, ctx.hcatHashFile)
|
_ = cracked_before
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatHybridCount)
|
|
||||||
ctx.hcatGoodMeasure(ctx.hcatHashType, ctx.hcatHashFile)
|
|
||||||
ctx.hcatRecycle(ctx.hcatHashType, ctx.hcatHashFile, ctx.hcatExtraCount)
|
|
||||||
|
|
||||||
|
|
||||||
def brute_force_crack(ctx: Any) -> None:
|
def brute_force_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Brute Force")
|
||||||
hcatMinLen = int(
|
hcatMinLen = int(
|
||||||
input("\nEnter the minimum password length to brute force (1): ") or 1
|
input("\nEnter the minimum password length to brute force (1): ") or 1
|
||||||
)
|
)
|
||||||
@@ -230,6 +250,7 @@ def brute_force_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def top_mask_crack(ctx: Any) -> None:
|
def top_mask_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Top Mask")
|
||||||
hcatTargetTime = int(
|
hcatTargetTime = int(
|
||||||
input("\nEnter a target time for completion in hours (4): ") or 4
|
input("\nEnter a target time for completion in hours (4): ") or 4
|
||||||
)
|
)
|
||||||
@@ -238,6 +259,7 @@ def top_mask_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def fingerprint_crack(ctx: Any) -> None:
|
def fingerprint_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Fingerprint")
|
||||||
while True:
|
while True:
|
||||||
raw = input("\nEnter expander max length (7-36) (7): ").strip()
|
raw = input("\nEnter expander max length (7-36) (7): ").strip()
|
||||||
if raw == "":
|
if raw == "":
|
||||||
@@ -261,6 +283,7 @@ def fingerprint_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def combinator_crack(ctx: Any) -> None:
|
def combinator_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Combinator")
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print("COMBINATOR ATTACK")
|
print("COMBINATOR ATTACK")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
@@ -299,6 +322,7 @@ def combinator_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def hybrid_crack(ctx: Any) -> None:
|
def hybrid_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Hybrid")
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print("HYBRID ATTACK")
|
print("HYBRID ATTACK")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
@@ -367,22 +391,27 @@ def hybrid_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def pathwell_crack(ctx: Any) -> None:
|
def pathwell_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Pathwell Brute Force")
|
||||||
ctx.hcatPathwellBruteForce(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatPathwellBruteForce(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
def prince_attack(ctx: Any) -> None:
|
def prince_attack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("PRINCE")
|
||||||
ctx.hcatPrince(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatPrince(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
def yolo_combination(ctx: Any) -> None:
|
def yolo_combination(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("YOLO Combination")
|
||||||
ctx.hcatYoloCombination(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatYoloCombination(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
def thorough_combinator(ctx: Any) -> None:
|
def thorough_combinator(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Thorough Combinator")
|
||||||
ctx.hcatThoroughCombinator(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatThoroughCombinator(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
def middle_combinator(ctx: Any) -> None:
|
def middle_combinator(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Middle Combinator")
|
||||||
ctx.hcatMiddleCombinator(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatMiddleCombinator(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
@@ -447,10 +476,12 @@ def combinator_3plus_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def bandrel_method(ctx: Any) -> None:
|
def bandrel_method(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Bandrel")
|
||||||
ctx.hcatBandrel(ctx.hcatHashType, ctx.hcatHashFile)
|
ctx.hcatBandrel(ctx.hcatHashType, ctx.hcatHashFile)
|
||||||
|
|
||||||
|
|
||||||
def ollama_attack(ctx: Any) -> None:
|
def ollama_attack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("LLM")
|
||||||
print("\n\tLLM Attack")
|
print("\n\tLLM Attack")
|
||||||
company = input("Company name: ").strip()
|
company = input("Company name: ").strip()
|
||||||
industry = input("Industry: ").strip()
|
industry = input("Industry: ").strip()
|
||||||
@@ -491,6 +522,7 @@ def _omen_pick_training_wordlist(ctx: Any):
|
|||||||
|
|
||||||
|
|
||||||
def omen_attack(ctx: Any) -> None:
|
def omen_attack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("OMEN")
|
||||||
print("\n\tOMEN Attack (Ordered Markov ENumerator)")
|
print("\n\tOMEN Attack (Ordered Markov ENumerator)")
|
||||||
omen_dir = os.path.join(ctx.hate_path, "omen")
|
omen_dir = os.path.join(ctx.hate_path, "omen")
|
||||||
create_bin = os.path.join(omen_dir, ctx.hcatOmenCreateBin)
|
create_bin = os.path.join(omen_dir, ctx.hcatOmenCreateBin)
|
||||||
@@ -579,6 +611,7 @@ def _markov_pick_training_source(ctx: Any):
|
|||||||
|
|
||||||
|
|
||||||
def adhoc_mask_crack(ctx: Any) -> None:
|
def adhoc_mask_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Ad-hoc Mask")
|
||||||
print(
|
print(
|
||||||
"\nEnter a hashcat mask. Tokens: ?l=lower ?u=upper ?d=digit ?s=special ?a=all ?b=binary ?1-?4=custom"
|
"\nEnter a hashcat mask. Tokens: ?l=lower ?u=upper ?d=digit ?s=special ?a=all ?b=binary ?1-?4=custom"
|
||||||
)
|
)
|
||||||
@@ -603,6 +636,7 @@ def adhoc_mask_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def markov_brute_force(ctx: Any) -> None:
|
def markov_brute_force(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Markov Brute Force")
|
||||||
print("\n\tMarkov Brute Force Attack")
|
print("\n\tMarkov Brute Force Attack")
|
||||||
hcstat2_path = f"{ctx.hcatHashFile}.hcstat2"
|
hcstat2_path = f"{ctx.hcatHashFile}.hcstat2"
|
||||||
need_training = True
|
need_training = True
|
||||||
@@ -640,6 +674,7 @@ def markov_brute_force(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def combipow_crack(ctx: Any) -> None:
|
def combipow_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Combipow")
|
||||||
wordlist = None
|
wordlist = None
|
||||||
while wordlist is None:
|
while wordlist is None:
|
||||||
path = input("\n[*] Enter path to wordlist (max 63 lines recommended): ").strip()
|
path = input("\n[*] Enter path to wordlist (max 63 lines recommended): ").strip()
|
||||||
@@ -665,6 +700,7 @@ def combipow_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def generate_rules_crack(ctx: Any) -> None:
|
def generate_rules_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Random Rules")
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print("RANDOM RULES ATTACK")
|
print("RANDOM RULES ATTACK")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
@@ -743,6 +779,7 @@ def generate_rules_crack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def ngram_attack(ctx: Any) -> None:
|
def ngram_attack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("N-gram")
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print("NGRAM ATTACK")
|
print("NGRAM ATTACK")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
@@ -769,6 +806,7 @@ def ngram_attack(ctx: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def permute_crack(ctx: Any) -> None:
|
def permute_crack(ctx: Any) -> None:
|
||||||
|
_notify.prompt_notify_for_attack("Permute")
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print("PERMUTATION ATTACK")
|
print("PERMUTATION ATTACK")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|||||||
@@ -25,5 +25,13 @@
|
|||||||
"passgptModel": "javirandor/passgpt-10characters",
|
"passgptModel": "javirandor/passgpt-10characters",
|
||||||
"passgptMaxCandidates": 1000000,
|
"passgptMaxCandidates": 1000000,
|
||||||
"passgptBatchSize": 1024,
|
"passgptBatchSize": 1024,
|
||||||
"passgptTrainingList": ""
|
"passgptTrainingList": "",
|
||||||
|
"notify_enabled": false,
|
||||||
|
"notify_pushover_token": "",
|
||||||
|
"notify_pushover_user": "",
|
||||||
|
"notify_per_crack_enabled": false,
|
||||||
|
"notify_attack_allowlist": [],
|
||||||
|
"notify_suppress_in_orchestrators": true,
|
||||||
|
"notify_max_cracks_per_burst": 5,
|
||||||
|
"notify_poll_interval_seconds": 5.0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -443,6 +443,14 @@ except KeyError:
|
|||||||
pass
|
pass
|
||||||
check_for_updates_enabled = config_parser.get("check_for_updates", True)
|
check_for_updates_enabled = config_parser.get("check_for_updates", True)
|
||||||
|
|
||||||
|
# Notification subsystem bootstrap. The notify module stores its own
|
||||||
|
# settings snapshot; we just hand it the resolved config path so it can
|
||||||
|
# atomically rewrite config.json when the user toggles enabled / answers
|
||||||
|
# "always" at a prompt.
|
||||||
|
from hate_crack import notify as _notify # noqa: E402 (kept close to config load)
|
||||||
|
|
||||||
|
_notify.init(_config_path, config_parser)
|
||||||
|
|
||||||
hcatExpanderBin = "expander.bin"
|
hcatExpanderBin = "expander.bin"
|
||||||
hcatCombinatorBin = "combinator.bin"
|
hcatCombinatorBin = "combinator.bin"
|
||||||
hcatPrinceBin = "pp64.bin"
|
hcatPrinceBin = "pp64.bin"
|
||||||
@@ -729,6 +737,93 @@ def _debug_cmd(cmd):
|
|||||||
print(f"[DEBUG] hashcat cmd: {_format_cmd(cmd)}")
|
print(f"[DEBUG] hashcat cmd: {_format_cmd(cmd)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _run_hcat_cmd(
|
||||||
|
cmd,
|
||||||
|
attack_name: str = "",
|
||||||
|
hash_file: str | None = None,
|
||||||
|
*,
|
||||||
|
stdin=None,
|
||||||
|
companion_procs=None,
|
||||||
|
reraise_interrupt: bool = False,
|
||||||
|
out_path: str | None = None,
|
||||||
|
):
|
||||||
|
"""Execute a hashcat subprocess and bracket it with notify hooks.
|
||||||
|
|
||||||
|
This consolidates the ``hcatProcess = subprocess.Popen(cmd); try:
|
||||||
|
wait() except KeyboardInterrupt: kill()`` dance that was duplicated
|
||||||
|
at ~31 sites in this module. The payoff: every hashcat invocation
|
||||||
|
now fires job-done notifications consistently, and the per-crack
|
||||||
|
tailer lifecycle is handled in exactly one place.
|
||||||
|
|
||||||
|
- ``attack_name`` is the label that appears in notifications. Pass
|
||||||
|
an empty string for no-notify invocations.
|
||||||
|
- ``hash_file`` is required to locate ``{hash_file}.out`` for the
|
||||||
|
tailer. When omitted, we skip the tailer and the job-done count.
|
||||||
|
- ``stdin`` mirrors the ``subprocess.Popen(..., stdin=...)`` kwarg
|
||||||
|
for generator-pipe callers.
|
||||||
|
- ``companion_procs`` is a list of generator ``Popen`` handles that
|
||||||
|
feed into this hashcat instance. On normal completion we
|
||||||
|
``wait()`` them; on ``KeyboardInterrupt`` we ``kill()`` them
|
||||||
|
alongside the hashcat process. This preserves the prior behavior
|
||||||
|
where a ctrl-C must tear down both sides of a pipe.
|
||||||
|
|
||||||
|
Notifications are fire-and-forget: suppression (see
|
||||||
|
``notify.suppressed_notifications``) and disabled-globally state are
|
||||||
|
both handled inside the notify module, so callers need not branch.
|
||||||
|
"""
|
||||||
|
global hcatProcess
|
||||||
|
|
||||||
|
companions = list(companion_procs) if companion_procs else []
|
||||||
|
|
||||||
|
# Resolve the output file path used for the tailer and cracked-count
|
||||||
|
# readback. Most hashcat calls write to ``{hash_file}.out``; a few
|
||||||
|
# multi-phase flows (LM-to-NT) write to a different file, in which
|
||||||
|
# case the caller passes ``out_path`` explicitly.
|
||||||
|
resolved_out = out_path if out_path else (hash_file + ".out" if hash_file else None)
|
||||||
|
|
||||||
|
tailer = None
|
||||||
|
if attack_name and resolved_out and not _notify.is_suppressed():
|
||||||
|
tailer = _notify.start_tailer(resolved_out, attack_name)
|
||||||
|
|
||||||
|
popen_kwargs = {"stdin": stdin} if stdin is not None else {}
|
||||||
|
hcatProcess = subprocess.Popen(cmd, **popen_kwargs)
|
||||||
|
interrupted = False
|
||||||
|
try:
|
||||||
|
hcatProcess.wait()
|
||||||
|
for gen in companions:
|
||||||
|
try:
|
||||||
|
gen.wait()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
interrupted = True
|
||||||
|
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
||||||
|
hcatProcess.kill()
|
||||||
|
for gen in companions:
|
||||||
|
try:
|
||||||
|
gen.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
_notify.stop_tailer(tailer)
|
||||||
|
|
||||||
|
# Only incur a lineCount read when notifications will actually fire.
|
||||||
|
# This avoids disturbing existing tests that assert a specific number
|
||||||
|
# of file reads during an attack; ``_should_fire`` mirrors the check
|
||||||
|
# inside ``notify_job_done`` itself.
|
||||||
|
if (
|
||||||
|
attack_name
|
||||||
|
and resolved_out
|
||||||
|
and not _notify.is_suppressed()
|
||||||
|
and _notify.get_settings().enabled
|
||||||
|
):
|
||||||
|
cracked = lineCount(resolved_out)
|
||||||
|
_notify.notify_job_done(attack_name, cracked, hash_file or resolved_out)
|
||||||
|
|
||||||
|
if interrupted and reraise_interrupt:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
def _is_gzipped(path: str) -> bool:
|
def _is_gzipped(path: str) -> bool:
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
@@ -1189,12 +1284,7 @@ def hcatBruteForce(hcatHashType, hcatHashFile, hcatMinLen, hcatMaxLen):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Brute Force", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
hcatBruteCount = lineCount(hcatHashFile + ".out")
|
hcatBruteCount = lineCount(hcatHashFile + ".out")
|
||||||
|
|
||||||
@@ -1226,12 +1316,7 @@ def hcatDictionary(hcatHashType, hcatHashFile):
|
|||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Dictionary", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
rule_d3ad0ne = get_rule_path("d3ad0ne.rule")
|
rule_d3ad0ne = get_rule_path("d3ad0ne.rule")
|
||||||
rule_toxic = get_rule_path("T0XlC.rule")
|
rule_toxic = get_rule_path("T0XlC.rule")
|
||||||
@@ -1267,12 +1352,7 @@ def hcatDictionary(hcatHashType, hcatHashFile):
|
|||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Dictionary", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
finally:
|
finally:
|
||||||
os.unlink(combined_path)
|
os.unlink(combined_path)
|
||||||
|
|
||||||
@@ -1316,12 +1396,7 @@ def hcatQuickDictionary(
|
|||||||
)
|
)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
_debug_cmd(cmd)
|
_debug_cmd(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Quick Dictionary", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
# Top Mask Attack
|
# Top Mask Attack
|
||||||
@@ -1383,12 +1458,7 @@ def hcatTopMask(hcatHashType, hcatHashFile, hcatTargetTime):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Top Mask", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
hcatMaskCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatMaskCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1462,12 +1532,9 @@ def hcatFingerprint(
|
|||||||
_insert_optimized_flag(fingerprint_cmd)
|
_insert_optimized_flag(fingerprint_cmd)
|
||||||
fingerprint_cmd.extend(shlex.split(hcatTuning))
|
fingerprint_cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(fingerprint_cmd)
|
_append_potfile_arg(fingerprint_cmd)
|
||||||
hcatProcess = subprocess.Popen(fingerprint_cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
fingerprint_cmd, attack_name="Fingerprint", hash_file=hcatHashFile
|
||||||
hcatProcess.wait()
|
)
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
# Secondary attack: run hybrid on the expanded candidates (mode 6/7 variants).
|
# Secondary attack: run hybrid on the expanded candidates (mode 6/7 variants).
|
||||||
# This is intentionally optional to avoid changing the "extensive" pipeline ordering.
|
# This is intentionally optional to avoid changing the "extensive" pipeline ordering.
|
||||||
@@ -1529,12 +1596,7 @@ def hcatCombination(hcatHashType, hcatHashFile, wordlists=None):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Combination", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
hcatCombinationCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatCombinationCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1568,15 +1630,15 @@ def hcatCombinator3(hcatHashType, hcatHashFile, wordlists):
|
|||||||
_append_potfile_arg(hashcat_cmd)
|
_append_potfile_arg(hashcat_cmd)
|
||||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||||
assert generator_proc.stdout is not None
|
assert generator_proc.stdout is not None
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
_run_hcat_cmd(
|
||||||
generator_proc.stdout.close()
|
hashcat_cmd,
|
||||||
try:
|
attack_name="Combinator3",
|
||||||
hcatProcess.wait()
|
hash_file=hcatHashFile,
|
||||||
generator_proc.wait()
|
stdin=generator_proc.stdout,
|
||||||
except KeyboardInterrupt:
|
companion_procs=[generator_proc],
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
)
|
||||||
hcatProcess.kill()
|
if generator_proc.stdout:
|
||||||
generator_proc.kill()
|
generator_proc.stdout.close()
|
||||||
|
|
||||||
hcatCombinator3Count = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatCombinator3Count = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1614,15 +1676,15 @@ def hcatCombinatorX(hcatHashType, hcatHashFile, wordlists, separator=None):
|
|||||||
_append_potfile_arg(hashcat_cmd)
|
_append_potfile_arg(hashcat_cmd)
|
||||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||||
assert generator_proc.stdout is not None
|
assert generator_proc.stdout is not None
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
_run_hcat_cmd(
|
||||||
generator_proc.stdout.close()
|
hashcat_cmd,
|
||||||
try:
|
attack_name="CombinatorX",
|
||||||
hcatProcess.wait()
|
hash_file=hcatHashFile,
|
||||||
generator_proc.wait()
|
stdin=generator_proc.stdout,
|
||||||
except KeyboardInterrupt:
|
companion_procs=[generator_proc],
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
)
|
||||||
hcatProcess.kill()
|
if generator_proc.stdout:
|
||||||
generator_proc.kill()
|
generator_proc.stdout.close()
|
||||||
|
|
||||||
hcatCombinatorXCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatCombinatorXCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1649,15 +1711,15 @@ def hcatNgramX(hcatHashType, hcatHashFile, corpus, group_size=3):
|
|||||||
_append_potfile_arg(hashcat_cmd)
|
_append_potfile_arg(hashcat_cmd)
|
||||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||||
assert generator_proc.stdout is not None
|
assert generator_proc.stdout is not None
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
_run_hcat_cmd(
|
||||||
generator_proc.stdout.close()
|
hashcat_cmd,
|
||||||
try:
|
attack_name="NgramX",
|
||||||
hcatProcess.wait()
|
hash_file=hcatHashFile,
|
||||||
generator_proc.wait()
|
stdin=generator_proc.stdout,
|
||||||
except KeyboardInterrupt:
|
companion_procs=[generator_proc],
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
)
|
||||||
hcatProcess.kill()
|
if generator_proc.stdout:
|
||||||
generator_proc.kill()
|
generator_proc.stdout.close()
|
||||||
|
|
||||||
hcatNgramXCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatNgramXCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1711,12 +1773,7 @@ def hcatHybrid(hcatHashType, hcatHashFile, wordlists=None):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Hybrid", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
hcatHybridCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatHybridCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -1749,13 +1806,12 @@ def hcatYoloCombination(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="YOLO Combination",
|
||||||
except KeyboardInterrupt:
|
hash_file=hcatHashFile,
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
reraise_interrupt=True,
|
||||||
hcatProcess.kill()
|
)
|
||||||
raise
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1800,12 +1856,7 @@ def hcatBandrel(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Bandrel", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
print(
|
print(
|
||||||
"Checking passwords against pipal for top {0} passwords and basewords".format(
|
"Checking passwords against pipal for top {0} passwords and basewords".format(
|
||||||
pipal_count
|
pipal_count
|
||||||
@@ -1845,12 +1896,7 @@ def hcatBandrel(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Bandrel", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
# Pull an Ollama model via the /api/pull streaming endpoint
|
# Pull an Ollama model via the /api/pull streaming endpoint
|
||||||
@@ -2053,12 +2099,14 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data):
|
|||||||
]
|
]
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
|
||||||
try:
|
try:
|
||||||
hcatProcess.wait()
|
_run_hcat_cmd(
|
||||||
|
cmd,
|
||||||
|
attack_name="LLM",
|
||||||
|
hash_file=hcatHashFile,
|
||||||
|
reraise_interrupt=True,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step D: Run hashcat with LLM candidates against every rule in the rules directory
|
# Step D: Run hashcat with LLM candidates against every rule in the rules directory
|
||||||
@@ -2088,12 +2136,14 @@ def hcatOllama(hcatHashType, hcatHashFile, mode, context_data):
|
|||||||
]
|
]
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
|
||||||
try:
|
try:
|
||||||
hcatProcess.wait()
|
_run_hcat_cmd(
|
||||||
|
cmd,
|
||||||
|
attack_name="LLM",
|
||||||
|
hash_file=hcatHashFile,
|
||||||
|
reraise_interrupt=True,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -2135,13 +2185,12 @@ def hcatMiddleCombinator(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="Middle Combinator",
|
||||||
except KeyboardInterrupt:
|
hash_file=hcatHashFile,
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
reraise_interrupt=True,
|
||||||
hcatProcess.kill()
|
)
|
||||||
raise
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -2180,12 +2229,7 @@ def hcatThoroughCombinator(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Thorough Combinator", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for x in range(len(masks)):
|
for x in range(len(masks)):
|
||||||
@@ -2209,13 +2253,12 @@ def hcatThoroughCombinator(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="Thorough Combinator",
|
||||||
except KeyboardInterrupt:
|
hash_file=hcatHashFile,
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
reraise_interrupt=True,
|
||||||
hcatProcess.kill()
|
)
|
||||||
raise
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
@@ -2240,13 +2283,12 @@ def hcatThoroughCombinator(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="Thorough Combinator",
|
||||||
except KeyboardInterrupt:
|
hash_file=hcatHashFile,
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
reraise_interrupt=True,
|
||||||
hcatProcess.kill()
|
)
|
||||||
raise
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
@@ -2273,11 +2315,14 @@ def hcatThoroughCombinator(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
hcatProcess.wait()
|
cmd,
|
||||||
|
attack_name="Thorough Combinator",
|
||||||
|
hash_file=hcatHashFile,
|
||||||
|
reraise_interrupt=True,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
pass
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
# Pathwell Mask Brute Force Attack
|
# Pathwell Mask Brute Force Attack
|
||||||
@@ -2300,12 +2345,7 @@ def hcatPathwellBruteForce(hcatHashType, hcatHashFile):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Pathwell Brute Force", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
def hcatAdHocMask(hcatHashType, hcatHashFile, mask, custom_charsets=""):
|
def hcatAdHocMask(hcatHashType, hcatHashFile, mask, custom_charsets=""):
|
||||||
@@ -2329,12 +2369,7 @@ def hcatAdHocMask(hcatHashType, hcatHashFile, mask, custom_charsets=""):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Ad-hoc Mask", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
def hcatMarkovTrain(source_file, hcatHashFile):
|
def hcatMarkovTrain(source_file, hcatHashFile):
|
||||||
@@ -2429,12 +2464,7 @@ def hcatMarkovBruteForce(hcatHashType, hcatHashFile, hcatMinLen, hcatMaxLen):
|
|||||||
_insert_optimized_flag(cmd)
|
_insert_optimized_flag(cmd)
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Markov Brute Force", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
# Combipow Passphrase Attack
|
# Combipow Passphrase Attack
|
||||||
@@ -2478,15 +2508,16 @@ def hcatCombipow(hcatHashType, hcatHashFile, wordlist, use_space_sep=True):
|
|||||||
hashcat_cmd.extend(shlex.split(hcatTuning))
|
hashcat_cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(hashcat_cmd)
|
_append_potfile_arg(hashcat_cmd)
|
||||||
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
generator_proc = subprocess.Popen(generator_cmd, stdout=subprocess.PIPE)
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=generator_proc.stdout)
|
|
||||||
generator_proc.stdout.close()
|
|
||||||
try:
|
try:
|
||||||
hcatProcess.wait()
|
_run_hcat_cmd(
|
||||||
generator_proc.wait()
|
hashcat_cmd,
|
||||||
except KeyboardInterrupt:
|
attack_name="Combipow",
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
hash_file=hcatHashFile,
|
||||||
hcatProcess.kill()
|
stdin=generator_proc.stdout,
|
||||||
generator_proc.kill()
|
companion_procs=[generator_proc],
|
||||||
|
)
|
||||||
|
if generator_proc.stdout:
|
||||||
|
generator_proc.stdout.close()
|
||||||
finally:
|
finally:
|
||||||
if tmp_file is not None:
|
if tmp_file is not None:
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
@@ -2532,15 +2563,15 @@ def hcatPrince(hcatHashType, hcatHashFile):
|
|||||||
hashcat_cmd = _add_debug_mode_for_rules(hashcat_cmd)
|
hashcat_cmd = _add_debug_mode_for_rules(hashcat_cmd)
|
||||||
with _open_wordlist(prince_base) as base:
|
with _open_wordlist(prince_base) as base:
|
||||||
prince_proc = subprocess.Popen(prince_cmd, stdin=base, stdout=subprocess.PIPE)
|
prince_proc = subprocess.Popen(prince_cmd, stdin=base, stdout=subprocess.PIPE)
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=prince_proc.stdout)
|
_run_hcat_cmd(
|
||||||
prince_proc.stdout.close()
|
hashcat_cmd,
|
||||||
try:
|
attack_name="PRINCE",
|
||||||
hcatProcess.wait()
|
hash_file=hcatHashFile,
|
||||||
prince_proc.wait()
|
stdin=prince_proc.stdout,
|
||||||
except KeyboardInterrupt:
|
companion_procs=[prince_proc],
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
)
|
||||||
hcatProcess.kill()
|
if prince_proc.stdout:
|
||||||
prince_proc.kill()
|
prince_proc.stdout.close()
|
||||||
|
|
||||||
|
|
||||||
def hcatPermute(hcatHashType, hcatHashFile, wordlist):
|
def hcatPermute(hcatHashType, hcatHashFile, wordlist):
|
||||||
@@ -2570,17 +2601,15 @@ def hcatPermute(hcatHashType, hcatHashFile, wordlist):
|
|||||||
permute_proc = subprocess.Popen(
|
permute_proc = subprocess.Popen(
|
||||||
[permute_path], stdin=wl_file, stdout=subprocess.PIPE
|
[permute_path], stdin=wl_file, stdout=subprocess.PIPE
|
||||||
)
|
)
|
||||||
hcatProcess = subprocess.Popen(
|
_run_hcat_cmd(
|
||||||
hashcat_cmd, stdin=permute_proc.stdout
|
hashcat_cmd,
|
||||||
|
attack_name="Permute",
|
||||||
|
hash_file=hcatHashFile,
|
||||||
|
stdin=permute_proc.stdout,
|
||||||
|
companion_procs=[permute_proc],
|
||||||
)
|
)
|
||||||
permute_proc.stdout.close()
|
if permute_proc.stdout:
|
||||||
try:
|
permute_proc.stdout.close()
|
||||||
hcatProcess.wait()
|
|
||||||
permute_proc.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(f"Killing PID {hcatProcess.pid}...")
|
|
||||||
hcatProcess.kill()
|
|
||||||
permute_proc.kill()
|
|
||||||
hcatPermuteCount = lineCount(f"{hcatHashFile}.out") - hcatHashCracked
|
hcatPermuteCount = lineCount(f"{hcatHashFile}.out") - hcatHashCracked
|
||||||
|
|
||||||
|
|
||||||
@@ -2709,16 +2738,21 @@ def hcatOmen(hcatHashType, hcatHashFile, max_candidates, hcatChains=""):
|
|||||||
enum_proc = subprocess.Popen(
|
enum_proc = subprocess.Popen(
|
||||||
enum_cmd, cwd=model_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
enum_cmd, cwd=model_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
hcatProcess = subprocess.Popen(hashcat_cmd, stdin=enum_proc.stdout)
|
|
||||||
enum_proc.stdout.close()
|
|
||||||
try:
|
try:
|
||||||
hcatProcess.wait()
|
_run_hcat_cmd(
|
||||||
enum_proc.wait()
|
hashcat_cmd,
|
||||||
|
attack_name="OMEN",
|
||||||
|
hash_file=hcatHashFile,
|
||||||
|
stdin=enum_proc.stdout,
|
||||||
|
companion_procs=[enum_proc],
|
||||||
|
reraise_interrupt=True,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
if enum_proc.stderr:
|
||||||
hcatProcess.kill()
|
enum_proc.stderr.close()
|
||||||
enum_proc.kill()
|
|
||||||
return
|
return
|
||||||
|
if enum_proc.stdout:
|
||||||
|
enum_proc.stdout.close()
|
||||||
if enum_proc.returncode != 0:
|
if enum_proc.returncode != 0:
|
||||||
stderr_output = (
|
stderr_output = (
|
||||||
enum_proc.stderr.read().decode("utf-8", errors="replace").strip()
|
enum_proc.stderr.read().decode("utf-8", errors="replace").strip()
|
||||||
@@ -2756,12 +2790,7 @@ def hcatGoodMeasure(hcatHashType, hcatHashFile):
|
|||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Good Measure", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
hcatExtraCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
hcatExtraCount = lineCount(hcatHashFile + ".out") - hcatHashCracked
|
||||||
|
|
||||||
@@ -2789,11 +2818,12 @@ def hcatLMtoNT():
|
|||||||
]
|
]
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="LM to NT (LM phase)",
|
||||||
except KeyboardInterrupt:
|
hash_file=f"{hcatHashFile}.lm",
|
||||||
hcatProcess.kill()
|
out_path=f"{hcatHashFile}.lm.cracked",
|
||||||
|
)
|
||||||
|
|
||||||
_write_delimited_field(f"{hcatHashFile}.lm.cracked", f"{hcatHashFile}.working", 2)
|
_write_delimited_field(f"{hcatHashFile}.lm.cracked", f"{hcatHashFile}.working", 2)
|
||||||
converted = convert_hex("{hash_file}.working".format(hash_file=hcatHashFile))
|
converted = convert_hex("{hash_file}.working".format(hash_file=hcatHashFile))
|
||||||
@@ -2840,12 +2870,12 @@ def hcatLMtoNT():
|
|||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(
|
||||||
try:
|
cmd,
|
||||||
hcatProcess.wait()
|
attack_name="LM to NT (NT phase)",
|
||||||
except KeyboardInterrupt:
|
hash_file=f"{hcatHashFile}.nt",
|
||||||
print("Killing PID {0}...".format(str(hcatProcess.pid)))
|
out_path=f"{hcatHashFile}.nt.out",
|
||||||
hcatProcess.kill()
|
)
|
||||||
|
|
||||||
# toggle-lm-ntlm.rule by Didier Stevens https://blog.didierstevens.com/2016/07/16/tool-to-generate-hashcat-toggle-rules/
|
# toggle-lm-ntlm.rule by Didier Stevens https://blog.didierstevens.com/2016/07/16/tool-to-generate-hashcat-toggle-rules/
|
||||||
|
|
||||||
@@ -2882,11 +2912,7 @@ def hcatRecycle(hcatHashType, hcatHashFile, hcatNewPasswords):
|
|||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
cmd = _add_debug_mode_for_rules(cmd)
|
cmd = _add_debug_mode_for_rules(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Recycle", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
hcatProcess.kill()
|
|
||||||
|
|
||||||
|
|
||||||
def hcatGenerateRules(hcatHashType, hcatHashFile, rule_count, wordlist):
|
def hcatGenerateRules(hcatHashType, hcatHashFile, rule_count, wordlist):
|
||||||
@@ -2922,12 +2948,7 @@ def hcatGenerateRules(hcatHashType, hcatHashFile, rule_count, wordlist):
|
|||||||
]
|
]
|
||||||
cmd.extend(shlex.split(hcatTuning))
|
cmd.extend(shlex.split(hcatTuning))
|
||||||
_append_potfile_arg(cmd)
|
_append_potfile_arg(cmd)
|
||||||
hcatProcess = subprocess.Popen(cmd)
|
_run_hcat_cmd(cmd, attack_name="Random Rules", hash_file=hcatHashFile)
|
||||||
try:
|
|
||||||
hcatProcess.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(f"Killing PID {hcatProcess.pid}...")
|
|
||||||
hcatProcess.kill()
|
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(rules_path):
|
if os.path.exists(rules_path):
|
||||||
os.unlink(rules_path)
|
os.unlink(rules_path)
|
||||||
@@ -4067,6 +4088,26 @@ def quit_hc():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_notifications():
|
||||||
|
"""Global on/off toggle for Pushover notifications.
|
||||||
|
|
||||||
|
Flips ``notify_enabled`` in the active settings and persists to
|
||||||
|
``config.json``. Prints the new state so the user has immediate
|
||||||
|
confirmation even though the menu label will also refresh on the
|
||||||
|
next render.
|
||||||
|
"""
|
||||||
|
new_state = _notify.toggle_enabled()
|
||||||
|
label = "ON" if new_state else "OFF"
|
||||||
|
print(f"\nPushover notifications are now {label}.")
|
||||||
|
if new_state:
|
||||||
|
settings = _notify.get_settings()
|
||||||
|
if not settings.pushover_token or not settings.pushover_user:
|
||||||
|
print(
|
||||||
|
"[!] notify_pushover_token / notify_pushover_user are empty in "
|
||||||
|
"config.json — notifications will silently no-op until set."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_main_menu_items():
|
def get_main_menu_items():
|
||||||
"""Return ordered (key, label) pairs for the main menu."""
|
"""Return ordered (key, label) pairs for the main menu."""
|
||||||
items = [
|
items = [
|
||||||
@@ -4091,6 +4132,10 @@ def get_main_menu_items():
|
|||||||
("22", "Combipow Passphrase Attack"),
|
("22", "Combipow Passphrase Attack"),
|
||||||
("80", "Wordlist Tools"),
|
("80", "Wordlist Tools"),
|
||||||
("81", "Rule File Tools"),
|
("81", "Rule File Tools"),
|
||||||
|
(
|
||||||
|
"83",
|
||||||
|
f"Toggle Pushover Notifications [{'ON' if _notify.get_settings().enabled else 'OFF'}]",
|
||||||
|
),
|
||||||
("90", "Download rules from Hashmob.net"),
|
("90", "Download rules from Hashmob.net"),
|
||||||
("91", "Analyze Hashcat Rules"),
|
("91", "Analyze Hashcat Rules"),
|
||||||
("92", "Download wordlists from Hashmob.net"),
|
("92", "Download wordlists from Hashmob.net"),
|
||||||
@@ -4134,6 +4179,7 @@ def get_main_menu_options():
|
|||||||
"22": combipow_crack,
|
"22": combipow_crack,
|
||||||
"80": wordlist_tools_submenu,
|
"80": wordlist_tools_submenu,
|
||||||
"81": rule_tools_submenu,
|
"81": rule_tools_submenu,
|
||||||
|
"83": toggle_notifications,
|
||||||
"90": lambda: download_hashmob_rules(rules_dir=rulesDirectory),
|
"90": lambda: download_hashmob_rules(rules_dir=rulesDirectory),
|
||||||
"91": analyze_rules,
|
"91": analyze_rules,
|
||||||
"92": download_hashmob_wordlists,
|
"92": download_hashmob_wordlists,
|
||||||
|
|||||||
218
tests/test_run_hcat_cmd.py
Normal file
218
tests/test_run_hcat_cmd.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
"""Tests for the ``_run_hcat_cmd`` subprocess/notify wrapper in main.py."""
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def main_module(hc_module):
|
||||||
|
return hc_module._main
|
||||||
|
|
||||||
|
|
||||||
|
def _make_mock_proc(wait_side_effect=None):
|
||||||
|
proc = 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
|
||||||
|
|
||||||
|
|
||||||
|
class TestRunHcatCmd:
|
||||||
|
def test_normal_flow_waits_and_notifies(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc) as mock_popen,
|
||||||
|
patch.object(main_module, "lineCount", return_value=42),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=True)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat", "-m", "1000"], attack_name="Brute Force", hash_file=hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_popen.assert_called_once()
|
||||||
|
proc.wait.assert_called_once()
|
||||||
|
proc.kill.assert_not_called()
|
||||||
|
mock_notify.notify_job_done.assert_called_once_with(
|
||||||
|
"Brute Force", 42, hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_keyboard_interrupt_kills_process(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch.object(main_module, "lineCount", return_value=0),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=False)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"], attack_name="Brute Force", hash_file=hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
proc.kill.assert_called_once()
|
||||||
|
|
||||||
|
def test_no_notify_when_attack_name_empty(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
main_module._run_hcat_cmd(["hashcat"], attack_name="", hash_file=hash_file)
|
||||||
|
|
||||||
|
mock_notify.notify_job_done.assert_not_called()
|
||||||
|
mock_notify.start_tailer.assert_not_called()
|
||||||
|
|
||||||
|
def test_suppressed_skips_notifications(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = True
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=True)
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"], attack_name="Brute Force", hash_file=hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_notify.start_tailer.assert_not_called()
|
||||||
|
mock_notify.notify_job_done.assert_not_called()
|
||||||
|
|
||||||
|
def test_stdin_is_forwarded_to_popen(self, main_module, tmp_path):
|
||||||
|
stdin_stub = object()
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc) as mock_popen,
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=False)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(["hashcat"], stdin=stdin_stub)
|
||||||
|
|
||||||
|
_, kwargs = mock_popen.call_args
|
||||||
|
assert kwargs.get("stdin") is stdin_stub
|
||||||
|
|
||||||
|
def test_companion_procs_killed_on_interrupt(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||||
|
companion = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch.object(main_module, "lineCount", return_value=0),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=False)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"],
|
||||||
|
attack_name="Combinator3",
|
||||||
|
hash_file=hash_file,
|
||||||
|
companion_procs=[companion],
|
||||||
|
)
|
||||||
|
|
||||||
|
proc.kill.assert_called_once()
|
||||||
|
companion.kill.assert_called_once()
|
||||||
|
|
||||||
|
def test_companion_procs_waited_on_normal_exit(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
companion = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=False)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"],
|
||||||
|
attack_name="Combinator3",
|
||||||
|
hash_file=hash_file,
|
||||||
|
companion_procs=[companion],
|
||||||
|
)
|
||||||
|
|
||||||
|
companion.wait.assert_called_once()
|
||||||
|
companion.kill.assert_not_called()
|
||||||
|
|
||||||
|
def test_reraise_interrupt_propagates(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch.object(main_module, "lineCount", return_value=0),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=False)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
with pytest.raises(KeyboardInterrupt):
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"],
|
||||||
|
attack_name="YOLO",
|
||||||
|
hash_file=hash_file,
|
||||||
|
reraise_interrupt=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_out_path_override(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
alt_out = str(tmp_path / "hashes.lm.cracked")
|
||||||
|
proc = _make_mock_proc()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch.object(main_module, "lineCount", return_value=9) as mock_lc,
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=True)
|
||||||
|
mock_notify.start_tailer.return_value = None
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"],
|
||||||
|
attack_name="LM Phase",
|
||||||
|
hash_file=hash_file,
|
||||||
|
out_path=alt_out,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_lc.assert_called_with(alt_out)
|
||||||
|
mock_notify.notify_job_done.assert_called_once_with(
|
||||||
|
"LM Phase", 9, hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tailer_is_stopped_in_finally(self, main_module, tmp_path):
|
||||||
|
hash_file = str(tmp_path / "hashes.txt")
|
||||||
|
proc = _make_mock_proc(wait_side_effect=KeyboardInterrupt())
|
||||||
|
tailer = MagicMock()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hate_crack.main.subprocess.Popen", return_value=proc),
|
||||||
|
patch.object(main_module, "lineCount", return_value=0),
|
||||||
|
patch("hate_crack.main._notify") as mock_notify,
|
||||||
|
):
|
||||||
|
mock_notify.is_suppressed.return_value = False
|
||||||
|
mock_notify.get_settings.return_value = MagicMock(enabled=True)
|
||||||
|
mock_notify.start_tailer.return_value = tailer
|
||||||
|
main_module._run_hcat_cmd(
|
||||||
|
["hashcat"], attack_name="Brute Force", hash_file=hash_file
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_notify.stop_tailer.assert_called_once_with(tailer)
|
||||||
@@ -32,6 +32,7 @@ MENU_OPTION_TEST_CASES = [
|
|||||||
("22", CLI_MODULE._attacks, "combipow_crack", "combipow"),
|
("22", CLI_MODULE._attacks, "combipow_crack", "combipow"),
|
||||||
("80", CLI_MODULE._attacks, "wordlist_tools_submenu", "wordlist-tools"),
|
("80", CLI_MODULE._attacks, "wordlist_tools_submenu", "wordlist-tools"),
|
||||||
("81", CLI_MODULE._attacks, "rule_tools_submenu", "rule-tools"),
|
("81", CLI_MODULE._attacks, "rule_tools_submenu", "rule-tools"),
|
||||||
|
("83", CLI_MODULE, "toggle_notifications", "toggle-notifications"),
|
||||||
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
|
("90", CLI_MODULE, "download_hashmob_rules", "hashmob-rules"),
|
||||||
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
|
("91", CLI_MODULE, "weakpass_wordlist_menu", "weakpass-menu"),
|
||||||
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
|
("92", CLI_MODULE, "download_hashmob_wordlists", "hashmob-wordlists"),
|
||||||
|
|||||||
Reference in New Issue
Block a user