Commit Graph
100 Commits
Author SHA1 Message Date
Justin BollingerandClaude Sonnet 4.6 4fa43a79f4 style: ruff format pass for Notifications submenu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:32:44 -04:00
Justin BollingerandClaude Sonnet 4.6 61fb8309d2 docs: document Notifications submenu (option 82) in README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:28:08 -04:00
Justin BollingerandClaude Sonnet 4.6 e33abc89f4 feat(notify): move options 83/84 under new Notifications submenu (82)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:07:17 -04:00
Justin BollingerandClaude Sonnet 4.6 43773fa054 feat(notify): add Notifications submenu dispatcher
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:02:37 -04:00
Justin BollingerandClaude Sonnet 4.6 6b3d4e7e77 feat(notify): add per-crack UI toggle with global-OFF guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:57:21 -04:00
Justin BollingerandClaude Sonnet 4.6 e644e54b4b feat(notify): add toggle_per_crack_enabled runtime toggle
Promotes notify_per_crack_enabled from config-file-only to a runtime
toggle with the same style (global-decl, default-init, OSError-via-logger)
as the existing toggle_enabled, with full TDD coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:52:00 -04:00
Justin BollingerandClaude Sonnet 4.6 8a148d5261 feat(notify): persist notify_per_crack_enabled atomically
Add save_per_crack_enabled() as a data-layer sibling to save_enabled(),
using the same _atomic_rewrite primitive so mid-write crashes cannot
corrupt config.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 18:48:24 -04:00
Justin Bollinger eae45517b3 Merge branch 'feat/test-pushover-menu' into feat/pushover-notifications
Adds menu option 84 (Send Test Pushover Notification) so users can verify
their Pushover credentials without running an attack. Ignores the global
notify_enabled toggle by design (prints a note when OFF).
2026-04-22 18:29:05 -04:00
Justin Bollinger 589259ebaf test(notify): cover option 84 in menu wiring parametrize 2026-04-22 18:15:09 -04:00
Justin Bollinger 6d0e9a154f feat(notify): wire option 84 into hate_crack.py proxy menu 2026-04-22 18:13:31 -04:00
Justin Bollinger 36115a75f2 feat(notify): wire option 84 into main.py menu 2026-04-22 18:11:35 -04:00
Justin Bollinger 16bdfe7d52 fix(tests): patch hate_crack.main._notify directly
tests/test_random_rules_attack.py purges and re-imports hate_crack.*
modules, which leaves main._notify pointing at a different notify
object than a top-level patch("hate_crack.notify._send_pushover")
would touch. Under the full suite that caused the test's mock to
miss and the production call to hit the real Pushover API.

Switch to patch.object(hc_main, "_notify") -- same pattern as
tests/test_run_hcat_cmd.py but anchored to the exact module object
already bound as hc_main, so it is immune to sys.modules churn
regardless of import order. Drop the now-redundant _install_settings
helper and _reset_notify_state fixture.
2026-04-22 18:07:52 -04:00
Justin Bollinger 9e0f040270 feat(notify): add test_pushover_notification helper
Canned send path so a user can verify Pushover credentials without
running an attack. Ignores the global notify_enabled toggle — the test's
purpose is to confirm the pipe is live, not that attack notifications
are enabled. Prints a note when the global toggle is OFF so the user is
not confused later.
2026-04-22 17:56:35 -04:00
Justin BollingerandClaude Opus 4.7 97bcc0ac78 test: isolate notify state between tests
hate_crack.main calls notify.init() at import time with whatever
config.json is resolved from the developer's environment (often
~/.hate_crack/config.json). If that file has notify_enabled: true, the
per-attack prompt in attacks.py fires input() during tests and trips
pytest's capture fd, failing unrelated tests.

Add an autouse conftest fixture that clears notify module state before
and after every test so the suite is hermetic regardless of local
config. Notify-specific tests already use their own
clear_state_for_tests() fixture; this change covers the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:27:15 -04:00
Justin BollingerandClaude Opus 4.7 b5af2498f5 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>
2026-04-22 15:16:12 -04:00
Justin BollingerandClaude Sonnet 4.6 aae5ce8e90 feat(notify): add notification package with Pushover backend
Introduce hate_crack.notify package with a small functional public API
and a CrackTailer thread for polling hashcat output files. Package
layout keeps the HTTP call (_send_pushover) isolated so future backends
(Slack, generic webhooks) can be added as a sibling function rather
than a framework rewrite.

Core pieces:
- settings.py: NotifySettings dataclass plus atomic config persistence
  (save_enabled, add_to_allowlist) via read-modify-write + os.replace.
- pushover.py: single _send_pushover() that never raises; network
  errors, missing requests, and missing creds all funnel to False.
- _suppress.py: thread-local suppression context manager so
  orchestrator attacks can chain primitives without flooding
  notifications.
- tailer.py: CrackTailer(threading.Thread) that seeks to EOF on start,
  polls at a user-configurable interval, and collapses per-tick bursts
  into a single aggregate notification when they exceed the cap.
- __init__.py: public API (init, prompt_notify_for_attack,
  notify_job_done, notify_crack, start_tailer, stop_tailer,
  toggle_enabled, suppressed_notifications). Privacy guarantee:
  notification payloads contain only attack name, counts, and hash
  path, never plaintexts.

72 new tests cover dataclass defaults, atomic config writes, idempotent
allowlist updates, HTTP payload privacy, suppression nesting and
thread-locality, tailer EOF seek, burst cap, truncation recovery, and
the per-attack prompt's [y/n/always] flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:41:38 -04:00
Justin BollingerandClaude Opus 4.6 28d442a63e feat: add auto-tagging workflow for semver bumps
Automatically creates a new git tag on push to main based on
conventional commit prefixes: feat: bumps minor (2.5.x → 2.6.0),
fix:/perf: bumps patch (2.5.1 → 2.5.2). The new tag triggers the
existing release workflow to create a GitHub release.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:32:40 -04:00
Justin BollingerandClaude Opus 4.6 622381f1b6 fix: use no-local-version scheme for clean semver display
Add local_scheme = "no-local-version" to setuptools-scm config so
versions never include the +g<hash> suffix. Simplify the regex in
__init__.py to only strip .post/.dev suffixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:29:55 -04:00
Justin BollingerandClaude Opus 4.6 1b5f6bee2e fix: correct convergence logic in hcatFingerprint loop
The old loop reassigned crackedBefore at the top of each iteration and
initialized crackedAfter to 0, which could cause the loop to enter
spuriously or skip entirely. Switch to while True / break to properly
detect when an iteration produces no new cracks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:10:01 -04:00
Justin BollingerandClaude Opus 4.6 2b169ae362 perf: combine d3ad0ne + T0XlC rules into single hashcat invocation
Merge both rule files into a temporary combined file so hashcat only
starts once per wordlist instead of twice, saving GPU initialization
overhead on each dictionary attack iteration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:55 -04:00
Justin BollingerandClaude Opus 4.6 c75572c8fc perf: use binary chunk counting in lineCount for large files
Read 1 MiB binary chunks and count newline bytes instead of iterating
text lines, which is significantly faster on large output files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:47 -04:00
Justin BollingerandClaude Opus 4.6 fa940f67c5 perf: replace hard 15s sleep with proper rate limiter for Hashmob downloads
Introduces a shared _RateLimiter class (1 req/2s) instead of per-function
locks with a 15-second sleep. Also tunes backoff from 256s to 30s with
30s penalty increments for faster retry on rate-limited responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:09:39 -04:00
Justin BollingerandClaude Opus 4.6 c1a2767be3 test: add e2e test for output file path correctness
Verifies that a pwdump file at /tmp/test_hashes.ntds produces output
at /tmp/test_hashes.ntds.out using real hashcat. Confirms no files
leak into the project directory. Gated behind HATE_CRACK_RUN_E2E=1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:07:33 -04:00
Justin BollingerandClaude Opus 4.6 8e83bdeea3 fix: only report combined output file when it exists
cleanup() previously always printed 'Cracked passwords combined in
X.out' even when combine_ntlm_output() returned early (no cracked
hashes) or the hash type wasn't NTLM pwdump. Now checks file
existence first and falls back to pointing at the raw .out file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:05:08 -04:00
Justin Bollinger cce1b9d466 Merge branch 'remove-symlink-logic' 2026-04-08 13:02:49 -04:00
Justin BollingerandClaude Opus 4.6 9b97cd2a48 fix: remove symlink/copy from _ensure_hashfile_in_cwd
Output files now land next to the original hashfile. resolve_path()
already resolves relative paths against HATE_CRACK_ORIG_CWD, so
relocating the hashfile into CWD was unnecessary and created
confusing symlinks in the working directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:02:26 -04:00
Justin BollingerandClaude Opus 4.6 fa07d37a84 fix: write output files to user's CWD, not install directory
The bash shim uses `uv run --directory <install_dir>` which changes the
process CWD to the install directory. _ensure_hashfile_in_cwd() and the
Hashview download path used os.getcwd() to determine the target directory
for output files (.out, .nt, etc.), causing them to land in the install
directory instead of where the user ran the command.

Add orig_cwd() helper that reads HATE_CRACK_ORIG_CWD (set by the shim)
and use it in _ensure_hashfile_in_cwd(), the Hashview download path, and
the potfile fallback path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:43:51 -04:00
Justin Bollinger d0bfb015a6 docs: update README for v2.5.0 changes 2026-03-20 11:15:29 -04:00
Justin Bollinger 55e12211bb feat: restore hcatOptimizedWordlists config and use as quick crack default
Re-add hcatOptimizedWordlists config key (previously removed) with a
default of ./optimized_wordlists. Falls back to hcatWordlists if the
configured directory does not exist.

Update quick_crack to list files from and default to hcatOptimizedWordlists
instead of hcatWordlists when prompting for a wordlist or directory.
2026-03-20 11:06:03 -04:00
Justin Bollinger 1de51e9094 feat: add tab autocomplete to wordlist menu file path prompts
Replace input() with ctx.select_file_with_autocomplete() for all file
and directory path prompts in the 7 wordlist tools submenu functions.
Non-path prompts (lengths, masks, offsets, mode selection) remain as
plain input() calls.

Update tests to set ctx.select_file_with_autocomplete.side_effect for
file path values and leave builtins.input patches only for non-path
inputs.
2026-03-20 10:30:08 -04:00
Justin Bollinger 1b812cda32 fix: move dev tools to dependency-groups so uv sync installs them always
ruff, ty, pytest, pytest-cov were in [project.optional-dependencies]
requiring --extra dev to install. Moved to [dependency-groups] which
uv sync includes by default, fixing the pre-push hook finding no ruff.
2026-03-20 10:16:53 -04:00
Justin Bollinger e627807794 fix: read version from package metadata instead of _version.py
Replaces the _version.py import with importlib.metadata.version() so
the version is always read from the installed package, which setuptools-scm
writes correctly during uv sync. Removes the version_file config and
the stale-file workarounds from make install/clean.
2026-03-20 10:16:04 -04:00
Justin Bollinger e7f15dc0b5 ci: auto-publish GitHub release on version tag push 2026-03-20 10:13:22 -04:00
Justin Bollinger a9cad18949 fix: force _version.py regeneration on install and clean
_version.py is gitignored but persists on disk with a stale version.
Delete it before uv sync so setuptools-scm regenerates it from the
current git tag. Also remove it in make clean for consistency.
2026-03-20 10:08:56 -04:00
Justin Bollinger b4b284cf50 fix: remove hashcat preflight check from Makefile
Hashcat path is configured via config.json at runtime. The build-time
check caused false failures when running as root (sudo) or before
config.json exists, and added no value since hashcat-utils builds
independently of the hashcat installation.
2026-03-20 10:02:54 -04:00
Justin Bollinger d44527cd6e fix: search candidate config dirs in Makefile preflight hashcat check
Mirrors the Python runtime's _candidate_roots() logic so hcatPath is
honored even when config.json lives in ~/.hate_crack or another
non-cwd location.
2026-03-20 09:55:28 -04:00
Justin Bollinger d1192f118f fix: honor config.json hcatPath/hcatBin in Makefile preflight check
The submodules-pre hashcat check now reads hcatPath and hcatBin from
config.json (mirroring main.py resolution logic) before falling back to
PATH lookup and the vendored hate_crack/hashcat/hashcat binary.
2026-03-20 09:37:34 -04:00
Justin Bollinger 2565641b29 fix: resolve hashcat binary from hcatPath when not in PATH
- Apply os.path.expanduser() to hcatPath so tilde (~) paths work,
  consistent with every other config path (hcatWordlists, rulesDirectory,
  hcatPotfilePath, hcatDebugLogPath)
- Improve error message when binary not found to show what hcatPath was
  checked, making it easier to diagnose misconfiguration
- Update optimizedKernelAttacks in config.json.example to include all 21
  attacks from DEFAULT_OPTIMIZED_ATTACKS (13 were added in v2.4.0 but
  config.json.example was not updated, causing the runtime override to
  exclude them)
2026-03-20 09:27:03 -04:00
Justin Bollinger dab7bb41ee chore: add pytest-timeout dev dependency 2026-03-19 23:43:49 -04:00
Justin Bollinger f80dfcee4e fix: add skip guards for missing runtime artifacts in e2e/integration tests 2026-03-19 23:42:43 -04:00
Justin Bollinger a3ad5579b7 fix: preserve hate_crack.api in module reload to prevent test isolation hang 2026-03-19 23:40:24 -04:00
Justin Bollinger 3b036f9696 test: add optimized kernel unit and integration tests (#82) 2026-03-19 21:37:58 -04:00
Justin Bollinger 7880bffce1 feat: add dynamic optimized kernel (-O) to 11 additional attack types (#82) 2026-03-19 21:11:59 -04:00
Justin Bollinger 4aef38ec51 test: verify .7z wordlist filtering and parallel rule downloads (#80 #81) 2026-03-19 20:23:28 -04:00
Justin Bollinger e409511c88 fix: correct download_left_hashes potfile merge bugs
Merge fix/download-left-hashes-potfile-bugs
2026-03-19 19:14:00 -04:00
Justin Bollinger e006833812 fix: correct download_left_hashes potfile merge bugs
- Delete block that wrongly appended found hashes back into the left
  (unsolved) file - found hashes belong only in the potfile
- Fix get_hcat_potfile_path() to return "" when config key is
  explicitly set to "", respecting user intent to disable potfile override
- Fix get_hcat_potfile_path() to resolve relative paths relative to the
  config file directory, matching main.py's hate_path resolution
- Add potfile_path parameter to download_left_hashes() and
  download_hashes_from_hashview() so CLI --potfile-path and
  --no-potfile-path overrides propagate to the API merge step
- Update main.py call sites to pass hcatPotfilePath through
- Add tests covering all four bug fixes
2026-03-19 19:13:41 -04:00
Justin Bollinger 8770a700df fix: locate uv binary before upgrade to handle non-standard PATH
When running as root or via sudo, /root/.local/bin may not be in PATH.
Use shutil.which with fallback to ~/.local/bin/uv, and fail clearly
if uv can't be found.
2026-03-19 18:05:00 -04:00
Justin Bollinger 88064a5a4f fix: use git fetch --tags and uv sync --reinstall to fix version after upgrade
uv's PEP 517 build isolation copies source to a temp dir without .git,
so setuptools-scm can't determine the version and falls back to the
existing _version.py. Forcing --reinstall-package hate_crack makes uv
rebuild from source in the actual repo dir (editable mode) where git
is accessible, so the correct version is generated.
2026-03-19 18:02:54 -04:00
Justin Bollinger 0f692efe55 fix: drop make clean from upgrade command to fix version detection
make clean deinits submodules and wipes the uv cache, causing
setuptools-scm to compute a wrong version during the subsequent
uv sync. The clean step is unnecessary for an upgrade - just
git pull && make install is sufficient.
2026-03-19 17:57:09 -04:00
Justin Bollinger 8483a17242 feat: add --update flag to trigger in-place upgrade on demand
Refactors the upgrade logic into _run_upgrade() so it can be called
both from the startup version check prompt and directly via --update,
bypassing the version comparison entirely.
2026-03-19 17:49:23 -04:00
Justin Bollinger 9fba3f7bf6 feat: add upgrade prompt to version update check
When a newer release is found, prompt the user to upgrade in-place.
Resolves the git repo root via `git rev-parse --show-toplevel` before
running `git pull && make clean && make && make install`, so the upgrade
works correctly whether hate_crack is run from source or installed into
site-packages. Ctrl-C and EOF at the prompt continue normally.
2026-03-19 17:45:36 -04:00
Justin Bollinger 7cd98ea714 Merge remote-tracking branch 'origin/main' into bug/analyze-rules-tab-complete 2026-03-19 16:22:23 -04:00
Justin Bollinger 95d0eb229a feat: add rule file tools submenu (key 81) with cleanup and optimize
Resolves merge conflict with origin/main (keys 19-22 and 80 from main
kept, rule tools submenu added at key 81).
2026-03-19 16:03:40 -04:00
Justin Bollinger 4b5091ef89 feat: add wordlist tools submenu (key 80) with 7 hashcat-utils filters
Resolves merge conflict with origin/main (keys 19-22 from main kept,
wordlist tools submenu added at key 80).

Fixes test isolation bug in test_random_rules_attack.py where
load_cli_module() nuked hate_crack.attacks from sys.modules, breaking
__globals__ references in wordlist_tools_submenu for downstream tests.
Also corrects wrong menu key assertion (21 not 20) for generate_rules_crack.
2026-03-19 16:00:09 -04:00
Justin Bollinger a31497649f feat: add combipow passphrase attack at menu key 22
Resolves merge conflict with origin/main (keys 19-21 used by ngram,
permute, random-rules). Combipow takes key 22.

- gzip support: decompress .gz wordlists to a temp file before passing
  path to combipow.bin (which requires a filename argument)
- UI line-counting uses gzip.open for .gz files
- Update tests to reference key 22 instead of 21
2026-03-19 15:50:25 -04:00
Justin Bollinger 6032787fa0 chore: ignore large local wordlist files 2026-03-19 15:42:18 -04:00
Justin Bollinger 63e7286fcb merge: resolve conflicts with main - random rules moves to key 21 2026-03-19 15:42:11 -04:00
Justin Bollinger 92a835c6d8 merge: resolve conflicts with main - permute moves to key 20, ngram stays at 19 2026-03-19 15:33:48 -04:00
Justin Bollinger ffce245f78 fix: remove unused tempfile import 2026-03-19 15:30:27 -04:00
Justin Bollinger 142ee4b245 fix: transparently decompress gzip wordlists for external tool attacks
Add _open_wordlist() helper that returns gzip.open() or open() based on
the .gz extension. Apply it to the three functions that open wordlist
files themselves before piping to an external binary:

- hcatPrince: prince base list fed to pp64.bin
- hcatPermute: wordlist fed to permute.bin
- hcatMarkovTrain: source file fed to hcstat2gen.bin

Attacks that pass wordlist paths directly to hashcat are unaffected -
hashcat already handles .gz natively.
2026-03-19 15:29:59 -04:00
Justin Bollinger 78b51f9b10 revert: update hashcat-utils submodule to reverted ngramX 2026-03-19 15:05:37 -04:00
Justin Bollinger 724e089d63 Revert "fix: ngramX generates n-grams per line, not across entire corpus"
This reverts commit e354f3e57e.
2026-03-19 15:05:29 -04:00
Justin Bollinger e354f3e57e fix: ngramX generates n-grams per line, not across entire corpus
Previously ngramX.bin accumulated all words from the whole file into a
flat array, producing candidates that crossed line boundaries. With a
corpus of "roses are red\nviolets are blue", it generated "red violets"
and "are red violets" - mixing lines that represent separate phrases.

Fixes ngramX.c to process each line independently so n-grams stay
within a single line, matching the expected behavior for song lyrics,
book passages, and similar per-line corpora.
2026-03-19 15:02:42 -04:00
Justin Bollinger 5fe6e21405 feat: add ngramX attack and gzip auto-detection for external binaries
- Add _is_gzipped() magic-byte detector and _wordlist_path() context
  manager that transparently decompresses gzip files to a temp path
- Apply gzip handling to hcatCombinator3 and hcatCombinatorX via
  contextlib.ExitStack so compressed wordlists work without manual prep
- Add hcatNgramX() wrapper using ngramX.bin <corpus> <group_size> piped
  to hashcat, with gzip auto-detection on the corpus file
- Add ngram_attack() handler in attacks.py with tab-autocomplete corpus
  selection and configurable group size (default 3)
- Register attack as menu option 19 in both main.py and hate_crack.py
- Fix wordlist_optimizer.py: .app extension on macOS was wrong, use .bin
- Add tests/test_ngram_gzip.py covering ngram_attack handler, _is_gzipped,
  and _wordlist_path context manager (temp file cleanup, plain passthrough)
2026-03-19 14:35:29 -04:00
Justin Bollinger 7a7401266b feat: unify combinator attacks into single 2-8 wordlist handler
- Merge combinator, combinator3, and combinatorX into one unified
  combinator_crack function that routes by wordlist count:
  2 (no sep) -> hcatCombination, 3 (no sep) -> hcatCombinator3,
  4+ or any separator -> hcatCombinatorX
- Replace comma-separated wordlist input with one-at-a-time
  tab-autocomplete prompts (blank line to finish)
- Add _prompt_wordlist_paths helper using existing readline infrastructure
- Add hcatCombinator3Wordlist and hcatCombinatorXWordlist config vars
  with rockyou.txt defaults
- Print full hashcat command to stdout in --debug mode by calling
  _debug_cmd at the end of _append_potfile_arg (covers all 27 invocations)
- Collapse combinator submenu from 6 options to 4; keep combinator3_crack,
  combinatorX_crack, and combinator_3plus_crack as delegation shims
- Update tests to cover unified routing and new prompt interface
2026-03-19 14:18:25 -04:00
Justin Bollinger a9a533e41e refactor: improve TestHcatCombipow to use patch.object context managers
Replace direct module attribute mutation with patch.object for cleaner
test isolation. Use hate_crack.main.subprocess.Popen for targeted mocking
instead of the global subprocess.Popen.
2026-03-19 12:22:29 -04:00
Justin Bollinger 9345e69bf4 fix: ensure hashcat.induct exists before loopback test
Hashcat renames ~/.hashcat/sessions/hashcat.induct after each session.
When the directory is absent the loopback test fails with "No such file
or directory". Recreate the directory in the test setup so it always
exists when the loopback command runs.
2026-03-19 12:20:37 -04:00
Justin Bollinger 399cd06fdb docs: add Wordlist Tools submenu to README 2026-03-19 12:17:48 -04:00
Justin Bollinger 2d77d8e582 feat: add combipow passphrase attack (#88)
Add menu option 21 (Combipow Passphrase Attack) using combipow.bin from
hashcat-utils. Generates all unique non-empty subset combinations from a
short wordlist and pipes them into hashcat stdin.

- hcatCombipow(hash_type, hash_file, wordlist, use_space_sep) in main.py
- combipow_crack handler in attacks.py validates file, enforces 63-line
  limit, prompts for space separator, then delegates to hcatCombipow
- Wired into get_main_menu_items() and get_main_menu_options() in both
  main.py and hate_crack.py
- 12 tests covering menu presence, handler delegation, line-count
  enforcement, file validation, and subprocess flag construction
2026-03-19 12:17:26 -04:00
Justin Bollinger 69e6fb9cb3 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
2026-03-19 12:16:04 -04:00
Justin Bollinger 7664afda55 feat: add wordlist tools submenu (len, req-include, req-exclude, cutb, rli, rli2, splitlen, gate) (#90, #91, #92, #94)
Add Wordlist Tools submenu (key 80) with 7 preprocessing utilities backed by hashcat-utils binaries:

- Filter by length (len.bin) - #90
- Require/exclude character classes (req-include.bin, req-exclude.bin) - #90
- Extract substring (cutb.bin) - #90
- Remove matching lines (rli.bin, rli2.bin) - #91
- Split by length (splitlen.bin) - #92
- Shard wordlist (gate.bin) - #94

Three-layer pattern:
- main.py: low-level wrappers (wordlist_filter_len, wordlist_filter_req_include,
  wordlist_filter_req_exclude, wordlist_cutb, wordlist_splitlen, wordlist_subtract,
  wordlist_subtract_single, wordlist_gate) return bool for testability
- attacks.py: UI handlers with input validation and the wordlist_tools_submenu dispatcher
- hate_crack.py + main.py: menu item "80" wired in both get_main_menu_items and
  get_main_menu_options

Move interactive_menu import to attacks.py module level (was local import in
combinator_submenu) to support patching in tests.
2026-03-19 12:15:43 -04:00
Justin Bollinger e41134eb1a feat: add permutation attack using permute.bin (closes #86)
Adds Permutation Attack (menu option 19) that generates all character
permutations of each word in a targeted wordlist and pipes them to
hashcat via permute.bin from hashcat-utils.

- hcatPermute() in main.py: pipes permute.bin < wordlist | hashcat
- permute_crack() in attacks.py: prompts for single wordlist file with
  factorial-growth warning, tab-autocomplete support
- Menu option 19 wired in both main.py and hate_crack.py
- hcatPermuteCount tracking alongside other count globals
- Tests: test_permute_attack.py (handler behavior) and
  test_permute_wrapper.py (subprocess wiring)
- README: added entry in menu listing and attack descriptions
2026-03-19 12:14:05 -04:00
Justin Bollinger dfe9e28e70 feat: add random rules attack using generate-rules.bin (#87)
- Add hcatGenerateRules() in main.py: runs generate-rules.bin to
  produce N random rules, writes them to a temp file, runs hashcat
  with -r against a chosen wordlist, cleans up on exit
- Add generate_rules_crack() handler in attacks.py with count
  prompt (default 65536), wordlist picker with tab-completion,
  input validation, and abort on invalid input
- Add dispatcher generate_rules_crack() in main.py and key "20"
  in both main.py and hate_crack.py get_main_menu_options()
- Add ("20", "Random Rules Attack") to get_main_menu_items()
- Add tests: test_random_rules_attack.py (menu presence, handler
  wiring), test_random_rules_wrapper.py (subprocess behavior,
  cleanup, count passing, count tracking)
- Add key "20" to MENU_OPTION_TEST_CASES in test_ui_menu_options.py
- Update README: add option 20 to menu listing, add attack
  description, add version history entry
2026-03-19 12:13:38 -04:00
Justin Bollinger 9611565471 feat: add rule file management tools using cleanup-rules.bin and rules_optimize.bin (closes #93)
Adds Rule File Tools submenu (menu option 81) with three operations:
- Clean: removes invalid/duplicate rules via cleanup-rules.bin
- Optimize: consolidates redundant operations via rules_optimize.bin
- Clean and optimize: both in sequence with temp file handling

Wired through the standard three-layer pattern: main.py utility
functions + dispatcher, attacks.py handlers + submenu, root
hate_crack.py menu registration.
2026-03-19 12:12:12 -04:00
Justin Bollinger 53b99d4395 fix: use raw LZMA2 stream instead of XZ container for hcstat2 compression
hashcat's Lzma2Decode() expects a raw LZMA2 stream starting with 0xff chunk
headers, not an XZ container format (0xfd 37 7a...). FORMAT_RAW with
FILTER_LZMA2 produces the correct format.
2026-03-18 20:16:42 -04:00
Justin Bollinger afb4b279fc fix: compress hcstat2 output with LZMA2 format for hashcat compatibility
Hashcat requires hcstat2 files to be LZMA2-compressed. The hcstat2gen.bin utility outputs uncompressed binary data, so we need to compress the output after generation.

Root cause: Hashcat's source code (mpsp.c) uses hc_lzma2_decompress() to read hcstat2 files. The bundled hashcat.hcstat2 file uses LZMA2 compression (XZ format). hcstat2gen.bin outputs plain hcstat format which hashcat cannot decompress, causing "Could not uncompress data" error.

Fix: After hcstat2gen.bin completes, compress the output file using Python's lzma module with XZ format (LZMA2-based).

Also restore proper subprocess invocation: pass output path as argument to hcstat2gen.bin instead of using stdout redirection.

Tests: All 8 markov E2E tests pass, 479 total tests pass
2026-03-18 19:40:18 -04:00
Justin Bollinger c71d2a5321 fix: restore hcstat2gen.bin output path argument - markov training was creating empty file
The previous commit attempted to simplify by passing hcstat2_path as an argument and removing stdout redirection. However, hcstat2gen.bin writes directly to the file specified as its first argument and doesn't use stdout. The code was missing the proper invocation.

Root cause: Removed stdout=PIPE capture but forgot that the binary doesn't write to stdout - it writes directly to the file argument. This left the .hcstat2 file empty/uncreated.

Fix: Restore proper subprocess invocation - pass output path as argument, stdin from wordlist, stderr for error reporting. Remove stdout=PIPE since the binary doesn't use it.

Tests: All 8 markov E2E tests pass, 479 total tests pass
2026-03-18 19:30:23 -04:00
Justin Bollinger 374ac25233 fix: simplify markov training - hcstat2gen.bin supports gzipped input
- Remove unnecessary gzip decompression logic
- hcstat2gen.bin can read both plain text and gzipped files directly
- Simplified code: just pass source file and output path as arguments
- Removed gzip and tempfile imports (no longer needed)
- Add comprehensive end-to-end tests covering all markov attack flows
- Tests verify training with plain text and gzipped wordlists
- Tests verify all handler menu paths (use existing, generate new, cancel, training failure)
- Fixes 'cannot uncompress data' error by not trying to decompress
2026-03-18 19:24:19 -04:00
Justin Bollinger 07f7cad16f fix: pass hcstat2 output path as argument to hcstat2gen.bin
- hcstat2gen.bin expects: hcstat2gen.bin <outfile> < dictionary
- Previously tried to write to stdout which caused 'usage' error
- Now correctly passes output path as command argument
- Markov training now works for both plain text and gzipped wordlists
- Fixes 'Markov table generation failed' error
2026-03-18 19:19:31 -04:00
Justin Bollinger a13cec1792 fix: add detailed error reporting to markov training
- Check hcstat2gen.bin exists before running
- Verify source file is readable
- Capture stdout and stderr from hcstat2gen.bin
- Report hcstat2gen.bin errors with stderr output
- Verify output file created and non-empty
- Add 300 second timeout to prevent hanging
- Write proper output to .hcstat2 file from stdout
- Helps debug training failures with specific error messages
2026-03-18 19:17:54 -04:00
Justin Bollinger 120c251e92 fix: properly decompress gzipped files before markov training
- Add tempfile import for secure temporary file handling
- Detect gzipped files by magic bytes (0x1f 0x8b)
- Decompress gzipped files to temporary file first
- Pass uncompressed data directly to hcstat2gen.bin
- Properly clean up temporary files on success or error
- Fixes 'hcstat2: Could not uncompress data' error
2026-03-18 19:13:10 -04:00
Justin Bollinger 4ff0cff656 fix: handle gzipped training files in markov attack
- Add gzip import to main.py
- Detect gzipped files by magic bytes (0x1f 0x8b)
- Use gzip.open() for compressed files, regular open() for plain text
- Properly close file handles in all cases
- Allows markov training from both .txt and .txt.gz wordlists
2026-03-18 19:09:49 -04:00
Justin Bollinger 02ce2092ec docs: add ad-hoc mask and markov brute force attack documentation
- Update menu listing to show combinator submenu and new attacks (17, 18)
- Remove keys 10/11/12 from main menu (consolidated into submenu)
- Add detailed descriptions for Combinator Attacks submenu, Ad-hoc Mask Attack, and Markov Brute Force Attack
- Document Markov training source selection and table persistence
- Update version history with new feature details
2026-03-18 19:02:35 -04:00
Justin Bollinger 0d83bd03e2 test: fix menu option tests for new attack structure
- Update test cases to reflect combinator_submenu for key 6
- Remove test cases for keys 10/11/12 (moved to sub-menu)
- Add test cases for new keys 17 and 18
- Simplify adhoc_mask tests to avoid global state issues
2026-03-18 19:01:51 -04:00
Justin Bollinger 9a432f8b07 feat: add ad-hoc mask attack, markov brute force, and combinator sub-menu
- Add three hashcat wrapper functions: hcatAdHocMask, hcatMarkovTrain, hcatMarkovBruteForce
- Add corresponding attack handlers in attacks.py with OMEN-style training flow
- Consolidate 4 combinator attacks (keys 10/11/12) into interactive sub-menu (key 6)
- Add key 17 for ad-hoc mask attack and key 18 for markov brute force
- Update both main.py and hate_crack.py menu systems
- Add comprehensive test coverage for new handlers and wrappers
- Training source picker supports cracked passwords or any wordlist
2026-03-18 19:00:40 -04:00
Justin Bollinger 3bc85d9b8a feat: add rule support to OMEN attack and fix relative path resolution
Extract _select_rules() helper from quick_crack/loopback_attack and wire
it into omen_attack so OMEN can run with rule chains. Extend hcatOmen()
to accept and apply an hcatChains argument including debug mode injection.

Fix resolve_path() to honour HATE_CRACK_ORIG_CWD (set by the install shim)
so relative hash/wordlist paths resolve against the caller's working directory
instead of the repo root. Increase default omenMaxCandidates to 50M.
2026-03-18 18:17:41 -04:00
Justin Bollinger 9423b5a64d feat: restore wordlist_optimizer.py with modernizations
- Resolve binary paths relative to script location with platform-specific extensions
- Replace shell=True with proper subprocess.run argument lists (security fix)
- Convert %-formatting to f-strings throughout
- Replace lineCount() utility with os.path.getsize()
- Add fail-fast error handling with actionable messages
- Verify binaries exist before running
2026-03-17 19:15:20 -04:00
Justin Bollinger fda38d3498 docs: update README with OMEN fix and issues #80, #81, #82 2026-03-17 15:07:02 -04:00
Justin Bollinger 67c462cd41 fix: OMEN attack silent failure - validate model files, capture enumNG errors, add wordlist picker
- Add _omen_model_is_valid() checking all 5 required model files
- Add _omen_model_info() reading model_info.json metadata
- hcatOmenTrain returns bool and writes training metadata
- Capture enumNG stderr and report errors instead of silent failure
- Rewrite omen_attack with train/use/cancel menu and wordlist picker
2026-03-17 15:05:36 -04:00
Justin Bollinger 2852d652bc feat: parallelize hashmob rule downloads and update wordlist filtering in attacks
- Rewrite list_and_download_hashmob_rules with ThreadPoolExecutor
- Update quick_crack to use list_wordlist_files
- Add parallel download tests with stdin TTY mock
2026-03-17 15:05:29 -04:00
Justin Bollinger 36262200a9 feat: filter .7z files from wordlist menus, parallelize rule downloads, dynamic -O flag
- Add list_wordlist_files() helper filtering .7z/.torrent/.out from menus (#80)
- Use ThreadPoolExecutor for parallel rule downloads with summary (#81)
- Add per-attack optimized kernel (-O) via DEFAULT_OPTIMIZED_ATTACKS (#82)
- Add optimizedKernelAttacks config key for user override
2026-03-17 15:05:23 -04:00
Justin Bollinger a1b81ec01d fix: replace uv tool install with bash shim for reliable config resolution
Config and assets were not found when running hate_crack from outside the
repo directory. The shim uses `uv run --directory` to always execute from
the repo root, and _candidate_roots() now includes _repo_root and
_package_path as fallback search locations.
2026-03-17 12:45:19 -04:00
Justin Bollinger d8c009c334 fix: pass configured rules_directory to all rule download and listing callers
api.py's get_rules_dir() independently re-parsed config.json and could
resolve a different rules path than main.py's rulesDirectory global.
Thread the already-resolved rulesDirectory through download_hashmob_rules
and list_and_download_hashmob_rules so all callers (menu option 90,
--rules flag, quick_crack, loopback_attack) use the same path from config.
2026-03-16 14:44:28 -04:00
Justin Bollinger 83491ea17d docs: update README with recent bug fixes in version history 2026-03-16 14:33:43 -04:00
Justin Bollinger 6df74f7969 fix: use os.path.join for rule paths in quick_crack and loopback_attack
Replace 6 instances of f-string slash concatenation with os.path.join()
for building rule file paths, consistent with get_rule_path() in main.py.
2026-03-16 14:32:14 -04:00
Justin Bollinger 79cb290c11 fix: skip leading blank lines in hash format detection
A blank first line caused the format detection regex chain to fall
through to the error exit. Read lines in a loop and skip empty ones
before matching. Add tests for blank lines with LF, CRLF, whitespace,
and BOM variations.
2026-03-16 14:23:09 -04:00
Justin Bollinger f2572d0a80 fix: handle bare NTLM hash detection with BOM, null bytes, and encoding artifacts
Use utf-8-sig encoding to natively strip BOM and remove null bytes from
UTF-16 artifacts so the bare hash regex matches correctly. Replace the
unhelpful "unknown format" error with a diagnostic message showing the
actual first-line content and expected formats.
2026-03-16 14:17:37 -04:00
Justin Bollinger 83f63ddf23 test: add menu test module 2026-03-16 13:08:59 -04:00
Justin Bollinger 22d8858825 feat: default wordlist autocomplete to hcatWordlists dir and add TUI extra 2026-03-16 12:48:45 -04:00
Justin Bollinger ab11985022 fix: ensure potfile exists before passing --potfile-path to hashcat
When running as root, ~/.hashcat/ exists but hashcat.potfile does not.
Hashcat refuses to create the file when given an explicit --potfile-path.
Create the parent directory and touch the file in _append_potfile_arg()
before appending the flag.
2026-03-16 12:37:36 -04:00