README.md
Rendering markdown...
#!/usr/bin/env python3
"""
=============================================================================
WordPress 6.8+ Automated Hash Cracker
=============================================================================
Author: [n3fhara]
Date: 2026-03-18
Version: 1.0
Description:
------------
This script automates the offline cracking process for the new WordPress 6.8+
password hashes. The new format ("$wp$...") involves a complex pipeline:
1. PHP trim() simulation.
2. HMAC-SHA384 pre-hashing using the salt 'wp-sha384'.
3. Base64 encoding.
4. Bcrypt hashing.
This script bridges the gap between raw wordlists and Hashcat by:
- Normalizing the extracted WordPress hash to a standard Bcrypt format.
- Pre-computing the HMAC-SHA384 stage for the entire wordlist.
- Executing Hashcat (Mode 3200) transparently in the background.
- Mapping the recovered pre-hash back to the plaintext password.
Requirements:
-------------
- Python 3.6+
- Hashcat installed and available in the system PATH.
Usage:
------
python3 Auto_Crack.py -H '\$wp\$2y\$10\$zPA8xGJMvr.kvAAIIYaCreIMnTDpgw/9o8K7.ONBm/KBbNIywQtu.' -w /usr/share/wordlists/rockyou.txt
=============================================================================
"""
import hmac
import hashlib
import base64
import subprocess
import sys
import os
import argparse
import shutil
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Automated Offline Cracker for WordPress 6.8+ Hashes ($wp$2y$10$...)",
formatter_class=argparse.RawTextHelpHelpFormatter
)
# Required arguments
parser.add_argument("-H", "--hash", required=True,
help="The target WordPress hash.\nExample: '$wp$2y$10$...'")
parser.add_argument("-w", "--wordlist", required=True,
help="Path to the cleartext wordlist (e.g., /usr/share/wordlists/rockyou.txt)")
# Optional arguments for advanced configuration
parser.add_argument("-O", "--output-dir", default="./wp_crack_temp",
help="Directory to store temporary dictionaries (default: ./wp_crack_temp)")
parser.add_argument("--keep-files", action="store_true",
help="Do not delete temporary files after cracking finishes")
return parser.parse_args()
def prepare_environment(output_dir):
"""
Ensure the temporary workspace exists.
Args:
output_dir (str): Path to the temporary directory.
"""
if not os.path.exists(output_dir):
try:
os.makedirs(output_dir)
except OSError as e:
print(f"[-] Error creating directory '{output_dir}': {e}")
sys.exit(1)
def format_target_hash(raw_hash, target_file):
"""
Remove the custom '$wp' prefix to generate a valid bcrypt hash for Hashcat.
Args:
raw_hash (str): The raw hash extracted from the database.
target_file (str): File path to store the cleaned hash.
Returns:
str: The cleaned hash string.
"""
if raw_hash.startswith("$wp"):
clean_hash = raw_hash[3:]
else:
clean_hash = raw_hash
print("[!] Warning: Hash does not start with '$wp'. Ensure it is a valid WP 6.8+ hash.")
try:
with open(target_file, "w", encoding="utf-8") as f:
f.write(clean_hash + "\n")
except IOError as e:
print(f"[-] Error writing target hash file: {e}")
sys.exit(1)
return clean_hash
def prehash_wordlist(input_dict, output_dict, mapping_file):
"""
Apply the exact WordPress 6.8 cryptographic logic to the wordlist.
Args:
input_dict (str): Path to the raw wordlist.
output_dict (str): Path to output the Base64(HMAC-SHA384) strings.
mapping_file (str): Path to output the mapping (prehash:plaintext).
"""
if not os.path.isfile(input_dict):
print(f"[-] Error: Wordlist '{input_dict}' not found or inaccessible.")
sys.exit(1)
print("[*] Generating pre-hashed wordlist (HMAC-SHA384 + Base64)...")
try:
with open(input_dict, 'r', encoding='latin-1') as f_in, \
open(output_dict, 'w', encoding='utf-8') as f_out, \
open(mapping_file, 'w', encoding='utf-8') as f_map:
for line in f_in:
original_password = line.strip('\n')
# 1. Simulate PHP's trim() function
trimmed_password = original_password.strip(' \t\n\r\x0b\x0c')
# 2. WordPress 6.8 HMAC-SHA384 logic using 'wp-sha384' salt
hmac_obj = hmac.new(b'wp-sha384', trimmed_password.encode('utf-8'), digestmod=hashlib.sha384)
digest = hmac_obj.digest()
# 3. Base64 encoding
prehashed = base64.b64encode(digest).decode('utf-8')
f_out.write(prehashed + '\n')
f_map.write(f"{prehashed}:{original_password}\n")
except Exception as e:
print(f"[-] Error during dictionary pre-hashing: {e}")
sys.exit(1)
def run_hashcat(target_file, prehashed_dict, clean_hash):
"""
Execute Hashcat in a subprocess and parse its output.
Args:
target_file (str): File containing the target bcrypt hash.
prehashed_dict (str): File containing the pre-hashed wordlist.
clean_hash (str): The string value of the target hash.
Returns:
str or None: The cracked pre-hash string if found, otherwise None.
"""
print("[*] Launching Hashcat (Mode 3200) in the background. This may take a while...")
cracked_prehash = None
# Step 1: Check if the hash was already cracked in a previous session (Potfile check)
check_cmd = ["hashcat", "-m", "3200", target_file, "--show"]
try:
check_result = subprocess.run(check_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
except FileNotFoundError:
print("[-] Error: 'hashcat' command not found. Ensure it is installed and in your system PATH.")
sys.exit(1)
if clean_hash in check_result.stdout:
print("[+] Hash already found in Hashcat potfile!")
for line in check_result.stdout.split('\n'):
if clean_hash in line:
# Extract the pre-hash portion after the colon separator
cracked_prehash = line.split(':')[-1].strip()
break
else:
# Step 2: Execute actual dictionary attack
cmd = ["hashcat", "-m", "3200", "-a", "0", target_file, prehashed_dict, "--quiet"]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Hashcat returns 0 on success or sometimes 1 if exhausted. We rely on the output content.
if result.returncode == 0 or "Cracked" in result.stdout:
print("[+] Hashcat successfully cracked the target hash!")
# Re-run the --show command to properly extract the found password
show_result = subprocess.run(check_cmd, stdout=subprocess.PIPE, text=True)
for line in show_result.stdout.split('\n'):
if clean_hash in line:
cracked_prehash = line.split(':')[-1].strip()
break
else:
print("[-] Hashcat exhausted the wordlist. Password not found.")
# Normal exit, but no success
return None
return cracked_prehash
def reverse_map_password(cracked_prehash, mapping_file):
"""
Resolve the plaintext password from the mapping file using the cracked pre-hash.
Args:
cracked_prehash (str): The pre-hash recovered by Hashcat.
mapping_file (str): The file containing the prehash-to-plaintext relationship.
"""
print("[*] Resolving plaintext password from mapping file...")
try:
with open(mapping_file, 'r', encoding='utf-8') as f_map:
for line in f_map:
if line.startswith(cracked_prehash + ":"):
# Split only on the first colon to avoid breaking passwords containing colons
clear_password = line.split(':', 1)[1].strip('\n')
print("\n" + "=" * 50)
print(f"[+] SUCCESS! PASSWORD: {clear_password}")
print("=" * 50 + "\n")
return
print("[-] Error: Pre-hash found by Hashcat but not present in the mapping file.")
except Exception as e:
print(f"[-] Error reading mapping file: {e}")
def cleanup(output_dir):
"""Safely remove the temporary working directory."""
print("[*] Cleaning up temporary files...")
try:
shutil.rmtree(output_dir)
except OSError as e:
print(f"[-] Warning: Could not delete temporary directory: {e}")
def main():
"""Main execution flow."""
args = parse_args()
# Define temporary file paths
prepare_environment(args.output_dir)
target_file = os.path.join(args.output_dir, "target.hash")
prehashed_dict = os.path.join(args.output_dir, "prehashed.dict")
mapping_file = os.path.join(args.output_dir, "mapping.txt")
try:
# Execution Pipeline
clean_hash = format_target_hash(args.hash, target_file)
prehash_wordlist(args.wordlist, prehashed_dict, mapping_file)
cracked_prehash = run_hashcat(target_file, prehashed_dict, clean_hash)
if cracked_prehash:
reverse_map_password(cracked_prehash, mapping_file)
except KeyboardInterrupt:
print("\n\n[!] Process aborted by user.")
sys.exit(130)
finally:
# Guarantee cleanup even if an error occurs (unless --keep-files is used)
if not args.keep_files:
cleanup(args.output_dir)
else:
print(f"[*] Temporary files kept in '{args.output_dir}'")
if __name__ == "__main__":
main()