#!/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()