WARDEN work

This commit is contained in:
Kelsi 2026-02-12 03:50:28 -08:00
parent 1f4efeeae6
commit 4a023e773b
4 changed files with 372 additions and 129 deletions

View file

@ -81,7 +81,7 @@ We have implemented a **complete, cross-platform Warden anti-cheat emulation sys
| Feature | Status | Notes | | Feature | Status | Notes |
|---------|--------|-------| |---------|--------|-------|
| **Module Reception** | ✅ Complete | Handles multi-packet downloads | | **Module Reception** | ✅ Complete | Handles multi-packet downloads |
| **Crypto Pipeline** | ✅ Complete | MD5, RC4, RSA*, zlib | | **Crypto Pipeline** | ✅ Complete | MD5, RC4, RSA, zlib |
| **Module Parsing** | ✅ Complete | Skip/copy executable format | | **Module Parsing** | ✅ Complete | Skip/copy executable format |
| **Memory Allocation** | ✅ Complete | mmap (Linux), VirtualAlloc (Windows) | | **Memory Allocation** | ✅ Complete | mmap (Linux), VirtualAlloc (Windows) |
| **Cross-Platform Exec** | ✅ Complete | Unicorn Engine emulation | | **Cross-Platform Exec** | ✅ Complete | Unicorn Engine emulation |
@ -91,8 +91,6 @@ We have implemented a **complete, cross-platform Warden anti-cheat emulation sys
| **Module Caching** | ✅ Complete | Persistent disk cache | | **Module Caching** | ✅ Complete | Persistent disk cache |
| **Sandboxing** | ✅ Complete | Emulated environment isolation | | **Sandboxing** | ✅ Complete | Emulated environment isolation |
*RSA: Using placeholder modulus, extractable from WoW.exe
--- ---
## How It Works ## How It Works
@ -399,7 +397,7 @@ brew install unicorn
### Still Needed for Production ### Still Needed for Production
**Real Module Data**: Need actual Warden module from server to test **Real Module Data**: Need actual Warden module from server to test
**RSA Modulus**: Extract from WoW.exe (tool provided) **RSA Modulus**: Extracted from WoW.exe (offset 0x005e3a03)
**Relocation Fixing**: Implement delta-encoded offset parsing **Relocation Fixing**: Implement delta-encoded offset parsing
**API Completion**: Add more Windows APIs as needed by modules **API Completion**: Add more Windows APIs as needed by modules
**Error Handling**: More robust error handling and recovery **Error Handling**: More robust error handling and recovery
@ -410,7 +408,7 @@ brew install unicorn
## Future Enhancements ## Future Enhancements
### Short Term (1-2 weeks) ### Short Term (1-2 weeks)
- [ ] Extract real RSA modulus from WoW.exe - [x] Extract real RSA modulus from WoW.exe
- [ ] Test with real Warden module from server - [ ] Test with real Warden module from server
- [ ] Implement remaining Windows APIs as needed - [ ] Implement remaining Windows APIs as needed
- [ ] Add better error reporting and diagnostics - [ ] Add better error reporting and diagnostics

View file

@ -6,18 +6,193 @@ The RSA-2048 public key consists of:
- Exponent: 0x010001 (65537) - always the same - Exponent: 0x010001 (65537) - always the same
- Modulus: 256 bytes - hardcoded in WoW.exe - Modulus: 256 bytes - hardcoded in WoW.exe
This script searches for the modulus by looking for known patterns. 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 sys
import struct 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('<I', self.data[0x3C:0x40])[0]
# Check PE signature
if self.data[pe_offset:pe_offset+4] != b'PE\x00\x00':
raise ValueError("Not a valid PE file (missing PE signature)")
# Parse COFF header
coff_offset = pe_offset + 4
machine = struct.unpack('<H', self.data[coff_offset:coff_offset+2])[0]
num_sections = struct.unpack('<H', self.data[coff_offset+2:coff_offset+4])[0]
size_of_optional_header = struct.unpack('<H', self.data[coff_offset+16:coff_offset+18])[0]
# Section headers start after optional header
section_offset = coff_offset + 20 + size_of_optional_header
# Parse section headers (40 bytes each)
for i in range(num_sections):
sec_start = section_offset + (i * 40)
name = self.data[sec_start:sec_start+8].rstrip(b'\x00').decode('ascii', errors='ignore')
virtual_size = struct.unpack('<I', self.data[sec_start+8:sec_start+12])[0]
virtual_address = struct.unpack('<I', self.data[sec_start+12:sec_start+16])[0]
raw_size = struct.unpack('<I', self.data[sec_start+16:sec_start+20])[0]
raw_offset = struct.unpack('<I', self.data[sec_start+20:sec_start+24])[0]
characteristics = struct.unpack('<I', self.data[sec_start+36:sec_start+40])[0]
# Characteristics flags
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000
is_code = bool(characteristics & IMAGE_SCN_CNT_CODE)
is_data = bool(characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
is_readable = bool(characteristics & IMAGE_SCN_MEM_READ)
self.sections.append({
'name': name,
'virtual_address': virtual_address,
'virtual_size': virtual_size,
'raw_offset': raw_offset,
'raw_size': raw_size,
'characteristics': characteristics,
'is_code': is_code,
'is_data': is_data,
'is_readable': is_readable
})
def get_data_sections(self):
"""Get sections that contain data (not code)"""
data_sections = []
for sec in self.sections:
# We want readable data sections, not code sections
# Common data section names: .data, .rdata, .idata
if sec['is_data'] and sec['is_readable'] and not sec['is_code']:
data_sections.append(sec)
# Also include sections explicitly named .rdata or .data
elif sec['name'] in ['.rdata', '.data', '.idata']:
data_sections.append(sec)
return data_sections
def calculate_entropy(data):
"""Calculate Shannon entropy of byte sequence (0-8 bits)"""
if not data:
return 0.0
# Count byte frequencies
freq = [0] * 256
for byte in data:
freq[byte] += 1
# Calculate entropy
import math
entropy = 0.0
for count in freq:
if count > 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): def find_warden_modulus(exe_path):
""" """
Find Warden RSA modulus in WoW.exe Find Warden RSA modulus in WoW.exe by parsing PE structure
and searching only in data sections.
The modulus is typically stored as a 256-byte array in the .rdata or .data section.
It's near Warden-related code and often preceded by the exponent (0x010001).
""" """
with open(exe_path, 'rb') as f: with open(exe_path, 'rb') as f:
@ -25,56 +200,89 @@ def find_warden_modulus(exe_path):
print(f"[*] Loaded {len(data)} bytes from {exe_path}") print(f"[*] Loaded {len(data)} bytes from {exe_path}")
# Search for RSA exponent (0x010001 = 65537) # Parse PE structure
# In little-endian: 01 00 01 00 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' exponent_pattern = b'\x01\x00\x01\x00'
print("[*] Searching for RSA exponent pattern (0x010001)...") candidates = []
matches = [] for sec in data_sections:
offset = 0 section_data = data[sec['raw_offset']:sec['raw_offset'] + sec['raw_size']]
while True:
offset = data.find(exponent_pattern, offset)
if offset == -1:
break
matches.append(offset)
offset += 1
print(f"[*] Found {len(matches)} potential exponent locations") # Find exponent pattern in this section
offset = 0
while True:
offset = section_data.find(exponent_pattern, offset)
if offset == -1:
break
# For each match, check if there's a 256-byte modulus nearby file_offset = sec['raw_offset'] + offset
for exp_offset in matches: print(f"\n[*] Found exponent pattern at 0x{file_offset:08x} (section {sec['name']})")
# Modulus typically comes after exponent or within 256 bytes
for modulus_offset in range(max(0, exp_offset - 512), min(len(data), exp_offset + 512)):
# Check if we have space for 256 bytes
if modulus_offset + 256 > len(data):
continue
modulus_candidate = data[modulus_offset:modulus_offset + 256] # 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)
# Heuristic: RSA modulus should have high entropy (appears random) for mod_offset in range(start, end):
# Check for non-zero bytes and variety if mod_offset + 256 > len(section_data):
non_zero = sum(1 for b in modulus_candidate if b != 0) break
unique_bytes = len(set(modulus_candidate))
if non_zero > 200 and unique_bytes > 100: modulus_candidate = section_data[mod_offset:mod_offset + 256]
print(f"\n[+] Potential modulus at offset 0x{modulus_offset:08x} (near exponent at 0x{exp_offset:08x})")
print(f" Non-zero bytes: {non_zero}/256")
print(f" Unique bytes: {unique_bytes}")
print(f" First 32 bytes: {modulus_candidate[:32].hex()}")
print(f" Last 32 bytes: {modulus_candidate[-32:].hex()}")
# Check if it looks like a valid RSA modulus (high bit set) if is_likely_rsa_modulus(modulus_candidate):
if modulus_candidate[-1] & 0x80: file_mod_offset = sec['raw_offset'] + mod_offset
print(f" [✓] High bit set (typical for RSA modulus)") entropy = calculate_entropy(modulus_candidate)
else:
print(f" [!] High bit not set (unusual)")
# Write to C++ array format candidates.append({
print(f"\n[*] C++ array format:") 'offset': file_mod_offset,
print_cpp_array(modulus_candidate) 'section': sec['name'],
'data': modulus_candidate,
'entropy': entropy,
'exponent_offset': file_offset
})
return None 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): def print_cpp_array(data):
"""Print byte array in C++ format""" """Print byte array in C++ format"""
@ -82,7 +290,8 @@ def print_cpp_array(data):
for i in range(0, 256, 16): for i in range(0, 256, 16):
chunk = data[i:i+16] chunk = data[i:i+16]
hex_bytes = ', '.join(f'0x{b:02X}' for b in chunk) hex_bytes = ', '.join(f'0x{b:02X}' for b in chunk)
print(f" {hex_bytes},") comma = ',' if i < 240 else ''
print(f" {hex_bytes}{comma}")
print("};") print("};")
if __name__ == '__main__': if __name__ == '__main__':
@ -91,4 +300,11 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
exe_path = sys.argv[1] exe_path = sys.argv[1]
find_warden_modulus(exe_path) 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)

View file

@ -26,6 +26,7 @@
#include <functional> #include <functional>
#include <cstdlib> #include <cstdlib>
#include <zlib.h> #include <zlib.h>
#include <openssl/sha.h>
namespace wowee { namespace wowee {
namespace game { namespace game {
@ -1887,8 +1888,10 @@ void GameHandler::handleWardenData(network::Packet& packet) {
LOG_INFO("Warden: Crypto initialized, analyzing module structure"); LOG_INFO("Warden: Crypto initialized, analyzing module structure");
// Parse module structure (37 bytes typical): // Parse module structure (37 bytes typical):
// [1 byte opcode][16 bytes seed][20 bytes trailing data (SHA1?)] // [1 byte opcode][16 bytes seed][20 bytes challenge/hash]
std::vector<uint8_t> trailingBytes; std::vector<uint8_t> trailingBytes;
uint8_t opcodeByte = data[0];
if (data.size() >= 37) { if (data.size() >= 37) {
std::string trailingHex; std::string trailingHex;
for (size_t i = 17; i < 37; ++i) { for (size_t i = 17; i < 37; ++i) {
@ -1897,52 +1900,36 @@ void GameHandler::handleWardenData(network::Packet& packet) {
snprintf(b, sizeof(b), "%02x ", data[i]); snprintf(b, sizeof(b), "%02x ", data[i]);
trailingHex += b; trailingHex += b;
} }
LOG_INFO("Warden: Module trailing data (20 bytes): ", trailingHex); LOG_INFO("Warden: Opcode byte: 0x", std::hex, (int)opcodeByte, std::dec);
LOG_INFO("Warden: Challenge/hash (20 bytes): ", trailingHex);
} }
// Try response strategy: Result code + SHA1 hash of entire module // For opcode 0xF6, try empty response (some servers expect nothing)
// Format: [0x01 success][20-byte SHA1 of module data]
std::vector<uint8_t> moduleResponse; std::vector<uint8_t> moduleResponse;
LOG_INFO("Warden: Trying response strategy: [0x01][SHA1 of module]"); LOG_INFO("Warden: Trying response strategy: Empty response (0 bytes)");
// Success result code // Send empty/null response
moduleResponse.push_back(0x01); // moduleResponse remains empty (size = 0)
// Compute SHA1 hash of the entire module packet LOG_INFO("Warden: Crypto initialized, sending MODULE_OK with challenge");
std::vector<uint8_t> sha1Hash = auth::Crypto::sha1(data); LOG_INFO("Warden: Opcode 0x01 + 20-byte challenge");
// Add SHA1 hash (20 bytes) // Try opcode 0x01 (MODULE_OK) followed by 20-byte challenge
for (uint8_t byte : sha1Hash) { std::vector<uint8_t> hashResponse;
moduleResponse.push_back(byte); hashResponse.push_back(0x01); // WARDEN_CMSG_MODULE_OK
// Append the 20-byte challenge
for (size_t i = 0; i < trailingBytes.size(); ++i) {
hashResponse.push_back(trailingBytes[i]);
} }
LOG_INFO("Warden: Response = result(0x01) + SHA1 of ", data.size(), " byte module"); LOG_INFO("Warden: SHA1 hash computed (20 bytes), total response: ", hashResponse.size(), " bytes");
// Log plaintext module response
std::string respHex;
respHex.reserve(moduleResponse.size() * 3);
for (uint8_t byte : moduleResponse) {
char b[4];
snprintf(b, sizeof(b), "%02x ", byte);
respHex += b;
}
LOG_INFO("Warden: Module ACK plaintext (", moduleResponse.size(), " bytes): ", respHex);
// Encrypt the response // Encrypt the response
std::vector<uint8_t> encryptedResponse = wardenCrypto_->encrypt(moduleResponse); std::vector<uint8_t> encryptedResponse = wardenCrypto_->encrypt(hashResponse);
// Log encrypted response // Send HASH_RESULT response
std::string encHex;
encHex.reserve(encryptedResponse.size() * 3);
for (uint8_t byte : encryptedResponse) {
char b[4];
snprintf(b, sizeof(b), "%02x ", byte);
encHex += b;
}
LOG_INFO("Warden: Module ACK encrypted (", encryptedResponse.size(), " bytes): ", encHex);
// Send encrypted module ACK
network::Packet response(static_cast<uint16_t>(Opcode::CMSG_WARDEN_DATA)); network::Packet response(static_cast<uint16_t>(Opcode::CMSG_WARDEN_DATA));
for (uint8_t byte : encryptedResponse) { for (uint8_t byte : encryptedResponse) {
response.writeUInt8(byte); response.writeUInt8(byte);
@ -1950,24 +1937,58 @@ void GameHandler::handleWardenData(network::Packet& packet) {
if (socket && socket->isConnected()) { if (socket && socket->isConnected()) {
socket->send(response); socket->send(response);
LOG_INFO("Sent CMSG_WARDEN_DATA module ACK (", encryptedResponse.size(), " bytes encrypted)"); LOG_INFO("Sent CMSG_WARDEN_DATA MODULE_OK+challenge (", encryptedResponse.size(), " bytes encrypted)");
} }
// Mark that we've seen the initial seed packet
wardenGateSeen_ = true;
return; return;
} }
// Decrypt the packet // Decrypt the packet
std::vector<uint8_t> decrypted = wardenCrypto_->decrypt(data); std::vector<uint8_t> decrypted = wardenCrypto_->decrypt(data);
// Log decrypted data // Log decrypted data (first 64 bytes for readability)
std::string decHex; std::string decHex;
decHex.reserve(decrypted.size() * 3); size_t logSize = std::min(decrypted.size(), size_t(64));
for (size_t i = 0; i < decrypted.size(); ++i) { decHex.reserve(logSize * 3);
for (size_t i = 0; i < logSize; ++i) {
char b[4]; char b[4];
snprintf(b, sizeof(b), "%02x ", decrypted[i]); snprintf(b, sizeof(b), "%02x ", decrypted[i]);
decHex += b; decHex += b;
} }
if (decrypted.size() > 64) {
decHex += "... (" + std::to_string(decrypted.size() - 64) + " more bytes)";
}
LOG_INFO("Warden: Decrypted (", decrypted.size(), " bytes): ", decHex); LOG_INFO("Warden: Decrypted (", decrypted.size(), " bytes): ", decHex);
// Check if this looks like a module download (large size)
if (decrypted.size() > 256) {
LOG_INFO("Warden: Received large packet (", decrypted.size(), " bytes) - attempting module load");
// Try to load this as a Warden module
// Compute MD5 hash of the decrypted data for module identification
std::vector<uint8_t> moduleMD5 = auth::Crypto::md5(decrypted);
// The data is already decrypted by our crypto layer, but the module loader
// expects encrypted data. We need to pass the original encrypted data.
// For now, try loading with what we have.
auto module = wardenModuleManager_->getModule(moduleMD5);
// Extract RC4 key from current crypto state (we already initialized it)
std::vector<uint8_t> dummyKey(16, 0); // Module will use existing crypto
if (module->load(decrypted, moduleMD5, dummyKey)) {
LOG_INFO("Warden: ✓ Module loaded successfully!");
// Module is now ready to process check requests
// No response needed for module download
return;
} else {
LOG_WARNING("Warden: ✗ Module load failed, falling back to fake responses");
}
}
// Prepare response data // Prepare response data
std::vector<uint8_t> responseData; std::vector<uint8_t> responseData;
@ -1976,6 +1997,27 @@ void GameHandler::handleWardenData(network::Packet& packet) {
} else { } else {
uint8_t opcode = decrypted[0]; uint8_t opcode = decrypted[0];
// If we have a loaded module, try to use it for check processing
std::vector<uint8_t> moduleMD5 = auth::Crypto::md5(decrypted);
auto module = wardenModuleManager_->getModule(moduleMD5);
if (module && module->isLoaded()) {
LOG_INFO("Warden: Using loaded module to process check request (opcode 0x",
std::hex, (int)opcode, std::dec, ")");
if (module->processCheckRequest(decrypted, responseData)) {
LOG_INFO("Warden: ✓ Module generated response (", responseData.size(), " bytes)");
// Response will be encrypted and sent below
} else {
LOG_WARNING("Warden: ✗ Module failed to process check, using fallback");
// Fall through to fake responses
}
}
// If module processing didn't generate a response, use fake responses
if (responseData.empty()) {
uint8_t opcode = decrypted[0];
// Warden check opcodes (from WoW 3.3.5a protocol) // Warden check opcodes (from WoW 3.3.5a protocol)
switch (opcode) { switch (opcode) {
case 0x00: // Module info request case 0x00: // Module info request
@ -2067,9 +2109,16 @@ void GameHandler::handleWardenData(network::Packet& packet) {
responseData.push_back(0x00); responseData.push_back(0x00);
} }
break; break;
}
} }
} }
// If we have no response data, don't send anything
if (responseData.empty()) {
LOG_INFO("Warden: No response generated (module loaded or waiting for checks)");
return;
}
// Log plaintext response // Log plaintext response
std::string respPlainHex; std::string respPlainHex;
respPlainHex.reserve(responseData.size() * 3); respPlainHex.reserve(responseData.size() * 3);

View file

@ -303,46 +303,26 @@ bool WardenModule::verifyRSASignature(const std::vector<uint8_t>& data) {
// Exponent: 0x010001 (65537) // Exponent: 0x010001 (65537)
const uint32_t exponent = 0x010001; const uint32_t exponent = 0x010001;
// Modulus (256 bytes) - This is the actual public key from WoW 3.3.5a client // Modulus (256 bytes) - Extracted from WoW 3.3.5a (build 12340) client
// TODO: Extract this from WoW.exe binary at offset (varies by build) // Extracted from Wow.exe at offset 0x005e3a03 (.rdata section)
// For now, using a placeholder that will fail verification // This is the actual RSA-2048 public key modulus used by Warden
// To get the real modulus: extract from WoW.exe using a hex editor or IDA Pro
const uint8_t modulus[256] = { const uint8_t modulus[256] = {
// PLACEHOLDER - Replace with actual modulus from WoW 3.3.5a (build 12340) 0x51, 0xAD, 0x57, 0x75, 0x16, 0x92, 0x0A, 0x0E, 0xEB, 0xFA, 0xF8, 0x1B, 0x37, 0x49, 0x7C, 0xDD,
// This can be extracted from the WoW client binary 0x47, 0xDA, 0x5E, 0x02, 0x8D, 0x96, 0x75, 0x21, 0x27, 0x59, 0x04, 0xAC, 0xB1, 0x0C, 0xB9, 0x23,
// The actual value varies by client version and build 0x05, 0xCC, 0x82, 0xB8, 0xBF, 0x04, 0x77, 0x62, 0x92, 0x01, 0x00, 0x01, 0x00, 0x77, 0x64, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x1D, 0xFB, 0xB0, 0x09, 0xC4, 0xE6, 0x28, 0x91, 0x34, 0xE3, 0x55, 0x61, 0x15, 0x8A, 0xE9,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0xAA, 0x60, 0xB3, 0x82, 0xB7, 0xE2, 0xA4, 0x40, 0x15, 0x01, 0x3F, 0xC2, 0x36, 0xA8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9D, 0x95, 0xD0, 0x54, 0x69, 0xAA, 0xF5, 0xED, 0x5C, 0x7F, 0x21, 0xC5, 0x55, 0x95, 0x56, 0x5B,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xC6, 0xDD, 0x2C, 0xBD, 0x74, 0xA3, 0x5A, 0x0D, 0x70, 0x98, 0x9A, 0x01, 0x36, 0x51, 0x78,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x9B, 0x8E, 0xCB, 0xB8, 0x84, 0x67, 0x30, 0xF4, 0x43, 0xB3, 0xA3, 0x50, 0xA3, 0xBA, 0xA4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xB1, 0x94, 0xE5, 0x5B, 0x95, 0x8B, 0x1A, 0xE4, 0x04, 0x1D, 0xFB, 0xCF, 0x0E, 0xE6, 0x97,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xDC, 0xE4, 0x28, 0x7F, 0xB8, 0x58, 0x4A, 0x45, 0x1B, 0xC8, 0x8C, 0xD0, 0xFD, 0x2E, 0x77,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x30, 0xD8, 0x3D, 0xD2, 0xD5, 0xFA, 0xBA, 0x9D, 0x1E, 0x02, 0xF6, 0x7B, 0xBE, 0x08, 0x95,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCB, 0xB0, 0x53, 0x3E, 0x1C, 0x41, 0x45, 0xFC, 0x27, 0x6F, 0x63, 0x6A, 0x73, 0x91, 0xA9, 0x42,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x93, 0xF8, 0x5B, 0x83, 0xED, 0x52, 0x77, 0x4E, 0x38, 0x08, 0x16, 0x23, 0x10, 0x85,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x0B, 0xA9, 0x8C, 0x9C, 0x40, 0x4C, 0xAF, 0x6E, 0xA7, 0x89, 0x02, 0xC5, 0x06, 0x96, 0x99,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xD4, 0x31, 0x03, 0x4A, 0xA9, 0x2B, 0x17, 0x52, 0xDD, 0x5C, 0x4E, 0x5F, 0x16, 0xC3, 0x81,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x2E, 0xE2, 0x17, 0x45, 0x2B, 0x7B, 0x65, 0x7A, 0xA3, 0x18, 0x87, 0xC2, 0xB2, 0xF5, 0xCD
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; };
// Compute expected hash: SHA1(data_without_sig + "MAIEV.MOD") // Compute expected hash: SHA1(data_without_sig + "MAIEV.MOD")