#!/usr/bin/env python3 """ Extract Warden RSA public key modulus from WoW 3.3.5a executable. The RSA-2048 public key consists of: - Exponent: 0x010001 (65537) - always the same - Modulus: 256 bytes - hardcoded in WoW.exe This script parses the PE structure and searches only in data sections to avoid finding x86 code instead of the actual cryptographic key. """ import sys import struct class PEParser: """Simple PE32 executable parser""" def __init__(self, data): self.data = data self.sections = [] self.parse() def parse(self): """Parse PE headers and section table""" # Check DOS signature if self.data[:2] != b'MZ': raise ValueError("Not a valid PE file (missing MZ signature)") # Get offset to PE header (at 0x3C in DOS header) pe_offset = struct.unpack(' 0: p = count / len(data) entropy -= p * math.log2(p) return entropy def is_likely_rsa_modulus(data): """ Apply heuristics to determine if data looks like an RSA modulus RSA modulus characteristics: - 256 bytes exactly - High entropy (appears random) - High bit of MSB typically set (> 0x80) - Not all zeros or repetitive patterns - No obvious x86 instruction sequences - No sequential byte patterns """ if len(data) != 256: return False # Check entropy (should be > 7.5 for cryptographic data) entropy = calculate_entropy(data) if entropy < 7.0: return False # Check for non-zero bytes non_zero = sum(1 for b in data if b != 0) if non_zero < 240: # At least 93% non-zero return False # Check byte variety unique_bytes = len(set(data)) if unique_bytes < 120: # At least 120 different byte values return False # Check for sequential patterns (e.g., 0x81, 0x82, 0x83, ...) # Real RSA modulus should NOT have long sequential runs max_sequential = 0 current_sequential = 1 for i in range(1, len(data)): if data[i] == (data[i-1] + 1) % 256: current_sequential += 1 max_sequential = max(max_sequential, current_sequential) else: current_sequential = 1 if max_sequential > 8: # More than 8 consecutive sequential bytes is suspicious return False # Check for repetitive patterns (same byte repeated) max_repetition = 0 current_repetition = 1 for i in range(1, len(data)): if data[i] == data[i-1]: current_repetition += 1 max_repetition = max(max_repetition, current_repetition) else: current_repetition = 1 if max_repetition > 4: # More than 4 identical bytes in a row is suspicious return False # Check for x86 code patterns (common instruction bytes) # MOV: 0x8B, 0x89, 0x88, 0x8A # PUSH: 0x50-0x57 # POP: 0x58-0x5F # Common prologue: 0x55 (PUSH EBP), 0x8B, 0xEC (MOV EBP, ESP) code_patterns = [ b'\x55\x8B\xEC', # Standard function prologue b'\x8B\x44\x24', # MOV EAX, [ESP+...] b'\x8B\x4C\x24', # MOV ECX, [ESP+...] b'\xFF\x15', # CALL [...] b'\xE8', # CALL relative ] for pattern in code_patterns: if pattern in data[:64]: # Check first 64 bytes return False # MSB should have high bit set (typical for RSA modulus) # In little-endian, this would be the LAST byte if data[-1] < 0x80: return False return True def find_warden_modulus(exe_path): """ Find Warden RSA modulus in WoW.exe by parsing PE structure and searching only in data sections. """ with open(exe_path, 'rb') as f: data = f.read() print(f"[*] Loaded {len(data)} bytes from {exe_path}") # Parse PE structure try: pe = PEParser(data) print(f"[*] Found {len(pe.sections)} PE sections") except Exception as e: print(f"[!] Failed to parse PE: {e}") return None # Get data sections data_sections = pe.get_data_sections() print(f"[*] Identified {len(data_sections)} data sections:") for sec in data_sections: print(f" {sec['name']:8} - offset 0x{sec['raw_offset']:08x}, size {sec['raw_size']:8} bytes") # Search for RSA exponent in data sections only exponent_pattern = b'\x01\x00\x01\x00' candidates = [] for sec in data_sections: section_data = data[sec['raw_offset']:sec['raw_offset'] + sec['raw_size']] # Find exponent pattern in this section offset = 0 while True: offset = section_data.find(exponent_pattern, offset) if offset == -1: break file_offset = sec['raw_offset'] + offset print(f"\n[*] Found exponent pattern at 0x{file_offset:08x} (section {sec['name']})") # Search for 256-byte modulus near this exponent # Try before and after the exponent search_range = 1024 start = max(0, offset - search_range) end = min(len(section_data), offset + search_range) for mod_offset in range(start, end): if mod_offset + 256 > len(section_data): break modulus_candidate = section_data[mod_offset:mod_offset + 256] if is_likely_rsa_modulus(modulus_candidate): file_mod_offset = sec['raw_offset'] + mod_offset entropy = calculate_entropy(modulus_candidate) candidates.append({ 'offset': file_mod_offset, 'section': sec['name'], 'data': modulus_candidate, 'entropy': entropy, 'exponent_offset': file_offset }) offset += 1 # Sort candidates by entropy (higher is better) candidates.sort(key=lambda x: x['entropy'], reverse=True) if not candidates: print("\n[!] No RSA modulus candidates found") print("[!] The modulus might be obfuscated or in an unexpected format") return None print(f"\n[*] Found {len(candidates)} RSA modulus candidate(s)") for i, cand in enumerate(candidates[:3]): # Show top 3 print(f"\n{'='*70}") print(f"[+] Candidate #{i+1}") print(f" File offset: 0x{cand['offset']:08x}") print(f" Section: {cand['section']}") print(f" Entropy: {cand['entropy']:.3f} bits/byte") print(f" Near exponent at: 0x{cand['exponent_offset']:08x}") print(f" First 32 bytes: {cand['data'][:32].hex()}") print(f" Last 32 bytes: {cand['data'][-32:].hex()}") if i == 0: print(f"\n[*] C++ array format (BEST CANDIDATE):") print_cpp_array(cand['data']) return candidates[0]['data'] if candidates else None def print_cpp_array(data): """Print byte array in C++ format""" print("const uint8_t modulus[256] = {") for i in range(0, 256, 16): chunk = data[i:i+16] hex_bytes = ', '.join(f'0x{b:02X}' for b in chunk) comma = ',' if i < 240 else '' print(f" {hex_bytes}{comma}") print("};") if __name__ == '__main__': if len(sys.argv) != 2: print(f"Usage: {sys.argv[0]} ") sys.exit(1) exe_path = sys.argv[1] modulus = find_warden_modulus(exe_path) if modulus: print(f"\n[✓] Successfully extracted RSA modulus!") print(f"[*] Copy the C++ array above into warden_module.cpp") else: print(f"\n[✗] Failed to extract RSA modulus") sys.exit(1)