mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-30 23:50:31 -07:00
@@ -33,6 +33,15 @@ Core logic is now split into modules under `hate_crack/`:
|
|||||||
|
|
||||||
The top-level `hate_crack.py` remains the main entry point and orchestrates these modules.
|
The top-level `hate_crack.py` remains the main entry point and orchestrates these modules.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
## References and Thanks
|
||||||
|
|
||||||
|
This project depends on and is inspired by a number of external projects and services. Thanks to:
|
||||||
|
|
||||||
|
- Hashview (http://github.com/hashview/)
|
||||||
|
- Weakpass (https://weakpass.com)
|
||||||
|
- Hashmob (https://hashmob.net)
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
## Usage
|
## Usage
|
||||||
`$ ./hate_crack.py`
|
`$ ./hate_crack.py`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"hcatTuning": "--force --remove",
|
"hcatTuning": "--force --remove",
|
||||||
"hcatWordlists": "/Passwords/wordlists",
|
"hcatWordlists": "/Passwords/wordlists",
|
||||||
"hcatOptimizedWordlists": "/Passwords/optimized_wordlists",
|
"hcatOptimizedWordlists": "/Passwords/optimized_wordlists",
|
||||||
|
"rules_directory": "/path/to/hashcat/rules",
|
||||||
"hcatDictionaryWordlist": ["rockyou.txt"],
|
"hcatDictionaryWordlist": ["rockyou.txt"],
|
||||||
"hcatCombinationWordlist": ["rockyou.txt","rockyou.txt"],
|
"hcatCombinationWordlist": ["rockyou.txt","rockyou.txt"],
|
||||||
"hcatHybridlist": ["rockyou.txt"],
|
"hcatHybridlist": ["rockyou.txt"],
|
||||||
|
|||||||
+87
-19
@@ -104,6 +104,7 @@ from hate_crack.api import (
|
|||||||
download_all_weakpass_torrents,
|
download_all_weakpass_torrents,
|
||||||
download_hashes_from_hashview,
|
download_hashes_from_hashview,
|
||||||
download_hashmob_wordlists,
|
download_hashmob_wordlists,
|
||||||
|
download_hashmob_rules,
|
||||||
download_weakpass_torrent,
|
download_weakpass_torrent,
|
||||||
extract_with_7z,
|
extract_with_7z,
|
||||||
)
|
)
|
||||||
@@ -120,6 +121,17 @@ hcatWordlists = config_parser['hcatWordlists']
|
|||||||
hcatOptimizedWordlists = config_parser['hcatOptimizedWordlists']
|
hcatOptimizedWordlists = config_parser['hcatOptimizedWordlists']
|
||||||
hcatRules = []
|
hcatRules = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
rulesDirectory = config_parser['rules_directory']
|
||||||
|
except KeyError as e:
|
||||||
|
print('{0} is not defined in config.json using defaults from config.json.example'.format(e))
|
||||||
|
rulesDirectory = default_config.get('rules_directory')
|
||||||
|
if not rulesDirectory:
|
||||||
|
rulesDirectory = os.path.join(hcatPath, "rules") if hcatPath else os.path.join(hate_path, "rules")
|
||||||
|
rulesDirectory = os.path.expanduser(rulesDirectory)
|
||||||
|
if not os.path.isabs(rulesDirectory):
|
||||||
|
rulesDirectory = os.path.join(hate_path, rulesDirectory)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
maxruntime = config_parser['bandrelmaxruntime']
|
maxruntime = config_parser['bandrelmaxruntime']
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
@@ -219,6 +231,38 @@ def verify_wordlist_dir(directory, wordlist):
|
|||||||
print('Exiting. Please check configuration and try again.')
|
print('Exiting. Please check configuration and try again.')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_rule_path(rule_name, fallback_dir=None):
|
||||||
|
candidates = []
|
||||||
|
if rulesDirectory:
|
||||||
|
candidates.append(os.path.join(rulesDirectory, rule_name))
|
||||||
|
if fallback_dir:
|
||||||
|
candidates.append(os.path.join(fallback_dir, rule_name))
|
||||||
|
for candidate in candidates:
|
||||||
|
if os.path.isfile(candidate):
|
||||||
|
return candidate
|
||||||
|
return candidates[0] if candidates else rule_name
|
||||||
|
|
||||||
|
def ensure_toggle_rule():
|
||||||
|
"""Ensure toggles-lm-ntlm.rule exists in the configured rules directory."""
|
||||||
|
if not rulesDirectory:
|
||||||
|
return None
|
||||||
|
target_path = os.path.join(rulesDirectory, "toggles-lm-ntlm.rule")
|
||||||
|
if os.path.isfile(target_path):
|
||||||
|
return target_path
|
||||||
|
source_path = os.path.join(hate_path, "rules", "toggles-lm-ntlm.rule")
|
||||||
|
try:
|
||||||
|
os.makedirs(rulesDirectory, exist_ok=True)
|
||||||
|
if os.path.isfile(source_path):
|
||||||
|
with open(source_path, "r") as src, open(target_path, "w") as dst:
|
||||||
|
dst.write(src.read())
|
||||||
|
else:
|
||||||
|
with open(target_path, "w") as dst:
|
||||||
|
dst.write("l\nu\n")
|
||||||
|
print(f"[i] Created rule file: {target_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[!] Failed to create toggles-lm-ntlm.rule: {e}")
|
||||||
|
return target_path
|
||||||
|
|
||||||
def cleanup_wordlist_artifacts():
|
def cleanup_wordlist_artifacts():
|
||||||
wordlists_dir = hcatWordlists or os.path.join(hate_path, "wordlists")
|
wordlists_dir = hcatWordlists or os.path.join(hate_path, "wordlists")
|
||||||
if not os.path.isabs(wordlists_dir):
|
if not os.path.isabs(wordlists_dir):
|
||||||
@@ -479,15 +523,16 @@ def hcatBruteForce(hcatHashType, hcatHashFile, hcatMinLen, hcatMaxLen):
|
|||||||
def hcatDictionary(hcatHashType, hcatHashFile):
|
def hcatDictionary(hcatHashType, hcatHashFile):
|
||||||
global hcatDictionaryCount
|
global hcatDictionaryCount
|
||||||
global hcatProcess
|
global hcatProcess
|
||||||
|
rule_best66 = get_rule_path("best66.rule")
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {optimized_wordlists}/* "
|
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {optimized_wordlists}/* "
|
||||||
"-r {hcatPath}/rules/best66.rule {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {rule_best66} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
hcatPath=hcatPath,
|
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hcatHashType=hcatHashType,
|
hcatHashType=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
optimized_wordlists=hcatOptimizedWordlists,
|
optimized_wordlists=hcatOptimizedWordlists,
|
||||||
|
rule_best66=rule_best66,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -498,15 +543,16 @@ def hcatDictionary(hcatHashType, hcatHashFile):
|
|||||||
|
|
||||||
|
|
||||||
for wordlist in hcatDictionaryWordlist:
|
for wordlist in hcatDictionaryWordlist:
|
||||||
|
rule_d3ad0ne = get_rule_path("d3ad0ne.rule")
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} "
|
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} "
|
||||||
"-r {hcatPath}/rules/d3ad0ne.rule {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {rule_d3ad0ne} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
hcatPath=hcatPath,
|
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hcatHashType=hcatHashType,
|
hcatHashType=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
hcatWordlist=wordlist,
|
hcatWordlist=wordlist,
|
||||||
|
rule_d3ad0ne=rule_d3ad0ne,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -515,16 +561,16 @@ def hcatDictionary(hcatHashType, hcatHashFile):
|
|||||||
print('Killing PID {0}...'.format(str(hcatProcess.pid)))
|
print('Killing PID {0}...'.format(str(hcatProcess.pid)))
|
||||||
hcatProcess.kill()
|
hcatProcess.kill()
|
||||||
|
|
||||||
|
rule_toxic = get_rule_path("T0XlC.rule")
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} "
|
"{hcatBin} -m {hcatHashType} {hash_file} --session {session_name} -o {hash_file}.out {hcatWordlist} "
|
||||||
"-r {hcatPath}/rules/T0XlC.rule {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {rule_toxic} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
hcatPath=hcatPath,
|
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hcatHashType=hcatHashType,
|
hcatHashType=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
hcatWordlist=wordlist,
|
hcatWordlist=wordlist,
|
||||||
|
rule_toxic=rule_toxic,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -1037,16 +1083,19 @@ def hcatPathwellBruteForce(hcatHashType, hcatHashFile):
|
|||||||
def hcatPrince(hcatHashType, hcatHashFile):
|
def hcatPrince(hcatHashType, hcatHashFile):
|
||||||
global hcatProcess
|
global hcatProcess
|
||||||
hcatHashCracked = lineCount(hcatHashFile + ".out")
|
hcatHashCracked = lineCount(hcatHashFile + ".out")
|
||||||
|
prince_rules_dir = os.path.join(hate_path, "princeprocessor", "rules")
|
||||||
|
prince_rule = get_rule_path("prince_optimized.rule", fallback_dir=prince_rules_dir)
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hate_path}/princeprocessor/{prince_bin} --case-permute --elem-cnt-min=1 --elem-cnt-max=16 -c < "
|
"{hate_path}/princeprocessor/{prince_bin} --case-permute --elem-cnt-min=1 --elem-cnt-max=16 -c < "
|
||||||
"{hcatPrinceBaseList} | {hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out "
|
"{hcatPrinceBaseList} | {hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out "
|
||||||
"-r {hate_path}/princeprocessor/rules/prince_optimized.rule {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {prince_rule} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
prince_bin=hcatPrinceBin,
|
prince_bin=hcatPrinceBin,
|
||||||
hash_type=hcatHashType,
|
hash_type=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
hcatPrinceBaseList=hcatPrinceBaseList,
|
hcatPrinceBaseList=hcatPrinceBaseList,
|
||||||
|
prince_rule=prince_rule,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -1059,16 +1108,18 @@ def hcatPrince(hcatHashType, hcatHashFile):
|
|||||||
def hcatGoodMeasure(hcatHashType, hcatHashFile):
|
def hcatGoodMeasure(hcatHashType, hcatHashFile):
|
||||||
global hcatExtraCount
|
global hcatExtraCount
|
||||||
global hcatProcess
|
global hcatProcess
|
||||||
|
rule_combinator = get_rule_path("combinator.rule")
|
||||||
|
rule_insidepro = get_rule_path("InsidePro-PasswordsPro.rule")
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -r {hcatPath}/rules/combinator.rule "
|
"{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out -r {rule_combinator} "
|
||||||
"-r {hcatPath}/rules/InsidePro-PasswordsPro.rule {hcatGoodMeasureBaseList} {tuning} "
|
"-r {rule_insidepro} {hcatGoodMeasureBaseList} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
"--potfile-path={hate_path}/hashcat.pot".format(
|
|
||||||
hcatPath=hcatPath,
|
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hash_type=hcatHashType,
|
hash_type=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
hcatGoodMeasureBaseList=hcatGoodMeasureBaseList,
|
hcatGoodMeasureBaseList=hcatGoodMeasureBaseList,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
|
rule_combinator=rule_combinator,
|
||||||
|
rule_insidepro=rule_insidepro,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -1136,10 +1187,11 @@ def hcatLMtoNT():
|
|||||||
|
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m 1000 {hash_file}.nt --session {session_name} -o {hash_file}.nt.out {hash_file}.combined "
|
"{hcatBin} -m 1000 {hash_file}.nt --session {session_name} -o {hash_file}.nt.out {hash_file}.combined "
|
||||||
"-r {hate_path}/rules/toggles-lm-ntlm.rule {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {toggle_rule} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
|
toggle_rule=ensure_toggle_rule() or get_rule_path("toggles-lm-ntlm.rule", fallback_dir=os.path.join(hate_path, "rules")),
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -1170,15 +1222,15 @@ def hcatRecycle(hcatHashType, hcatHashFile, hcatNewPasswords):
|
|||||||
with open(working_file, 'w') as f:
|
with open(working_file, 'w') as f:
|
||||||
f.write("\n".join(converted))
|
f.write("\n".join(converted))
|
||||||
for rule in hcatRules:
|
for rule in hcatRules:
|
||||||
|
rule_path = get_rule_path(rule)
|
||||||
hcatProcess = subprocess.Popen(
|
hcatProcess = subprocess.Popen(
|
||||||
"{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out {hash_file}.working "
|
"{hcatBin} -m {hash_type} {hash_file} --session {session_name} -o {hash_file}.out {hash_file}.working "
|
||||||
"-r {hcatPath}/rules/{rule} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
"-r {rule_path} {tuning} --potfile-path={hate_path}/hashcat.pot".format(
|
||||||
rule=rule,
|
|
||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hash_type=hcatHashType,
|
hash_type=hcatHashType,
|
||||||
hash_file=hcatHashFile,
|
hash_file=hcatHashFile,
|
||||||
session_name=generate_session_id(),
|
session_name=generate_session_id(),
|
||||||
hcatPath=hcatPath,
|
rule_path=rule_path,
|
||||||
tuning=hcatTuning,
|
tuning=hcatTuning,
|
||||||
hate_path=hate_path), shell=True)
|
hate_path=hate_path), shell=True)
|
||||||
try:
|
try:
|
||||||
@@ -1698,7 +1750,11 @@ def pipal():
|
|||||||
print('Killing PID {0}...'.format(str(pipalProcess.pid)))
|
print('Killing PID {0}...'.format(str(pipalProcess.pid)))
|
||||||
pipalProcess.kill()
|
pipalProcess.kill()
|
||||||
print("Pipal file is at " + hcatHashFilePipal + ".pipal\n")
|
print("Pipal file is at " + hcatHashFilePipal + ".pipal\n")
|
||||||
view_choice = input("Would you like to view (cat) the pipal output? (Y/n): ").strip().lower()
|
import sys
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
view_choice = 'y'
|
||||||
|
else:
|
||||||
|
view_choice = input("Would you like to view (cat) the pipal output? (Y/n): ").strip().lower()
|
||||||
if view_choice in ('', 'y', 'yes'):
|
if view_choice in ('', 'y', 'yes'):
|
||||||
print("\n--- Pipal Output Start ---\n")
|
print("\n--- Pipal Output Start ---\n")
|
||||||
with open(hcatHashFilePipal + ".pipal") as pipalfile:
|
with open(hcatHashFilePipal + ".pipal") as pipalfile:
|
||||||
@@ -1802,7 +1858,7 @@ def main():
|
|||||||
global lmHashesFound
|
global lmHashesFound
|
||||||
global debug_mode
|
global debug_mode
|
||||||
global hashview_url, hashview_api_key
|
global hashview_url, hashview_api_key
|
||||||
global hcatPath, hcatBin, hcatWordlists, hcatOptimizedWordlists
|
global hcatPath, hcatBin, hcatWordlists, hcatOptimizedWordlists, rulesDirectory
|
||||||
global pipalPath, maxruntime, bandrelbasewords
|
global pipalPath, maxruntime, bandrelbasewords
|
||||||
|
|
||||||
|
|
||||||
@@ -1818,6 +1874,7 @@ def main():
|
|||||||
parser.add_argument('--weakpass', action='store_true', help='Download wordlists from Weakpass')
|
parser.add_argument('--weakpass', action='store_true', help='Download wordlists from Weakpass')
|
||||||
parser.add_argument('--rank', type=int, default=-1, help='Only show wordlists with this rank (use 0 to show all, default: >4)')
|
parser.add_argument('--rank', type=int, default=-1, help='Only show wordlists with this rank (use 0 to show all, default: >4)')
|
||||||
parser.add_argument('--hashmob', action='store_true', help='Download wordlists from Hashmob.net')
|
parser.add_argument('--hashmob', action='store_true', help='Download wordlists from Hashmob.net')
|
||||||
|
parser.add_argument('--rules', action='store_true', help='Download rules from Hashmob.net')
|
||||||
parser.add_argument('--cleanup', action='store_true', help='Cleanup .out files, torrents, and extract or remove .7z archives')
|
parser.add_argument('--cleanup', action='store_true', help='Cleanup .out files, torrents, and extract or remove .7z archives')
|
||||||
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
||||||
# Removed add_common_args(parser) since config items are now only set via config file
|
# Removed add_common_args(parser) since config items are now only set via config file
|
||||||
@@ -1837,6 +1894,7 @@ def main():
|
|||||||
hcatBin=hcatBin,
|
hcatBin=hcatBin,
|
||||||
hcatWordlists=hcatWordlists,
|
hcatWordlists=hcatWordlists,
|
||||||
hcatOptimizedWordlists=hcatOptimizedWordlists,
|
hcatOptimizedWordlists=hcatOptimizedWordlists,
|
||||||
|
rules_directory=rulesDirectory,
|
||||||
pipalPath=pipalPath,
|
pipalPath=pipalPath,
|
||||||
maxruntime=maxruntime,
|
maxruntime=maxruntime,
|
||||||
bandrelbasewords=bandrelbasewords,
|
bandrelbasewords=bandrelbasewords,
|
||||||
@@ -1848,6 +1906,7 @@ def main():
|
|||||||
hcatBin = config.hcatBin
|
hcatBin = config.hcatBin
|
||||||
hcatWordlists = config.hcatWordlists
|
hcatWordlists = config.hcatWordlists
|
||||||
hcatOptimizedWordlists = config.hcatOptimizedWordlists
|
hcatOptimizedWordlists = config.hcatOptimizedWordlists
|
||||||
|
rulesDirectory = config.rules_directory
|
||||||
pipalPath = config.pipalPath
|
pipalPath = config.pipalPath
|
||||||
maxruntime = config.maxruntime
|
maxruntime = config.maxruntime
|
||||||
bandrelbasewords = config.bandrelbasewords
|
bandrelbasewords = config.bandrelbasewords
|
||||||
@@ -1905,6 +1964,9 @@ def main():
|
|||||||
if args.hashmob:
|
if args.hashmob:
|
||||||
download_hashmob_wordlists(print_fn=print)
|
download_hashmob_wordlists(print_fn=print)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
if args.rules:
|
||||||
|
download_hashmob_rules(print_fn=print)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if args.hashfile and args.hashtype:
|
if args.hashfile and args.hashtype:
|
||||||
hcatHashFile = resolve_path(args.hashfile)
|
hcatHashFile = resolve_path(args.hashfile)
|
||||||
@@ -1923,7 +1985,8 @@ def main():
|
|||||||
print("\t(1) Download hashes from Hashview")
|
print("\t(1) Download hashes from Hashview")
|
||||||
print("\t(2) Download wordlists from Weakpass")
|
print("\t(2) Download wordlists from Weakpass")
|
||||||
print("\t(3) Download wordlists from Hashmob.net")
|
print("\t(3) Download wordlists from Hashmob.net")
|
||||||
print("\t(4) Exit")
|
print("\t(4) Download rules from Hashmob.net")
|
||||||
|
print("\t(5) Exit")
|
||||||
choice = input("\nSelect an option: ")
|
choice = input("\nSelect an option: ")
|
||||||
if choice == '1' or args.download_hashview:
|
if choice == '1' or args.download_hashview:
|
||||||
if not hashview_api_key:
|
if not hashview_api_key:
|
||||||
@@ -1950,6 +2013,9 @@ def main():
|
|||||||
elif choice == '3' or args.hashmob:
|
elif choice == '3' or args.hashmob:
|
||||||
download_hashmob_wordlists(print_fn=print)
|
download_hashmob_wordlists(print_fn=print)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
elif choice == '4' or args.rules:
|
||||||
|
download_hashmob_rules(print_fn=print)
|
||||||
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
@@ -2031,6 +2097,7 @@ def main():
|
|||||||
"11": middle_combinator,
|
"11": middle_combinator,
|
||||||
"12": thorough_combinator,
|
"12": thorough_combinator,
|
||||||
"13": bandrel_method,
|
"13": bandrel_method,
|
||||||
|
"91": download_hashmob_rules,
|
||||||
"92": weakpass_wordlist_menu,
|
"92": weakpass_wordlist_menu,
|
||||||
"93": download_hashmob_wordlists,
|
"93": download_hashmob_wordlists,
|
||||||
"94": weakpass_wordlist_menu,
|
"94": weakpass_wordlist_menu,
|
||||||
@@ -2055,6 +2122,7 @@ def main():
|
|||||||
print("\t(11) Middle Combinator Attack")
|
print("\t(11) Middle Combinator Attack")
|
||||||
print("\t(12) Thorough Combinator Attack")
|
print("\t(12) Thorough Combinator Attack")
|
||||||
print("\t(13) Bandrel Methodology")
|
print("\t(13) Bandrel Methodology")
|
||||||
|
print("\n\t(91) Download rules from Hashmob.net")
|
||||||
print("\n\t(92) Download wordlists from Weakpass")
|
print("\n\t(92) Download wordlists from Weakpass")
|
||||||
print("\t(93) Download wordlists from Hashmob.net")
|
print("\t(93) Download wordlists from Hashmob.net")
|
||||||
print("\t(94) Weakpass Wordlist Menu")
|
print("\t(94) Weakpass Wordlist Menu")
|
||||||
|
|||||||
+250
-39
@@ -57,6 +57,31 @@ def get_hcat_wordlists_dir():
|
|||||||
os.makedirs(default, exist_ok=True)
|
os.makedirs(default, exist_ok=True)
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
def get_rules_dir():
|
||||||
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
project_root = os.path.abspath(os.path.join(pkg_dir, os.pardir))
|
||||||
|
candidates = [
|
||||||
|
os.path.join(pkg_dir, 'config.json'),
|
||||||
|
os.path.join(project_root, 'config.json')
|
||||||
|
]
|
||||||
|
default = os.path.join(project_root, 'rules')
|
||||||
|
for config_path in candidates:
|
||||||
|
try:
|
||||||
|
if os.path.isfile(config_path):
|
||||||
|
with open(config_path) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
path = config.get('rules_directory')
|
||||||
|
if path:
|
||||||
|
path = os.path.expanduser(path)
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
path = os.path.join(project_root, path)
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
return path
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
os.makedirs(default, exist_ok=True)
|
||||||
|
return default
|
||||||
|
|
||||||
def cleanup_torrent_files(directory=None):
|
def cleanup_torrent_files(directory=None):
|
||||||
"""Remove stray .torrent files from the wordlists directory on graceful exit."""
|
"""Remove stray .torrent files from the wordlists directory on graceful exit."""
|
||||||
if directory is None:
|
if directory is None:
|
||||||
@@ -165,9 +190,27 @@ def download_torrent_file(torrent_url, save_dir=None, wordlist_id=None):
|
|||||||
if not os.path.isabs(save_dir):
|
if not os.path.isabs(save_dir):
|
||||||
save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), save_dir)
|
save_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), save_dir)
|
||||||
os.makedirs(save_dir, exist_ok=True)
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
# Optionally include hashmob_api_key in headers if present
|
||||||
headers = {
|
headers = {
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
hashmob_api_key = None
|
||||||
|
# Try to get hashmob_api_key from config
|
||||||
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
project_root = os.path.abspath(os.path.join(pkg_dir, os.pardir))
|
||||||
|
for cfg in (os.path.join(pkg_dir, 'config.json'), os.path.join(project_root, 'config.json')):
|
||||||
|
if os.path.isfile(cfg):
|
||||||
|
try:
|
||||||
|
with open(cfg) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
key = config.get('hashmob_api_key')
|
||||||
|
if key:
|
||||||
|
hashmob_api_key = key
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if hashmob_api_key:
|
||||||
|
headers["api-key"] = hashmob_api_key
|
||||||
|
|
||||||
# Resolve a filename even if a URL is provided.
|
# Resolve a filename even if a URL is provided.
|
||||||
if not torrent_url.startswith("http"):
|
if not torrent_url.startswith("http"):
|
||||||
@@ -759,47 +802,136 @@ def download_hashmob_wordlist(file_name, out_path):
|
|||||||
url = f"https://hashmob.net/api/v2/downloads/research/wordlists/{file_name}"
|
url = f"https://hashmob.net/api/v2/downloads/research/wordlists/{file_name}"
|
||||||
api_key = get_hashmob_api_key()
|
api_key = get_hashmob_api_key()
|
||||||
headers = {"api-key": api_key} if api_key else {}
|
headers = {"api-key": api_key} if api_key else {}
|
||||||
|
import time
|
||||||
|
base_backoff = 256
|
||||||
|
max_backoff = 300
|
||||||
|
penalty_add = 2
|
||||||
|
penalty = base_backoff
|
||||||
|
import threading
|
||||||
|
lock = getattr(download_hashmob_wordlist, '_rate_lock', None)
|
||||||
|
if lock is None:
|
||||||
|
lock = threading.Lock()
|
||||||
|
download_hashmob_wordlist._rate_lock = lock
|
||||||
|
while True:
|
||||||
|
with lock:
|
||||||
|
time.sleep(15)
|
||||||
|
try:
|
||||||
|
with requests.get(url, headers=headers, stream=True, timeout=60, allow_redirects=True) as r:
|
||||||
|
if r.status_code == 429:
|
||||||
|
print(f"[!] Rate limit hit (429). Backing off for {penalty} seconds...")
|
||||||
|
time.sleep(penalty)
|
||||||
|
penalty = min(penalty + penalty_add, max_backoff)
|
||||||
|
penalty_add *= 2
|
||||||
|
continue
|
||||||
|
if r.status_code in (301, 302, 303, 307, 308):
|
||||||
|
redirect_url = r.headers.get('Location')
|
||||||
|
if redirect_url:
|
||||||
|
print(f"Following redirect to: {redirect_url}")
|
||||||
|
return download_hashmob_wordlist(redirect_url, out_path)
|
||||||
|
print("Redirect with no Location header!")
|
||||||
|
return False
|
||||||
|
r.raise_for_status()
|
||||||
|
content_type = r.headers.get('Content-Type', '')
|
||||||
|
if 'text/plain' in content_type:
|
||||||
|
html = r.content.decode(errors='replace')
|
||||||
|
import re
|
||||||
|
match = re.search(
|
||||||
|
r"<meta[^>]+http-equiv=['\"]refresh['\"][^>]+content=['\"]0;url=([^'\"]+)['\"]",
|
||||||
|
html,
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
if match:
|
||||||
|
real_url = match.group(1)
|
||||||
|
print(f"Found meta refresh redirect to: {real_url}")
|
||||||
|
with requests.get(real_url, stream=True, timeout=120) as r2:
|
||||||
|
r2.raise_for_status()
|
||||||
|
with open(out_path, 'wb') as f:
|
||||||
|
for chunk in r2.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
print(f"Downloaded {out_path}")
|
||||||
|
return True
|
||||||
|
print("Error: Received HTML instead of file. Possible permission or quota issue.")
|
||||||
|
return False
|
||||||
|
with open(out_path, 'wb') as f:
|
||||||
|
for chunk in r.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
print(f"Downloaded {out_path}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
if hasattr(e, 'response') and getattr(e.response, 'status_code', None) == 429:
|
||||||
|
print(f"[!] Rate limit hit (429). Backing off for {penalty} seconds...")
|
||||||
|
time.sleep(penalty)
|
||||||
|
penalty = min(penalty + penalty_add, max_backoff)
|
||||||
|
penalty_add *= 2
|
||||||
|
continue
|
||||||
|
print(f"Error downloading wordlist: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def download_hashmob_rule_list():
|
||||||
|
"""Fetch available rules from Hashmob API v2 and print them."""
|
||||||
|
url = "https://hashmob.net/api/v2/resource"
|
||||||
|
api_key = get_hashmob_api_key()
|
||||||
|
headers = {"api-key": api_key} if api_key else {}
|
||||||
try:
|
try:
|
||||||
with requests.get(url, headers=headers, stream=True, timeout=60, allow_redirects=True) as r:
|
resp = requests.get(url, headers=headers, timeout=30)
|
||||||
if r.status_code in (301, 302, 303, 307, 308):
|
resp.raise_for_status()
|
||||||
redirect_url = r.headers.get('Location')
|
data = resp.json()
|
||||||
if redirect_url:
|
rules = [r for r in data if r.get('type') in ('rule', 'official_rule')]
|
||||||
print(f"Following redirect to: {redirect_url}")
|
print("Available Hashmob Rules:")
|
||||||
return download_hashmob_wordlist(redirect_url, out_path)
|
for idx, rule in enumerate(rules):
|
||||||
print("Redirect with no Location header!")
|
print(f"{idx+1}. {rule.get('name', rule.get('file_name', ''))}")
|
||||||
return False
|
return rules
|
||||||
r.raise_for_status()
|
|
||||||
content_type = r.headers.get('Content-Type', '')
|
|
||||||
if 'text/plain' in content_type:
|
|
||||||
html = r.content.decode(errors='replace')
|
|
||||||
import re
|
|
||||||
match = re.search(
|
|
||||||
r"<meta[^>]+http-equiv=['\"]refresh['\"][^>]+content=['\"]0;url=([^'\"]+)['\"]",
|
|
||||||
html,
|
|
||||||
re.IGNORECASE
|
|
||||||
)
|
|
||||||
if match:
|
|
||||||
real_url = match.group(1)
|
|
||||||
print(f"Found meta refresh redirect to: {real_url}")
|
|
||||||
with requests.get(real_url, stream=True, timeout=120) as r2:
|
|
||||||
r2.raise_for_status()
|
|
||||||
with open(out_path, 'wb') as f:
|
|
||||||
for chunk in r2.iter_content(chunk_size=8192):
|
|
||||||
if chunk:
|
|
||||||
f.write(chunk)
|
|
||||||
print(f"Downloaded {out_path}")
|
|
||||||
return True
|
|
||||||
print("Error: Received HTML instead of file. Possible permission or quota issue.")
|
|
||||||
return False
|
|
||||||
with open(out_path, 'wb') as f:
|
|
||||||
for chunk in r.iter_content(chunk_size=8192):
|
|
||||||
if chunk:
|
|
||||||
f.write(chunk)
|
|
||||||
print(f"Downloaded {out_path}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error downloading wordlist: {e}")
|
print(f"Error fetching Hashmob rules: {e}")
|
||||||
return False
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def download_hashmob_rule(file_name, out_path):
|
||||||
|
"""Download a rule file from Hashmob by file name."""
|
||||||
|
url = f"https://hashmob.net/api/v2/downloads/research/official/hashmob_rules/{file_name}"
|
||||||
|
api_key = get_hashmob_api_key()
|
||||||
|
headers = {"api-key": api_key} if api_key else {}
|
||||||
|
import time
|
||||||
|
base_backoff = 256
|
||||||
|
max_backoff = 300
|
||||||
|
penalty_add = 2
|
||||||
|
penalty = base_backoff
|
||||||
|
import threading
|
||||||
|
lock = getattr(download_hashmob_rule, '_rate_lock', None)
|
||||||
|
if lock is None:
|
||||||
|
lock = threading.Lock()
|
||||||
|
download_hashmob_rule._rate_lock = lock
|
||||||
|
while True:
|
||||||
|
with lock:
|
||||||
|
time.sleep(15)
|
||||||
|
try:
|
||||||
|
with requests.get(url, headers=headers, stream=True, timeout=60, allow_redirects=True) as r:
|
||||||
|
if r.status_code == 429:
|
||||||
|
print(f"[!] Rate limit hit (429). Backing off for {penalty} seconds...")
|
||||||
|
time.sleep(penalty)
|
||||||
|
penalty = min(penalty + penalty_add, max_backoff)
|
||||||
|
penalty_add *= 2
|
||||||
|
continue
|
||||||
|
r.raise_for_status()
|
||||||
|
with open(out_path, 'wb') as f:
|
||||||
|
for chunk in r.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
print(f"Downloaded {out_path}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
# If it's a 429 error, handle backoff, else fail
|
||||||
|
if hasattr(e, 'response') and getattr(e.response, 'status_code', None) == 429:
|
||||||
|
print(f"[!] Rate limit hit (429). Backing off for {penalty} seconds...")
|
||||||
|
time.sleep(penalty)
|
||||||
|
penalty = min(penalty + penalty_add, max_backoff)
|
||||||
|
penalty_add *= 2
|
||||||
|
continue
|
||||||
|
print(f"Error downloading rule: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def list_official_wordlists():
|
def list_official_wordlists():
|
||||||
@@ -898,6 +1030,79 @@ def list_and_download_official_wordlists():
|
|||||||
print(f"Error listing official wordlists: {e}")
|
print(f"Error listing official wordlists: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def list_and_download_hashmob_rules():
|
||||||
|
"""List rules via the Hashmob API, prompt for selection, and download."""
|
||||||
|
rules = download_hashmob_rule_list()
|
||||||
|
if not rules:
|
||||||
|
return
|
||||||
|
print("a. Download ALL files")
|
||||||
|
sel = input("Enter the number(s) to download (e.g. 1,3,5-7), or 'a' for all, or 'q' to quit: ")
|
||||||
|
if sel.lower() == 'q':
|
||||||
|
return
|
||||||
|
rules_dir = get_rules_dir()
|
||||||
|
|
||||||
|
def parse_indices(selection, max_index):
|
||||||
|
indices = set()
|
||||||
|
for part in selection.split(','):
|
||||||
|
part = part.strip()
|
||||||
|
if not part:
|
||||||
|
continue
|
||||||
|
if '-' in part:
|
||||||
|
try:
|
||||||
|
start, end = map(int, part.split('-', 1))
|
||||||
|
if start > end:
|
||||||
|
start, end = end, start
|
||||||
|
indices.update(range(start, end + 1))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
indices.add(int(part))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return sorted(i for i in indices if 1 <= i <= max_index)
|
||||||
|
|
||||||
|
# Track already-downloaded rules to avoid duplicates
|
||||||
|
downloaded_rules = set()
|
||||||
|
# Scan rules_dir for existing files
|
||||||
|
if os.path.isdir(rules_dir):
|
||||||
|
for fname in os.listdir(rules_dir):
|
||||||
|
downloaded_rules.add(fname)
|
||||||
|
|
||||||
|
def already_downloaded(file_name):
|
||||||
|
sanitized = sanitize_filename(file_name)
|
||||||
|
return sanitized in downloaded_rules
|
||||||
|
|
||||||
|
if sel.lower() == 'a':
|
||||||
|
for entry in rules:
|
||||||
|
file_name = entry.get('file_name')
|
||||||
|
if not file_name:
|
||||||
|
print("No file_name found for an entry, skipping.")
|
||||||
|
continue
|
||||||
|
out_path = os.path.join(rules_dir, sanitize_filename(file_name))
|
||||||
|
if already_downloaded(file_name):
|
||||||
|
print(f"[i] Skipping already downloaded rule: {file_name}")
|
||||||
|
continue
|
||||||
|
download_hashmob_rule(file_name, out_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
indices = parse_indices(sel, len(rules))
|
||||||
|
if not indices:
|
||||||
|
print("No valid selection.")
|
||||||
|
return
|
||||||
|
for idx in indices:
|
||||||
|
entry = rules[idx - 1]
|
||||||
|
file_name = entry.get('file_name')
|
||||||
|
if not file_name:
|
||||||
|
print("No file_name found for selection, skipping.")
|
||||||
|
continue
|
||||||
|
out_path = os.path.join(rules_dir, sanitize_filename(file_name))
|
||||||
|
if already_downloaded(file_name):
|
||||||
|
print(f"[i] Skipping already downloaded rule: {file_name}")
|
||||||
|
continue
|
||||||
|
download_hashmob_rule(file_name, out_path)
|
||||||
|
|
||||||
|
|
||||||
def download_official_wordlist(file_name, out_path):
|
def download_official_wordlist(file_name, out_path):
|
||||||
"""Download a file from the official wordlists directory with a progress bar."""
|
"""Download a file from the official wordlists directory with a progress bar."""
|
||||||
import sys
|
import sys
|
||||||
@@ -995,6 +1200,12 @@ def download_hashmob_wordlists(print_fn=print) -> None:
|
|||||||
print_fn("Hashmob wordlist download complete.")
|
print_fn("Hashmob wordlist download complete.")
|
||||||
|
|
||||||
|
|
||||||
|
def download_hashmob_rules(print_fn=print) -> None:
|
||||||
|
"""Download Hashmob rules."""
|
||||||
|
list_and_download_hashmob_rules()
|
||||||
|
print_fn("Hashmob rules download complete.")
|
||||||
|
|
||||||
|
|
||||||
def download_weakpass_torrent(download_torrent, filename: str, print_fn=print) -> None:
|
def download_weakpass_torrent(download_torrent, filename: str, print_fn=print) -> None:
|
||||||
"""Download a single Weakpass torrent file by name or URL."""
|
"""Download a single Weakpass torrent file by name or URL."""
|
||||||
print_fn(f"[i] Downloading: {filename}")
|
print_fn(f"[i] Downloading: {filename}")
|
||||||
|
|||||||
Reference in New Issue
Block a user