feat: add hashcat/princeprocessor submodules, fix fresh-install setup

- Add hashcat as git submodule; compile with make, skip if already in PATH
- Convert princeprocessor from tracked files to git submodule
- Change .DEFAULT_GOAL to install so plain `make` does a full install
- Install uv, Xcode CLT (macOS), build-essential (Debian) if missing
- vendor-assets falls back to system hashcat if submodule not compiled
- Remove hcatOptimizedWordlists; all attacks now use hcatWordlists
- Default hcatWordlists to ./wordlists, rules_directory to ./hashcat/rules
- Default hcatTuning to empty string (no --force --remove)
- Backfill missing config.json keys from config.json.example at startup
- Wrap hcatBin/hcatTuning/hcatWordlists loading in try/except with defaults
- Fall back to vendored hashcat binary at hate_path/hashcat/hashcat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-02-20 16:19:07 -05:00
parent fed5da12a3
commit 15b3ab77fd
18 changed files with 99 additions and 9883 deletions

8
.gitmodules vendored
View File

@@ -10,3 +10,11 @@
path = omen
url = https://github.com/RUB-SysSec/OMEN.git
ignore = dirty
[submodule "hashcat"]
path = hashcat
url = https://github.com/hashcat/hashcat.git
ignore = dirty
[submodule "princeprocessor"]
path = princeprocessor
url = https://github.com/hashcat/princeprocessor.git
ignore = dirty

View File

@@ -1,4 +1,4 @@
.DEFAULT_GOAL := submodules
.DEFAULT_GOAL := install
.PHONY: install reinstall update dev-install dev-reinstall clean hashcat-utils submodules submodules-pre vendor-assets clean-vendor test coverage lint check ruff ty
hashcat-utils: submodules
@@ -12,6 +12,10 @@ submodules:
$(MAKE) submodules-pre; \
if [ -f .gitmodules ] && command -v git >/dev/null 2>&1; then \
for path in $$(git config --file .gitmodules --get-regexp path | awk '{print $$2}'); do \
if [ "$$path" = "hashcat" ] && command -v hashcat >/dev/null 2>&1; then \
echo "hashcat already installed in PATH, skipping submodule compilation"; \
continue; \
fi; \
if [ -f "$$path/Makefile" ] || [ -f "$$path/makefile" ]; then \
$(MAKE) -C "$$path"; \
fi; \
@@ -22,6 +26,9 @@ submodules:
submodules-pre:
@# Pre-step: basic sanity checks and file generation before building submodules.
@# Ensure required directories exist (whether as submodules or vendored copies).
@# hashcat is optional here: submodule is compiled if present, else PATH hashcat is used.
@test -d hashcat || command -v hashcat >/dev/null 2>&1 || { \
echo "Error: hashcat not found. Either initialize the hashcat submodule or install hashcat."; exit 1; }
@test -d hashcat-utils || { echo "Error: missing required directory: hashcat-utils"; exit 1; }
@test -d princeprocessor || { echo "Error: missing required directory: princeprocessor"; exit 1; }
@test -d omen || { echo "Warning: missing directory: omen (OMEN attacks will not be available)"; }
@@ -35,7 +42,32 @@ vendor-assets:
exit 1; \
fi
@echo "Syncing assets into package for uv tool install..."
@rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
@rm -rf hate_crack/hashcat hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
@mkdir -p hate_crack/hashcat
@if [ -f hashcat/hashcat ]; then \
echo "Vendoring compiled hashcat submodule binary..."; \
cp hashcat/hashcat hate_crack/hashcat/hashcat; \
[ -d hashcat/rules ] && cp -R hashcat/rules hate_crack/hashcat/rules || true; \
[ -d hashcat/OpenCL ] && cp -R hashcat/OpenCL hate_crack/hashcat/OpenCL || true; \
[ -d hashcat/modules ] && cp -R hashcat/modules hate_crack/hashcat/modules || true; \
elif [ -f hashcat/hashcat.app ]; then \
echo "Vendoring compiled hashcat submodule binary (macOS app)..."; \
cp hashcat/hashcat.app hate_crack/hashcat/hashcat; \
[ -d hashcat/rules ] && cp -R hashcat/rules hate_crack/hashcat/rules || true; \
[ -d hashcat/OpenCL ] && cp -R hashcat/OpenCL hate_crack/hashcat/OpenCL || true; \
[ -d hashcat/modules ] && cp -R hashcat/modules hate_crack/hashcat/modules || true; \
elif command -v hashcat >/dev/null 2>&1; then \
HASHCAT_PATH=$$(command -v hashcat); \
echo "Using system hashcat from $$HASHCAT_PATH..."; \
cp "$$HASHCAT_PATH" hate_crack/hashcat/hashcat; \
HASHCAT_DIR=$$(dirname $$(realpath "$$HASHCAT_PATH")); \
[ -d "$$HASHCAT_DIR/rules" ] && cp -R "$$HASHCAT_DIR/rules" hate_crack/hashcat/rules || true; \
[ -d "$$HASHCAT_DIR/OpenCL" ] && cp -R "$$HASHCAT_DIR/OpenCL" hate_crack/hashcat/OpenCL || true; \
[ -d "$$HASHCAT_DIR/modules" ] && cp -R "$$HASHCAT_DIR/modules" hate_crack/hashcat/modules || true; \
else \
echo "Error: hashcat not found. Either compile the hashcat submodule or install hashcat."; \
exit 1; \
fi
@cp -R hashcat-utils hate_crack/
@cp -R princeprocessor hate_crack/
@if [ -d omen ]; then \
@@ -46,23 +78,31 @@ vendor-assets:
clean-vendor:
@echo "Cleaning up vendored assets from working tree..."
@rm -rf hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
@rm -rf hate_crack/hashcat hate_crack/hashcat-utils hate_crack/princeprocessor hate_crack/omen
install: submodules vendor-assets
@echo "Detecting OS and installing dependencies..."
@if [ "$(shell uname)" = "Darwin" ]; then \
echo "Detected macOS"; \
xcode-select -p >/dev/null 2>&1 || { \
echo "Xcode Command Line Tools not found. Installing..."; \
xcode-select --install; \
echo "Re-run 'make' after the Xcode CLT installation completes."; \
exit 1; \
}; \
command -v brew >/dev/null 2>&1 || { echo >&2 "Homebrew not found. Please install Homebrew first: https://brew.sh/"; exit 1; }; \
brew install p7zip transmission-cli; \
elif [ -f /etc/debian_version ]; then \
echo "Detected Debian/Ubuntu"; \
command -v gcc >/dev/null 2>&1 || { sudo apt-get update && sudo apt-get install -y build-essential; }; \
sudo apt-get update; \
sudo apt-get install -y p7zip-full transmission-cli; \
else \
echo "Unsupported OS. Please install dependencies manually."; \
exit 1; \
fi
@uv tool install -e . --force --reinstall
@command -v uv >/dev/null 2>&1 || { echo "uv not found. Installing uv..."; curl -LsSf https://astral.sh/uv/install.sh | sh; }
@uv tool install -e .
update: submodules vendor-assets
@uv tool install -e . --force --reinstall
@@ -78,6 +118,7 @@ dev-reinstall: uninstall dev-install
clean:
-$(MAKE) -C hashcat-utils clean
-$(MAKE) -C hashcat clean
-@if [ -f .gitmodules ]; then git submodule deinit -f --all; fi
rm -rf .pytest_cache .ruff_cache build dist *.egg-info
rm -rf ~/.cache/uv

View File

@@ -1,12 +1,11 @@
{
"hcatPath": "/path/to/hashcat",
"hcatBin": "hashcat",
"hcatTuning": "--force --remove",
"hcatTuning": "",
"hcatPotfilePath": "~/.hashcat/hashcat.potfile",
"hcatDebugLogPath": "./hashcat_debug",
"hcatWordlists": "/Passwords/wordlists",
"hcatOptimizedWordlists": "/Passwords/optimized_wordlists",
"rules_directory": "/path/to/hashcat/rules",
"hcatWordlists": "./wordlists",
"rules_directory": "./hashcat/rules",
"hcatDictionaryWordlist": ["rockyou.txt"],
"hcatCombinationWordlist": ["rockyou.txt","rockyou.txt"],
"hcatHybridlist": ["rockyou.txt"],

1
hashcat Submodule

Submodule hashcat added at 2d71af3718

View File

@@ -69,11 +69,11 @@ def quick_crack(ctx: Any) -> None:
try:
raw_choice = input(
"\nEnter path of wordlist or wordlist directory (tab to autocomplete).\n"
f"Press Enter for default optimized wordlists [{ctx.hcatOptimizedWordlists}]: "
f"Press Enter for default wordlist directory [{ctx.hcatWordlists}]: "
)
raw_choice = raw_choice.strip()
if raw_choice == "":
wordlist_choice = ctx.hcatOptimizedWordlists
wordlist_choice = ctx.hcatWordlists
elif raw_choice.isdigit() and 1 <= int(raw_choice) <= len(wordlist_files):
chosen = os.path.join(
ctx.hcatWordlists, wordlist_files[int(raw_choice) - 1]

View File

@@ -1,12 +1,11 @@
{
"hcatPath": "",
"hcatBin": "hashcat",
"hcatTuning": "--force --remove",
"hcatTuning": "",
"hcatPotfilePath": "~/.hashcat/hashcat.potfile",
"hcatDebugLogPath": "./hashcat_debug",
"hcatWordlists": "/Passwords/wordlists",
"hcatOptimizedWordlists": "/Passwords/optimized_wordlists",
"rules_directory": "/path/to/hashcat/rules",
"hcatWordlists": "./wordlists",
"rules_directory": "./hashcat/rules",
"hcatDictionaryWordlist": ["rockyou.txt"],
"hcatCombinationWordlist": ["rockyou.txt","rockyou.txt"],
"hcatHybridlist": ["rockyou.txt"],

View File

@@ -184,6 +184,16 @@ if not os.path.isfile(defaults_path):
with open(defaults_path) as defaults:
default_config = json.load(defaults)
_config_updated = False
for _key, _value in default_config.items():
if _key not in config_parser:
config_parser[_key] = _value
print(f"[config] Added missing key '{_key}' with default value")
_config_updated = True
if _config_updated:
with open(_config_path, "w") as _cf:
json.dump(config_parser, _cf, indent=2)
try:
hashview_url = config_parser["hashview_url"]
except KeyError as e:
@@ -243,7 +253,10 @@ def ensure_binary(binary_path, build_dir=None, name=None):
# NOTE: hcatPath is the hashcat install directory, NOT for hate_crack assets.
# hashcat-utils and princeprocessor should ALWAYS use hate_path.
hcatPath = config_parser.get("hcatPath", "")
hcatBin = config_parser["hcatBin"]
try:
hcatBin = config_parser["hcatBin"]
except KeyError:
hcatBin = default_config["hcatBin"]
# If hcatBin is not absolute and hcatPath is set, construct full path from hcatPath + hcatBin
if not os.path.isabs(hcatBin) and hcatPath:
_candidate = os.path.join(hcatPath, hcatBin)
@@ -254,9 +267,20 @@ if not hcatPath:
_which = shutil.which(hcatBin)
if _which:
hcatPath = os.path.dirname(os.path.realpath(_which))
hcatTuning = config_parser["hcatTuning"]
hcatWordlists = config_parser["hcatWordlists"]
hcatOptimizedWordlists = config_parser["hcatOptimizedWordlists"]
# Fall back to the vendored hashcat binary if not found via PATH or hcatPath
if shutil.which(hcatBin) is None and not os.path.isfile(hcatBin):
_vendored_hcat = os.path.join(hate_path, "hashcat", "hashcat")
if os.path.isfile(_vendored_hcat) and os.access(_vendored_hcat, os.X_OK):
hcatBin = _vendored_hcat
hcatPath = os.path.join(hate_path, "hashcat")
try:
hcatTuning = config_parser["hcatTuning"]
except KeyError:
hcatTuning = default_config["hcatTuning"]
try:
hcatWordlists = config_parser["hcatWordlists"]
except KeyError:
hcatWordlists = "./wordlists"
hcatRules: list[str] = []
@@ -302,27 +326,16 @@ rulesDirectory = os.path.expanduser(rulesDirectory)
if not os.path.isabs(rulesDirectory):
rulesDirectory = os.path.join(hate_path, rulesDirectory)
# Normalize wordlist directories
# Normalize wordlist directory
hcatWordlists = os.path.expanduser(hcatWordlists)
if not os.path.isabs(hcatWordlists):
hcatWordlists = os.path.join(hate_path, hcatWordlists)
hcatOptimizedWordlists = os.path.expanduser(hcatOptimizedWordlists)
if not os.path.isabs(hcatOptimizedWordlists):
hcatOptimizedWordlists = os.path.join(hate_path, hcatOptimizedWordlists)
if not os.path.isdir(hcatWordlists):
fallback_wordlists = os.path.join(hate_path, "wordlists")
if os.path.isdir(fallback_wordlists):
print(f"[!] hcatWordlists directory not found: {hcatWordlists}")
print(f"[!] Falling back to {fallback_wordlists}")
hcatWordlists = fallback_wordlists
if not os.path.isdir(hcatOptimizedWordlists):
fallback_optimized = os.path.join(hate_path, "optimized_wordlists")
if os.path.isdir(fallback_optimized):
print(
f"[!] hcatOptimizedWordlists directory not found: {hcatOptimizedWordlists}"
)
print(f"[!] Falling back to {fallback_optimized}")
hcatOptimizedWordlists = fallback_optimized
try:
maxruntime = config_parser["bandrelmaxruntime"]
@@ -1198,9 +1211,9 @@ def hcatDictionary(hcatHashType, hcatHashFile):
global hcatDictionaryCount
global hcatProcess
rule_best66 = get_rule_path("best66.rule")
optimized_lists = sorted(glob.glob(os.path.join(hcatOptimizedWordlists, "*")))
optimized_lists = sorted(glob.glob(os.path.join(hcatWordlists, "*")))
if not optimized_lists:
optimized_lists = [os.path.join(hcatOptimizedWordlists, "*")]
optimized_lists = [os.path.join(hcatWordlists, "*")]
cmd = [
hcatBin,
"-m",
@@ -1589,10 +1602,10 @@ def hcatYoloCombination(hcatHashType, hcatHashFile):
global hcatProcess
try:
while 1:
hcatLeft = random.choice(os.listdir(hcatOptimizedWordlists))
hcatRight = random.choice(os.listdir(hcatOptimizedWordlists))
left_path = os.path.join(hcatOptimizedWordlists, hcatLeft)
right_path = os.path.join(hcatOptimizedWordlists, hcatRight)
hcatLeft = random.choice(os.listdir(hcatWordlists))
hcatRight = random.choice(os.listdir(hcatWordlists))
left_path = os.path.join(hcatWordlists, hcatLeft)
right_path = os.path.join(hcatWordlists, hcatRight)
cmd = [
hcatBin,
"-m",
@@ -3575,7 +3588,7 @@ def main():
global lmHashesFound
global debug_mode
global hashview_url, hashview_api_key
global hcatPath, hcatBin, hcatWordlists, hcatOptimizedWordlists, rulesDirectory
global hcatPath, hcatBin, hcatWordlists, rulesDirectory
global pipalPath, maxruntime, bandrelbasewords
global hcatPotfilePath
@@ -3812,7 +3825,6 @@ def main():
hcatPath=hcatPath,
hcatBin=hcatBin,
hcatWordlists=hcatWordlists,
hcatOptimizedWordlists=hcatOptimizedWordlists,
rules_directory=rulesDirectory,
pipalPath=pipalPath,
maxruntime=maxruntime,
@@ -3824,7 +3836,6 @@ def main():
hcatPath = config.hcatPath
hcatBin = config.hcatBin
hcatWordlists = config.hcatWordlists
hcatOptimizedWordlists = config.hcatOptimizedWordlists
rulesDirectory = config.rules_directory
pipalPath = config.pipalPath
maxruntime = config.maxruntime

1
princeprocessor Submodule

Submodule princeprocessor added at 4160061be7

View File

@@ -1,55 +0,0 @@
* v0.20 -> v0.21:
- Exit if stdout is closed or has a error
- Fix for "Bug --pw-min" issue
- Print position when stopped
- Allow wordlist as fileparameter
- Load only NUM words from input wordlist or use 0 to disable
* v0.19 -> v0.20:
- Add dupe suppression
- Add a fake-GMP header using uint128_t macros. This is to replace depency on GMP
- Add --case-permute amplifier option, default is disabled
- Fixed buffer overflow
- Fixed accidental reverted changes
- Fixed a bug where ee actually couldn't correctly support output longer than 31 but 32 is supported
- More memory savings: Use only the actual space needed for each word
* v0.18 -> v0.19:
- Fixed missing free() in shutdown section
- Fixed wrong version number in source
- Fixed discrepancies with logic and error messages
- Added validation check pw-max > elem-cnt-max
- Untie IN_LEN_* from PW_* to allow --pw-max > 16 without recompilation
- If out of memory, tell how much we tried to allocate
- Allow hex input for --skip and --limit
- Optimized output performance
* v0.17 -> v0.18:
- Fixed major bug where all candidates are of the same length till chain changes
* v0.16 -> v0.17:
- Fixed download url for binaries in README
- Fixed copy paste bug in input verification
- Fixed bug where pw_orders is not sorted
- Fixed memory leak
- Removed O_BINARY for stderr
- Removed some unused code
- Renamed variables so that they match the meaning from the presentation slides
- Optimized seeking performance
- Optimized output performance
* v0.15 -> v0.16:
- Open Source the project
- License is MIT
- Moved repository to github: https://github.com/jsteube/princeprocessor
- Added CHANGES
- Added LICENSE
- Added README.md
- Changed default value for --pw-max from 24 to 16 for faster startup time

View File

@@ -1,33 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Jens Steube,
Copyright (c) 2015 magnum
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------
malloc_tiny() and the hashed dupe suppression are based on code from John the
Ripper password cracker:
Copyright (c) 1996-99,2002-2003,2005-2006,2010-2012 by Solar Designer
Redistribution and use in source and binary forms, with or without
modification, are permitted.
There's ABSOLUTELY NO WARRANTY, express or implied.

View File

@@ -1,38 +0,0 @@
princeprocessor
==============
Standalone password candidate generator using the PRINCE algorithm
The name PRINCE is used as an acronym and stands for PRobability INfinite Chained Elements, which are the building blocks of the algorithm
Brief description
--------------
The princeprocessor is a password candidate generator and can be thought of as an advanced combinator attack. Rather than taking as input two different wordlists and then outputting all the possible two word combinations though, princeprocessor only has one input wordlist and builds "chains" of combined words. These chains can have 1 to N words from the input wordlist concatenated together. So for example if it is outputting guesses of length four, it could generate them using combinations from the input wordlist such as:
- 4 letter word
- 2 letter word + 2 letter word
- 1 letter word + 3 letter word
- 3 letter word + 1 letter word
- 1 letter word + 1 letter word + 2 letter word
- 1 letter word + 2 letter word + 1 letter word
- 2 letter word + 1 letter word + 1 letter word
- 1 letter word + 1 letter word + 1 letter word + 1 letter word
Detailed description
--------------
I'm going to write a detailed description in case I'm extremely bored. Till that, use the following resources:
- My talk about princeprocessor on Passwords^14 conference in Trondheim, Norway. Slides: https://hashcat.net/events/p14-trondheim/prince-attack.pdf
- Thanks to Matt Weir, he made a nice analysis of princeprocessor. You can find the post on his blog: http://reusablesec.blogspot.de/2014/12/tool-deep-dive-prince.html
Compile
--------------
Simply run make
Binary distribution
--------------
Binaries for Linux, Windows and OSX: https://github.com/jsteube/princeprocessor/releases

View File

@@ -1 +0,0 @@
0

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -50,7 +50,6 @@ def test_config_with_explicit_hashcat_path():
"hcatBin": "hashcat",
"hcatTuning": "--force",
"hcatWordlists": "./wordlists",
"hcatOptimizedWordlists": "./optimized_wordlists",
"rules_directory": "/opt/hashcat/rules",
"hcatDictionaryWordlist": ["rockyou.txt"],
"hcatCombinationWordlist": ["rockyou.txt"],