From 013ad55196d26b0031eb40bfa0c957fddd9915fc Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Thu, 28 May 2026 12:02:17 -0400 Subject: [PATCH 1/2] fix(pipal): preserve newline on $HEX[...] cracked rows binascii.unhexlify().decode() returned bytes without the trailing newline that normal rows inherit from password[-1], so HEX-encoded cracked passwords concatenated with the next entry in the .passwords file fed to pipal. Pipal under-counted entries and ranked corrupted mashups as base words. Re-append \n if missing. Co-Authored-By: Claude Opus 4.7 (1M context) --- hate_crack/main.py | 2 ++ tests/test_pipal.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/hate_crack/main.py b/hate_crack/main.py index 7e4c0fd..93dde4a 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -4220,6 +4220,8 @@ def pipal(): clearTextPass = binascii.unhexlify(match.group(1)).decode( "iso-8859-9" ) + if not clearTextPass.endswith("\n"): + clearTextPass += "\n" pipalFile.write(clearTextPass) pipalFile.close() diff --git a/tests/test_pipal.py b/tests/test_pipal.py index 407c95a..e740c25 100644 --- a/tests/test_pipal.py +++ b/tests/test_pipal.py @@ -46,6 +46,44 @@ def test_pipal_runs_and_parses_basewords(hc_module, tmp_path, capsys): assert "pass123" in content +def test_pipal_passwords_file_is_newline_separated_with_hex(hc_module, tmp_path): + """HEX-encoded cracked passwords must not be glued to the next line. + + Regression: binascii.unhexlify().decode() drops the trailing newline that + normal lines retain from password[-1], so HEX rows concatenated with the + next password in the .passwords file fed to pipal. + """ + hc = hc_module + hc.hcatHashType = "0" + hc.pipal_count = 3 + hc.hcatHashFile = str(tmp_path / "hashes") + + out_path = tmp_path / "hashes.out" + out_path.write_text( + "hash1:password123\n" + "hash2:$HEX[70617373313233]\n" # "pass123" + "hash3:letmein\n" + ) + + pipal_stub = tmp_path / "pipal_stub.py" + _write_executable( + pipal_stub, + "#!/usr/bin/env python3\n" + "import sys\n" + "out = sys.argv[sys.argv.index('--output') + 1]\n" + "open(out, 'w').write('Top 3 base words\\n'\n" + " 'pass123 1\\n'\n" + " 'letmein 1\\n'\n" + " 'welcome 1\\n')\n", + ) + hc.pipalPath = str(pipal_stub) + + hc.pipal() + + passwords = (tmp_path / "hashes.passwords").read_text().splitlines() + assert passwords == ["password123", "pass123", "letmein"] + + def test_pipal_missing_out_returns_empty(hc_module, tmp_path, capsys): hc = hc_module hc.hcatHashType = "0" From 8b58963660b9e6ea22b3d3fbb0d02a82e70c3a07 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Thu, 28 May 2026 12:02:17 -0400 Subject: [PATCH 2/2] docs: add v2.10.5 version history entry Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a7d51c3..6f83a7b 100644 --- a/README.md +++ b/README.md @@ -912,6 +912,9 @@ Interactive menu for downloading and managing wordlists from Weakpass.com via Bi ------------------------------------------------------------------- ### Version History +Version 2.10.5 + - Pipal analysis no longer corrupts its input when cracked passwords contain `$HEX[...]` rows. `binascii.unhexlify().decode()` returned the bytes without the trailing newline that normal rows inherit from `password[-1]`, so every HEX-encoded password got concatenated with the next one in the `.passwords` file fed to pipal (e.g. three cracks → two lines, one of them a bogus mashup). Pipal then under-counted entries and reported wrong top base words. The HEX branch now re-appends `\n` so each cracked password lands on its own line + Version 2.10.4 - Pushover notifications fire correctly for Quick Crack, Loopback, Combinator, PRINCE-LING, and N-gram attacks (#110). The handlers prompted the user under one name (e.g. "Quick Crack") while the underlying hashcat wrapper passed a different `attack_name` to `_should_fire` ("Quick Dictionary"), so the per-run consent lookup always missed. The prompt name now flows down to `_run_hcat_cmd` for both the job-done summary and the per-crack tailer