issue executing hate_crack from make install outside of the install directory

This commit is contained in:
Justin Bollinger
2026-02-01 21:54:33 -05:00
parent 82d2a1a13a
commit d23a0a8be8
5 changed files with 157 additions and 67 deletions
+3
View File
@@ -24,6 +24,7 @@ install:
clean:
-$(MAKE) -C hashcat-utils clean
rm -rf .pytest_cache .ruff_cache build dist *.egg-info
rm -rf ~/.cache/uv
find . -name "__pycache__" -type d -prune -exec rm -rf {} +
test:
@@ -37,10 +38,12 @@ uninstall:
command -v brew >/dev/null 2>&1 || { echo >&2 "Homebrew not found. Please uninstall Homebrew packages manually."; exit 1; }; \
brew uninstall --ignore-dependencies p7zip transmission-cli || true; \
uv tool uninstall hate_crack || true; \
rm -rf ~/.cache/uv; \
elif [ -f /etc/debian_version ]; then \
echo "Detected Debian/Ubuntu"; \
sudo apt-get remove -y p7zip-full transmission-cli || true; \
uv tool uninstall hate_crack || true; \
rm -rf ~/.cache/uv; \
else \
echo "Unsupported OS. Please uninstall dependencies manually."; \
exit 1; \
+22 -3
View File
@@ -109,13 +109,32 @@ uv tool install .
hate_crack
```
If you run the tool outside the repo, set `HATE_CRACK_HOME` so assets like
`hashcat-utils` can be found:
**Important:** The installed tool needs access to `hashcat-utils` and `princeprocessor` subdirectories. These are located in the original repository directory.
When running from outside the repository, configure the asset location in your `config.json` by setting `hcatPath`:
```json
{
"hcatPath": "/opt/hate_crack",
...
}
```
Then run the tool:
```bash
HATE_CRACK_HOME=/path/to/hate_crack hate_crack
hate_crack <hash_file> <hash_type>
```
Alternatively, run `hate_crack` from within the repository directory and it will automatically find the assets:
```bash
cd /path/to/hate_crack
hate_crack <hash_file> <hash_type>
```
If `hcatPath` is empty in config.json, the tool will search the current directory and parent directory for assets.
### Run as a script
The script uses a `uv` shebang. Make it executable and run:
+21
View File
@@ -3,6 +3,27 @@
## Overview
The test suite uses mocked API responses and local fixtures so it can run without external services (Hashview, Hashmob, Weakpass). Most tests are fast and run entirely offline. Live network checks and system dependency checks are now opt-in via environment variables.
## Important: Asset Location (hcatPath)
The application requires access to `hashcat-utils` and `princeprocessor` as subdirectories. When running tests or the tool outside the repository directory, configure the asset location in `config.json` by setting `hcatPath`:
```json
{
"hcatPath": "/path/to/hate_crack",
...
}
```
Then run tests or the tool normally:
```bash
uv run pytest -v
# or
hate_crack <hash_file> <hash_type>
```
If `hcatPath` is not set (empty string), the application will search the current directory and parent directory for these assets.
## Changes Made
### 1. Test Files (Current)
+1 -1
View File
@@ -1,5 +1,5 @@
{
"hcatPath": "",
"hcatPath": "/path/to/hate_crack",
"hcatBin": "hashcat",
"hcatTuning": "--force --remove",
"hcatWordlists": "/Passwords/wordlists",
+110 -63
View File
@@ -82,34 +82,71 @@ def _has_hate_crack_assets(path):
)
def _resolve_hate_path(package_path):
def _resolve_hate_path(package_path, config_dict=None):
# Try to use hcatPath from config.json if it's set and contains assets
if config_dict and config_dict.get('hcatPath'):
assets_path = config_dict.get('hcatPath')
if _has_hate_crack_assets(assets_path):
return assets_path
# Check environment variable as fallback
env_override = os.environ.get("HATE_CRACK_HOME") or os.environ.get("HATE_CRACK_ASSETS")
candidates = []
if env_override:
candidates.append(env_override)
if _has_hate_crack_assets(env_override):
return env_override
# Check current working directory and parent (look for repo directory)
cwd = os.getcwd()
candidates.extend([cwd, os.path.abspath(os.path.join(cwd, os.pardir))])
candidates.append(package_path)
for candidate in candidates:
for candidate in [cwd, os.path.abspath(os.path.join(cwd, os.pardir))]:
if _has_hate_crack_assets(candidate):
return candidate
# Check common locations for the repo
home = os.path.expanduser("~")
for candidate_name in ['hate_crack', 'hate-crack', '.hate_crack']:
candidate = os.path.join(home, candidate_name)
if _has_hate_crack_assets(candidate):
return candidate
# When installed as a tool, assets should be defined in config.json hcatPath
# If not set, default to package path (which may not have assets)
if _has_hate_crack_assets(package_path):
return package_path
# Last resort: return package_path, but this likely means assets are missing
if not config_dict or not config_dict.get('hcatPath'):
print("\nWarning: Could not find hate_crack assets (hashcat-utils, princeprocessor).")
print("Set 'hcatPath' in config.json to the installation directory:")
print(" \"hcatPath\": \"/path/to/hate_crack\"")
print("Or run from the repository directory where these assets are located.\n")
return package_path
hate_path = _resolve_hate_path(os.path.dirname(os.path.realpath(__file__)))
if not os.path.isfile(hate_path + '/config.json'):
# First get a temporary path to load config
_initial_package_path = os.path.dirname(os.path.realpath(__file__))
if not os.path.isfile(_initial_package_path + '/config.json'):
print('Initializing config.json from config.json.example')
shutil.copy(hate_path + '/config.json.example', hate_path + '/config.json')
shutil.copy(_initial_package_path + '/config.json.example', _initial_package_path + '/config.json')
with open(hate_path + '/config.json') as config:
with open(_initial_package_path + '/config.json') as config:
config_parser = json.load(config)
with open(hate_path + '/config.json.example') as defaults:
with open(_initial_package_path + '/config.json.example') as defaults:
default_config = json.load(defaults)
# Now resolve hate_path using config
hate_path = _resolve_hate_path(_initial_package_path, config_parser)
# If hate_path differs from initial path, reload config from the new location
if hate_path != _initial_package_path:
if os.path.isfile(hate_path + '/config.json'):
with open(hate_path + '/config.json') as config:
config_parser = json.load(config)
if os.path.isfile(hate_path + '/config.json.example'):
with open(hate_path + '/config.json.example') as defaults:
default_config = json.load(defaults)
try:
hashview_url = config_parser['hashview_url']
except KeyError as e:
@@ -179,7 +216,7 @@ from hate_crack.cli import (
setup_logging,
)
hcatPath = config_parser['hcatPath']
hcatPath = config_parser.get('hcatPath', '') or hate_path
hcatBin = config_parser['hcatBin']
hcatTuning = config_parser['hcatTuning']
hcatWordlists = config_parser['hcatWordlists']
@@ -278,6 +315,11 @@ def verify_wordlist_dir(directory, wordlist):
elif os.path.isfile(directory + '/' + wordlist):
return directory + '/' + wordlist
else:
# If not running interactively (no TTY), just return the expected path
# This prevents EOFError during module import when running via subprocess
if not sys.stdin.isatty():
return os.path.join(directory, wordlist)
print('Invalid path for {0}. Please check configuration and try again.'.format(wordlist))
response = input(f"Wordlist '{wordlist}' not found. Would you like to download rockyou.txt.gz automatically? (Y/n): ").strip().lower()
if response in ('', 'y', 'yes'):
@@ -361,57 +403,62 @@ def cleanup_wordlist_artifacts():
print(f"[!] Failed to remove archive {path}: {e}")
if not SKIP_INIT:
# hashcat biniary checks for systems that install hashcat binary in different location than the rest of the hashcat files
if hcatPath:
candidate = hcatPath.rstrip('/') + '/' + hcatBin
if os.path.isfile(candidate):
hcatBin = candidate
elif os.path.isfile(hcatBin):
pass
else:
print('Invalid path for hashcat binary. Please check configuration and try again.')
quit(1)
else:
# No hcatPath set, just use hcatBin (should be in PATH)
if shutil.which(hcatBin) is None:
print('Hashcat binary not found in PATH. Please check configuration and try again.')
quit(1)
# Verify hashcat-utils binaries exist and work
hashcat_utils_path = hate_path + '/hashcat-utils/bin'
required_binaries = [
(hcatExpanderBin, 'expander'),
(hcatCombinatorBin, 'combinator'),
]
for binary, name in required_binaries:
binary_path = hashcat_utils_path + '/' + binary
ensure_binary(binary_path, build_dir=os.path.join(hate_path, 'hashcat-utils'), name=name)
# Test binary execution
try:
test_result = subprocess.run(
[binary_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=2
)
# Binary should show usage and exit with error code (that's expected)
# If we get here without exception, the binary is executable
except subprocess.TimeoutExpired:
# Timeout is fine - means binary is running
pass
except Exception as e:
print(f'Error: {name} binary at {binary_path} failed to execute: {e}')
print('The binary may be compiled for the wrong architecture.')
print('Try recompiling hashcat-utils for your system.')
quit(1)
# Verify princeprocessor binary
prince_path = hate_path + '/princeprocessor/' + hcatPrinceBin
# Verify hashcat binary is available
# hcatPath is for assets (hashcat-utils, princeprocessor), not hashcat binary location
# hcatBin should be in PATH or be an absolute path
try:
ensure_binary(prince_path, build_dir=os.path.join(hate_path, 'princeprocessor'), name='PRINCE')
except SystemExit:
print('PRINCE attacks will not be available.')
if os.path.isabs(hcatBin):
if not os.path.isfile(hcatBin):
print(f'Hashcat binary not found at {hcatBin}. Please check configuration and try again.')
quit(1)
else:
# hcatBin should be in PATH
if shutil.which(hcatBin) is None:
print(f'Hashcat binary "{hcatBin}" not found in PATH. Please check configuration and try again.')
quit(1)
# Verify hashcat-utils binaries exist and work
hashcat_utils_path = hcatPath + '/hashcat-utils/bin'
required_binaries = [
(hcatExpanderBin, 'expander'),
(hcatCombinatorBin, 'combinator'),
]
for binary, name in required_binaries:
binary_path = hashcat_utils_path + '/' + binary
ensure_binary(binary_path, build_dir=os.path.join(hcatPath, 'hashcat-utils'), name=name)
# Test binary execution
try:
test_result = subprocess.run(
[binary_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=2
)
# Binary should show usage and exit with error code (that's expected)
# If we get here without exception, the binary is executable
except subprocess.TimeoutExpired:
# Timeout is fine - means binary is running
pass
except Exception as e:
print(f'Error: {name} binary at {binary_path} failed to execute: {e}')
print('The binary may be compiled for the wrong architecture.')
print('Try recompiling hashcat-utils for your system.')
quit(1)
# Verify princeprocessor binary
prince_path = hcatPath + '/princeprocessor/' + hcatPrinceBin
try:
ensure_binary(prince_path, build_dir=os.path.join(hcatPath, 'princeprocessor'), name='PRINCE')
except SystemExit:
print('PRINCE attacks will not be available.')
except Exception as e:
print(f'Module initialization error: {e}')
if not shutil.which('hashcat') and not os.path.exists('/usr/bin/hashcat'):
print('Warning: Cannot find hashcat in PATH. Install it to use hate_crack.')
# Allow module to load even if initialization fails
pass
#verify and convert wordlists to fully qualified paths
hcatMiddleBaseList = verify_wordlist_dir(hcatWordlists, hcatMiddleBaseList)