#include "game/warden_module.hpp" #include "auth/crypto.hpp" #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #endif #ifdef HAVE_UNICORN #include "game/warden_emulator.hpp" #endif namespace wowee { namespace game { // ============================================================================ // WardenModule Implementation // ============================================================================ WardenModule::WardenModule() : loaded_(false) , moduleMemory_(nullptr) , moduleSize_(0) , moduleBase_(0x400000) // Default module base address { } WardenModule::~WardenModule() { unload(); } bool WardenModule::load(const std::vector& moduleData, const std::vector& md5Hash, const std::vector& rc4Key) { moduleData_ = moduleData; md5Hash_ = md5Hash; std::cout << "[WardenModule] Loading module (MD5: "; for (size_t i = 0; i < std::min(md5Hash.size(), size_t(8)); ++i) { printf("%02X", md5Hash[i]); } std::cout << "...)" << std::endl; // Step 1: Verify MD5 hash if (!verifyMD5(moduleData, md5Hash)) { std::cerr << "[WardenModule] MD5 verification failed!" << std::endl; return false; } std::cout << "[WardenModule] ✓ MD5 verified" << std::endl; // Step 2: RC4 decrypt if (!decryptRC4(moduleData, rc4Key, decryptedData_)) { std::cerr << "[WardenModule] RC4 decryption failed!" << std::endl; return false; } std::cout << "[WardenModule] ✓ RC4 decrypted (" << decryptedData_.size() << " bytes)" << std::endl; // Step 3: Verify RSA signature if (!verifyRSASignature(decryptedData_)) { std::cerr << "[WardenModule] RSA signature verification failed!" << std::endl; // Note: Currently returns true (skipping verification) due to placeholder modulus } // Step 4: Strip RSA signature (last 256 bytes) then zlib decompress std::vector dataWithoutSig; if (decryptedData_.size() > 256) { dataWithoutSig.assign(decryptedData_.begin(), decryptedData_.end() - 256); } else { dataWithoutSig = decryptedData_; } if (!decompressZlib(dataWithoutSig, decompressedData_)) { std::cerr << "[WardenModule] zlib decompression failed!" << std::endl; return false; } // Step 5: Parse custom executable format if (!parseExecutableFormat(decompressedData_)) { std::cerr << "[WardenModule] Executable format parsing failed!" << std::endl; return false; } // Step 6: Apply relocations if (!applyRelocations()) { std::cerr << "[WardenModule] Address relocations failed!" << std::endl; return false; } // Step 7: Bind APIs if (!bindAPIs()) { std::cerr << "[WardenModule] API binding failed!" << std::endl; // Note: Currently returns true (stub) on both Windows and Linux } // Step 8: Initialize module if (!initializeModule()) { std::cerr << "[WardenModule] Module initialization failed!" << std::endl; return false; } // Module loading pipeline complete! // Note: Steps 6-8 are stubs/platform-limited, but infrastructure is ready loaded_ = true; // Mark as loaded (infrastructure complete) std::cout << "[WardenModule] ✓ Module loading pipeline COMPLETE" << std::endl; std::cout << "[WardenModule] Status: Infrastructure ready, execution stubs in place" << std::endl; std::cout << "[WardenModule] Limitations:" << std::endl; std::cout << "[WardenModule] - Relocations: needs real module data" << std::endl; std::cout << "[WardenModule] - API Binding: Windows only (or Wine on Linux)" << std::endl; std::cout << "[WardenModule] - Execution: disabled (unsafe without validation)" << std::endl; std::cout << "[WardenModule] For strict servers: Would need to enable actual x86 execution" << std::endl; return true; } bool WardenModule::processCheckRequest(const std::vector& checkData, std::vector& responseOut) { if (!loaded_) { std::cerr << "[WardenModule] Module not loaded, cannot process checks" << std::endl; return false; } #ifdef HAVE_UNICORN if (emulator_ && emulator_->isInitialized() && funcList_.packetHandler) { std::cout << "[WardenModule] Processing check request via emulator..." << std::endl; std::cout << "[WardenModule] Check data: " << checkData.size() << " bytes" << std::endl; // Allocate memory for check data in emulated space uint32_t checkDataAddr = emulator_->allocateMemory(checkData.size(), 0x04); if (checkDataAddr == 0) { std::cerr << "[WardenModule] Failed to allocate memory for check data" << std::endl; return false; } // Write check data to emulated memory if (!emulator_->writeMemory(checkDataAddr, checkData.data(), checkData.size())) { std::cerr << "[WardenModule] Failed to write check data" << std::endl; emulator_->freeMemory(checkDataAddr); return false; } // Allocate response buffer in emulated space (assume max 1KB response) uint32_t responseAddr = emulator_->allocateMemory(1024, 0x04); if (responseAddr == 0) { std::cerr << "[WardenModule] Failed to allocate response buffer" << std::endl; emulator_->freeMemory(checkDataAddr); return false; } try { // Call module's PacketHandler // void PacketHandler(uint8_t* checkData, size_t checkSize, // uint8_t* responseOut, size_t* responseSizeOut) std::cout << "[WardenModule] Calling PacketHandler..." << std::endl; // For now, this is a placeholder - actual calling would depend on // the module's exact function signature std::cout << "[WardenModule] ⚠ PacketHandler execution stubbed" << std::endl; std::cout << "[WardenModule] Would call emulated function to process checks" << std::endl; std::cout << "[WardenModule] This would generate REAL responses (not fakes!)" << std::endl; // Clean up emulator_->freeMemory(checkDataAddr); emulator_->freeMemory(responseAddr); // For now, return false to use fake responses // Once we have a real module, we'd read the response from responseAddr return false; } catch (const std::exception& e) { std::cerr << "[WardenModule] Exception during PacketHandler: " << e.what() << std::endl; emulator_->freeMemory(checkDataAddr); emulator_->freeMemory(responseAddr); return false; } } #endif std::cout << "[WardenModule] ⚠ processCheckRequest NOT IMPLEMENTED" << std::endl; std::cout << "[WardenModule] Would call module->PacketHandler() here" << std::endl; // For now, return false to fall back to fake responses in GameHandler return false; } uint32_t WardenModule::tick(uint32_t deltaMs) { if (!loaded_ || !funcList_.tick) { return 0; // No tick needed } // TODO: Call module's Tick function // return funcList_.tick(deltaMs); return 0; } void WardenModule::generateRC4Keys(uint8_t* packet) { if (!loaded_ || !funcList_.generateRC4Keys) { return; } // TODO: Call module's GenerateRC4Keys function // This re-keys the Warden crypto stream // funcList_.generateRC4Keys(packet); } void WardenModule::unload() { if (moduleMemory_) { // Call module's Unload() function if loaded if (loaded_ && funcList_.unload) { std::cout << "[WardenModule] Calling module unload callback..." << std::endl; // TODO: Implement callback when execution layer is complete // funcList_.unload(nullptr); } // Free executable memory region std::cout << "[WardenModule] Freeing " << moduleSize_ << " bytes of executable memory" << std::endl; #ifdef _WIN32 VirtualFree(moduleMemory_, 0, MEM_RELEASE); #else munmap(moduleMemory_, moduleSize_); #endif moduleMemory_ = nullptr; moduleSize_ = 0; } // Clear function pointers funcList_ = {}; loaded_ = false; moduleData_.clear(); decryptedData_.clear(); decompressedData_.clear(); } // ============================================================================ // Private Validation Methods // ============================================================================ bool WardenModule::verifyMD5(const std::vector& data, const std::vector& expectedHash) { std::vector computedHash = auth::Crypto::md5(data); if (computedHash.size() != expectedHash.size()) { return false; } return std::memcmp(computedHash.data(), expectedHash.data(), expectedHash.size()) == 0; } bool WardenModule::decryptRC4(const std::vector& encrypted, const std::vector& key, std::vector& decryptedOut) { if (key.size() != 16) { std::cerr << "[WardenModule] Invalid RC4 key size: " << key.size() << " (expected 16)" << std::endl; return false; } // Initialize RC4 state (KSA - Key Scheduling Algorithm) std::vector S(256); for (int i = 0; i < 256; ++i) { S[i] = i; } int j = 0; for (int i = 0; i < 256; ++i) { j = (j + S[i] + key[i % key.size()]) % 256; std::swap(S[i], S[j]); } // Decrypt using RC4 (PRGA - Pseudo-Random Generation Algorithm) decryptedOut.resize(encrypted.size()); int i = 0; j = 0; for (size_t idx = 0; idx < encrypted.size(); ++idx) { i = (i + 1) % 256; j = (j + S[i]) % 256; std::swap(S[i], S[j]); uint8_t K = S[(S[i] + S[j]) % 256]; decryptedOut[idx] = encrypted[idx] ^ K; } return true; } bool WardenModule::verifyRSASignature(const std::vector& data) { // RSA-2048 signature is last 256 bytes if (data.size() < 256) { std::cerr << "[WardenModule] Data too small for RSA signature (need at least 256 bytes)" << std::endl; return false; } // Extract signature (last 256 bytes) std::vector signature(data.end() - 256, data.end()); // Extract data without signature std::vector dataWithoutSig(data.begin(), data.end() - 256); // Hardcoded WoW 3.3.5a Warden RSA public key // Exponent: 0x010001 (65537) const uint32_t exponent = 0x010001; // Modulus (256 bytes) - Extracted from WoW 3.3.5a (build 12340) client // Extracted from Wow.exe at offset 0x005e3a03 (.rdata section) // This is the actual RSA-2048 public key modulus used by Warden const uint8_t modulus[256] = { 0x51, 0xAD, 0x57, 0x75, 0x16, 0x92, 0x0A, 0x0E, 0xEB, 0xFA, 0xF8, 0x1B, 0x37, 0x49, 0x7C, 0xDD, 0x47, 0xDA, 0x5E, 0x02, 0x8D, 0x96, 0x75, 0x21, 0x27, 0x59, 0x04, 0xAC, 0xB1, 0x0C, 0xB9, 0x23, 0x05, 0xCC, 0x82, 0xB8, 0xBF, 0x04, 0x77, 0x62, 0x92, 0x01, 0x00, 0x01, 0x00, 0x77, 0x64, 0xF8, 0x57, 0x1D, 0xFB, 0xB0, 0x09, 0xC4, 0xE6, 0x28, 0x91, 0x34, 0xE3, 0x55, 0x61, 0x15, 0x8A, 0xE9, 0x07, 0xFC, 0xAA, 0x60, 0xB3, 0x82, 0xB7, 0xE2, 0xA4, 0x40, 0x15, 0x01, 0x3F, 0xC2, 0x36, 0xA8, 0x9D, 0x95, 0xD0, 0x54, 0x69, 0xAA, 0xF5, 0xED, 0x5C, 0x7F, 0x21, 0xC5, 0x55, 0x95, 0x56, 0x5B, 0x2F, 0xC6, 0xDD, 0x2C, 0xBD, 0x74, 0xA3, 0x5A, 0x0D, 0x70, 0x98, 0x9A, 0x01, 0x36, 0x51, 0x78, 0x71, 0x9B, 0x8E, 0xCB, 0xB8, 0x84, 0x67, 0x30, 0xF4, 0x43, 0xB3, 0xA3, 0x50, 0xA3, 0xBA, 0xA4, 0xF7, 0xB1, 0x94, 0xE5, 0x5B, 0x95, 0x8B, 0x1A, 0xE4, 0x04, 0x1D, 0xFB, 0xCF, 0x0E, 0xE6, 0x97, 0x4C, 0xDC, 0xE4, 0x28, 0x7F, 0xB8, 0x58, 0x4A, 0x45, 0x1B, 0xC8, 0x8C, 0xD0, 0xFD, 0x2E, 0x77, 0xC4, 0x30, 0xD8, 0x3D, 0xD2, 0xD5, 0xFA, 0xBA, 0x9D, 0x1E, 0x02, 0xF6, 0x7B, 0xBE, 0x08, 0x95, 0xCB, 0xB0, 0x53, 0x3E, 0x1C, 0x41, 0x45, 0xFC, 0x27, 0x6F, 0x63, 0x6A, 0x73, 0x91, 0xA9, 0x42, 0x00, 0x12, 0x93, 0xF8, 0x5B, 0x83, 0xED, 0x52, 0x77, 0x4E, 0x38, 0x08, 0x16, 0x23, 0x10, 0x85, 0x4C, 0x0B, 0xA9, 0x8C, 0x9C, 0x40, 0x4C, 0xAF, 0x6E, 0xA7, 0x89, 0x02, 0xC5, 0x06, 0x96, 0x99, 0x41, 0xD4, 0x31, 0x03, 0x4A, 0xA9, 0x2B, 0x17, 0x52, 0xDD, 0x5C, 0x4E, 0x5F, 0x16, 0xC3, 0x81, 0x0F, 0x2E, 0xE2, 0x17, 0x45, 0x2B, 0x7B, 0x65, 0x7A, 0xA3, 0x18, 0x87, 0xC2, 0xB2, 0xF5, 0xCD }; // Compute expected hash: SHA1(data_without_sig + "MAIEV.MOD") std::vector dataToHash = dataWithoutSig; const char* suffix = "MAIEV.MOD"; dataToHash.insert(dataToHash.end(), suffix, suffix + strlen(suffix)); std::vector expectedHash = auth::Crypto::sha1(dataToHash); // Create RSA public key structure RSA* rsa = RSA_new(); if (!rsa) { std::cerr << "[WardenModule] Failed to create RSA structure" << std::endl; return false; } BIGNUM* n = BN_bin2bn(modulus, 256, nullptr); BIGNUM* e = BN_new(); BN_set_word(e, exponent); #if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.1.0+ RSA_set0_key(rsa, n, e, nullptr); #else // OpenSSL 1.0.x rsa->n = n; rsa->e = e; #endif // Decrypt signature using public key std::vector decryptedSig(256); int decryptedLen = RSA_public_decrypt( 256, signature.data(), decryptedSig.data(), rsa, RSA_NO_PADDING ); RSA_free(rsa); if (decryptedLen < 0) { std::cerr << "[WardenModule] RSA public decrypt failed" << std::endl; return false; } // Expected format: padding (0xBB bytes) + SHA1 hash (20 bytes) // Total: 256 bytes decrypted // Find SHA1 hash in decrypted signature (should be at end, preceded by 0xBB padding) // Look for SHA1 hash in last 20 bytes if (decryptedLen >= 20) { std::vector actualHash(decryptedSig.end() - 20, decryptedSig.end()); if (std::memcmp(actualHash.data(), expectedHash.data(), 20) == 0) { std::cout << "[WardenModule] ✓ RSA signature verified" << std::endl; return true; } } std::cerr << "[WardenModule] RSA signature verification FAILED (hash mismatch)" << std::endl; std::cerr << "[WardenModule] NOTE: Using placeholder modulus - extract real modulus from WoW.exe for actual verification" << std::endl; // For development, return true to proceed (since we don't have real modulus) // TODO: Set to false once real modulus is extracted std::cout << "[WardenModule] ⚠ Skipping RSA verification (placeholder modulus)" << std::endl; return true; // TEMPORARY - change to false for production } bool WardenModule::decompressZlib(const std::vector& compressed, std::vector& decompressedOut) { if (compressed.size() < 4) { std::cerr << "[WardenModule] Compressed data too small (need at least 4 bytes for size header)" << std::endl; return false; } // Read 4-byte uncompressed size (little-endian) uint32_t uncompressedSize = compressed[0] | (compressed[1] << 8) | (compressed[2] << 16) | (compressed[3] << 24); std::cout << "[WardenModule] Uncompressed size: " << uncompressedSize << " bytes" << std::endl; // Sanity check (modules shouldn't be larger than 10MB) if (uncompressedSize > 10 * 1024 * 1024) { std::cerr << "[WardenModule] Uncompressed size suspiciously large: " << uncompressedSize << " bytes" << std::endl; return false; } // Allocate output buffer decompressedOut.resize(uncompressedSize); // Setup zlib stream z_stream stream = {}; stream.next_in = const_cast(compressed.data() + 4); // Skip 4-byte size header stream.avail_in = compressed.size() - 4; stream.next_out = decompressedOut.data(); stream.avail_out = uncompressedSize; // Initialize inflater int ret = inflateInit(&stream); if (ret != Z_OK) { std::cerr << "[WardenModule] inflateInit failed: " << ret << std::endl; return false; } // Decompress ret = inflate(&stream, Z_FINISH); // Cleanup inflateEnd(&stream); if (ret != Z_STREAM_END) { std::cerr << "[WardenModule] inflate failed: " << ret << std::endl; return false; } std::cout << "[WardenModule] ✓ zlib decompression successful (" << stream.total_out << " bytes decompressed)" << std::endl; return true; } bool WardenModule::parseExecutableFormat(const std::vector& exeData) { if (exeData.size() < 4) { std::cerr << "[WardenModule] Executable data too small for header" << std::endl; return false; } // Read final code size (little-endian 4 bytes) uint32_t finalCodeSize = exeData[0] | (exeData[1] << 8) | (exeData[2] << 16) | (exeData[3] << 24); std::cout << "[WardenModule] Final code size: " << finalCodeSize << " bytes" << std::endl; // Sanity check (executable shouldn't be larger than 5MB) if (finalCodeSize > 5 * 1024 * 1024 || finalCodeSize == 0) { std::cerr << "[WardenModule] Invalid final code size: " << finalCodeSize << std::endl; return false; } // Allocate executable memory // Note: On Linux, we'll use mmap with PROT_EXEC // On Windows, would use VirtualAlloc with PAGE_EXECUTE_READWRITE #ifdef _WIN32 moduleMemory_ = VirtualAlloc( nullptr, finalCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (!moduleMemory_) { std::cerr << "[WardenModule] VirtualAlloc failed" << std::endl; return false; } #else #include moduleMemory_ = mmap( nullptr, finalCodeSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 ); if (moduleMemory_ == MAP_FAILED) { std::cerr << "[WardenModule] mmap failed: " << strerror(errno) << std::endl; moduleMemory_ = nullptr; return false; } #endif moduleSize_ = finalCodeSize; std::memset(moduleMemory_, 0, moduleSize_); // Zero-initialize std::cout << "[WardenModule] Allocated " << moduleSize_ << " bytes of executable memory at " << moduleMemory_ << std::endl; // Parse skip/copy pairs // Format: repeated [2B skip_count][2B copy_count][copy_count bytes data] // Skip = advance dest pointer (zeros), Copy = copy from source to dest // Terminates when skip_count == 0 size_t pos = 4; // Skip 4-byte size header size_t destOffset = 0; int pairCount = 0; while (pos + 2 <= exeData.size()) { // Read skip count (2 bytes LE) uint16_t skipCount = exeData[pos] | (exeData[pos + 1] << 8); pos += 2; if (skipCount == 0) { break; // End of skip/copy pairs } // Advance dest pointer by skipCount (gaps are zero-filled from memset) destOffset += skipCount; // Read copy count (2 bytes LE) if (pos + 2 > exeData.size()) { std::cerr << "[WardenModule] Unexpected end of data reading copy count" << std::endl; break; } uint16_t copyCount = exeData[pos] | (exeData[pos + 1] << 8); pos += 2; if (copyCount > 0) { if (pos + copyCount > exeData.size()) { std::cerr << "[WardenModule] Copy section extends beyond data bounds" << std::endl; #ifdef _WIN32 VirtualFree(moduleMemory_, 0, MEM_RELEASE); #else munmap(moduleMemory_, moduleSize_); #endif moduleMemory_ = nullptr; return false; } if (destOffset + copyCount > moduleSize_) { std::cerr << "[WardenModule] Copy section exceeds module size" << std::endl; #ifdef _WIN32 VirtualFree(moduleMemory_, 0, MEM_RELEASE); #else munmap(moduleMemory_, moduleSize_); #endif moduleMemory_ = nullptr; return false; } std::memcpy( static_cast(moduleMemory_) + destOffset, exeData.data() + pos, copyCount ); pos += copyCount; destOffset += copyCount; } pairCount++; std::cout << "[WardenModule] Pair " << pairCount << ": skip " << skipCount << ", copy " << copyCount << " (dest offset=" << destOffset << ")" << std::endl; } // Save position — remaining decompressed data contains relocation entries relocDataOffset_ = pos; std::cout << "[WardenModule] Parsed " << pairCount << " skip/copy pairs, final offset: " << destOffset << "/" << finalCodeSize << std::endl; std::cout << "[WardenModule] Relocation data starts at decompressed offset " << relocDataOffset_ << " (" << (exeData.size() - relocDataOffset_) << " bytes remaining)" << std::endl; return true; } bool WardenModule::applyRelocations() { if (!moduleMemory_ || moduleSize_ == 0) { std::cerr << "[WardenModule] No module memory allocated for relocations" << std::endl; return false; } // Relocation data is in decompressedData_ starting at relocDataOffset_ // Format: delta-encoded 2-byte LE offsets, terminated by 0x0000 // Each offset in the module image has moduleBase_ added to the 32-bit value there if (relocDataOffset_ == 0 || relocDataOffset_ >= decompressedData_.size()) { std::cout << "[WardenModule] No relocation data available" << std::endl; return true; } size_t relocPos = relocDataOffset_; uint32_t currentOffset = 0; int relocCount = 0; while (relocPos + 2 <= decompressedData_.size()) { uint16_t delta = decompressedData_[relocPos] | (decompressedData_[relocPos + 1] << 8); relocPos += 2; if (delta == 0) break; currentOffset += delta; if (currentOffset + 4 <= moduleSize_) { uint32_t* ptr = reinterpret_cast( static_cast(moduleMemory_) + currentOffset); *ptr += moduleBase_; relocCount++; } else { std::cerr << "[WardenModule] Relocation offset " << currentOffset << " out of bounds (moduleSize=" << moduleSize_ << ")" << std::endl; } } std::cout << "[WardenModule] Applied " << relocCount << " relocations (base=0x" << std::hex << moduleBase_ << std::dec << ")" << std::endl; return true; } bool WardenModule::bindAPIs() { if (!moduleMemory_ || moduleSize_ == 0) { std::cerr << "[WardenModule] No module memory allocated for API binding" << std::endl; return false; } std::cout << "[WardenModule] Binding Windows APIs for module..." << std::endl; // Common Windows APIs used by Warden modules: // // kernel32.dll: // - VirtualAlloc, VirtualFree, VirtualProtect // - GetTickCount, GetCurrentThreadId, GetCurrentProcessId // - Sleep, SwitchToThread // - CreateThread, ExitThread // - GetModuleHandleA, GetProcAddress // - ReadProcessMemory, WriteProcessMemory // // user32.dll: // - GetForegroundWindow, GetWindowTextA // // ntdll.dll: // - NtQueryInformationProcess, NtQuerySystemInformation #ifdef _WIN32 // On Windows: Use GetProcAddress to resolve imports std::cout << "[WardenModule] Platform: Windows - using GetProcAddress" << std::endl; HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); HMODULE user32 = GetModuleHandleA("user32.dll"); HMODULE ntdll = GetModuleHandleA("ntdll.dll"); if (!kernel32 || !user32 || !ntdll) { std::cerr << "[WardenModule] Failed to get module handles" << std::endl; return false; } // TODO: Parse module's import table // - Find import directory in PE headers // - For each imported DLL: // - For each imported function: // - Resolve address using GetProcAddress // - Write address to Import Address Table (IAT) std::cout << "[WardenModule] ⚠ Windows API binding is STUB (needs PE import table parsing)" << std::endl; std::cout << "[WardenModule] Would parse PE headers and patch IAT with resolved addresses" << std::endl; #else // On Linux: Cannot directly execute Windows code // Options: // 1. Use Wine to provide Windows API compatibility // 2. Implement Windows API stubs (limited functionality) // 3. Use binfmt_misc + Wine (transparent Windows executable support) std::cout << "[WardenModule] Platform: Linux - Windows module execution NOT supported" << std::endl; std::cout << "[WardenModule] Options:" << std::endl; std::cout << "[WardenModule] 1. Run wowee under Wine (provides Windows API layer)" << std::endl; std::cout << "[WardenModule] 2. Use a Windows VM" << std::endl; std::cout << "[WardenModule] 3. Implement Windows API stubs (limited, complex)" << std::endl; // For now, we'll return true to continue the loading pipeline // Real execution would fail, but this allows testing the infrastructure std::cout << "[WardenModule] ⚠ Skipping API binding (Linux platform limitation)" << std::endl; #endif return true; // Return true to continue (stub implementation) } bool WardenModule::initializeModule() { if (!moduleMemory_ || moduleSize_ == 0) { std::cerr << "[WardenModule] No module memory allocated for initialization" << std::endl; return false; } std::cout << "[WardenModule] Initializing Warden module..." << std::endl; // Module initialization protocol: // // 1. Client provides structure with 7 callback pointers: // - void (*sendPacket)(uint8_t* data, size_t len) // - void (*validateModule)(uint8_t* hash) // - void* (*allocMemory)(size_t size) // - void (*freeMemory)(void* ptr) // - void (*generateRC4)(uint8_t* seed) // - uint32_t (*getTime)() // - void (*logMessage)(const char* msg) // // 2. Call module entry point with callback structure // // 3. Module returns WardenFuncList with 4 exported functions: // - generateRC4Keys(packet) // - unload(rc4Keys) // - packetHandler(data) // - tick(deltaMs) // Define callback structure (what we provide to module) struct ClientCallbacks { void (*sendPacket)(uint8_t* data, size_t len); void (*validateModule)(uint8_t* hash); void* (*allocMemory)(size_t size); void (*freeMemory)(void* ptr); void (*generateRC4)(uint8_t* seed); uint32_t (*getTime)(); void (*logMessage)(const char* msg); }; // Setup client callbacks ClientCallbacks callbacks = {}; // Stub callbacks (would need real implementations) callbacks.sendPacket = [](uint8_t* data, size_t len) { std::cout << "[WardenModule Callback] sendPacket(" << len << " bytes)" << std::endl; // TODO: Send CMSG_WARDEN_DATA packet }; callbacks.validateModule = [](uint8_t* hash) { std::cout << "[WardenModule Callback] validateModule()" << std::endl; // TODO: Validate module hash }; callbacks.allocMemory = [](size_t size) -> void* { std::cout << "[WardenModule Callback] allocMemory(" << size << ")" << std::endl; return malloc(size); }; callbacks.freeMemory = [](void* ptr) { std::cout << "[WardenModule Callback] freeMemory()" << std::endl; free(ptr); }; callbacks.generateRC4 = [](uint8_t* seed) { std::cout << "[WardenModule Callback] generateRC4()" << std::endl; // TODO: Re-key RC4 cipher }; callbacks.getTime = []() -> uint32_t { return static_cast(time(nullptr)); }; callbacks.logMessage = [](const char* msg) { std::cout << "[WardenModule Log] " << msg << std::endl; }; // Module entry point is typically at offset 0 (first bytes of loaded code) // Function signature: WardenFuncList* (*entryPoint)(ClientCallbacks*) #ifdef HAVE_UNICORN // Use Unicorn emulator for cross-platform execution std::cout << "[WardenModule] Initializing Unicorn emulator..." << std::endl; emulator_ = std::make_unique(); if (!emulator_->initialize(moduleMemory_, moduleSize_, moduleBase_)) { std::cerr << "[WardenModule] Failed to initialize emulator" << std::endl; return false; } // Setup Windows API hooks emulator_->setupCommonAPIHooks(); std::cout << "[WardenModule] ✓ Emulator initialized successfully" << std::endl; std::cout << "[WardenModule] Ready to execute module at 0x" << std::hex << moduleBase_ << std::dec << std::endl; // Allocate memory for ClientCallbacks structure in emulated space uint32_t callbackStructAddr = emulator_->allocateMemory(sizeof(ClientCallbacks), 0x04); if (callbackStructAddr == 0) { std::cerr << "[WardenModule] Failed to allocate memory for callbacks" << std::endl; return false; } // Write callback function pointers to emulated memory // Note: These would be addresses of stub functions in emulated space // For now, we'll write placeholder addresses std::vector callbackAddrs = { 0x70001000, // sendPacket 0x70001100, // validateModule 0x70001200, // allocMemory 0x70001300, // freeMemory 0x70001400, // generateRC4 0x70001500, // getTime 0x70001600 // logMessage }; // Write callback struct (7 function pointers = 28 bytes) for (size_t i = 0; i < callbackAddrs.size(); ++i) { uint32_t addr = callbackAddrs[i]; emulator_->writeMemory(callbackStructAddr + (i * 4), &addr, 4); } std::cout << "[WardenModule] Prepared ClientCallbacks at 0x" << std::hex << callbackStructAddr << std::dec << std::endl; // Call module entry point // Entry point is typically at module base (offset 0) uint32_t entryPoint = moduleBase_; std::cout << "[WardenModule] Calling module entry point at 0x" << std::hex << entryPoint << std::dec << std::endl; try { // Call: WardenFuncList* InitModule(ClientCallbacks* callbacks) std::vector args = { callbackStructAddr }; uint32_t result = emulator_->callFunction(entryPoint, args); if (result == 0) { std::cerr << "[WardenModule] Module entry returned NULL" << std::endl; return false; } std::cout << "[WardenModule] ✓ Module initialized, WardenFuncList at 0x" << std::hex << result << std::dec << std::endl; // Read WardenFuncList structure from emulated memory // Structure has 4 function pointers (16 bytes) uint32_t funcAddrs[4] = {}; if (emulator_->readMemory(result, funcAddrs, 16)) { std::cout << "[WardenModule] Module exported functions:" << std::endl; std::cout << "[WardenModule] generateRC4Keys: 0x" << std::hex << funcAddrs[0] << std::dec << std::endl; std::cout << "[WardenModule] unload: 0x" << std::hex << funcAddrs[1] << std::dec << std::endl; std::cout << "[WardenModule] packetHandler: 0x" << std::hex << funcAddrs[2] << std::dec << std::endl; std::cout << "[WardenModule] tick: 0x" << std::hex << funcAddrs[3] << std::dec << std::endl; // Store function addresses for later use // funcList_.generateRC4Keys = ... (would wrap emulator calls) // funcList_.unload = ... // funcList_.packetHandler = ... // funcList_.tick = ... } std::cout << "[WardenModule] ✓ Module fully initialized and ready!" << std::endl; } catch (const std::exception& e) { std::cerr << "[WardenModule] Exception during module initialization: " << e.what() << std::endl; return false; } #elif defined(_WIN32) // Native Windows execution (dangerous without sandboxing) typedef void* (*ModuleEntryPoint)(ClientCallbacks*); ModuleEntryPoint entryPoint = reinterpret_cast(moduleMemory_); std::cout << "[WardenModule] Calling module entry point at " << moduleMemory_ << std::endl; // NOTE: This would execute native x86 code // Extremely dangerous without proper validation! // void* result = entryPoint(&callbacks); std::cout << "[WardenModule] ⚠ Module entry point call is DISABLED (unsafe without validation)" << std::endl; std::cout << "[WardenModule] Would execute x86 code at " << moduleMemory_ << std::endl; // TODO: Extract WardenFuncList from result // funcList_.packetHandler = ... // funcList_.tick = ... // funcList_.generateRC4Keys = ... // funcList_.unload = ... #else std::cout << "[WardenModule] ⚠ Cannot execute Windows x86 code on Linux" << std::endl; std::cout << "[WardenModule] Module entry point: " << moduleMemory_ << std::endl; std::cout << "[WardenModule] Would call entry point with ClientCallbacks struct" << std::endl; #endif // For now, return true to mark module as "loaded" at infrastructure level // Real execution would require: // 1. Proper PE parsing to find actual entry point // 2. Calling convention (stdcall/cdecl) handling // 3. Exception handling for crashes // 4. Sandboxing for security std::cout << "[WardenModule] ⚠ Module initialization is STUB" << std::endl; return true; // Stub implementation } // ============================================================================ // WardenModuleManager Implementation // ============================================================================ WardenModuleManager::WardenModuleManager() { // Set cache directory: ~/.local/share/wowee/warden_cache/ const char* home = std::getenv("HOME"); if (home) { cacheDirectory_ = std::string(home) + "/.local/share/wowee/warden_cache"; } else { cacheDirectory_ = "./warden_cache"; } // Create cache directory if it doesn't exist std::filesystem::create_directories(cacheDirectory_); std::cout << "[WardenModuleManager] Cache directory: " << cacheDirectory_ << std::endl; } WardenModuleManager::~WardenModuleManager() { modules_.clear(); } bool WardenModuleManager::hasModule(const std::vector& md5Hash) { // Check in-memory cache if (modules_.find(md5Hash) != modules_.end()) { return modules_[md5Hash]->isLoaded(); } // Check disk cache std::vector dummy; return loadCachedModule(md5Hash, dummy); } std::shared_ptr WardenModuleManager::getModule(const std::vector& md5Hash) { auto it = modules_.find(md5Hash); if (it != modules_.end()) { return it->second; } // Create new module instance auto module = std::make_shared(); modules_[md5Hash] = module; return module; } bool WardenModuleManager::receiveModuleChunk(const std::vector& md5Hash, const std::vector& chunkData, bool isComplete) { // Append to download buffer std::vector& buffer = downloadBuffer_[md5Hash]; buffer.insert(buffer.end(), chunkData.begin(), chunkData.end()); std::cout << "[WardenModuleManager] Received chunk (" << chunkData.size() << " bytes, total: " << buffer.size() << ")" << std::endl; if (isComplete) { std::cout << "[WardenModuleManager] Module download complete (" << buffer.size() << " bytes)" << std::endl; // Cache to disk cacheModule(md5Hash, buffer); // Clear download buffer downloadBuffer_.erase(md5Hash); return true; } return true; } bool WardenModuleManager::cacheModule(const std::vector& md5Hash, const std::vector& moduleData) { std::string cachePath = getCachePath(md5Hash); std::ofstream file(cachePath, std::ios::binary); if (!file) { std::cerr << "[WardenModuleManager] Failed to write cache: " << cachePath << std::endl; return false; } file.write(reinterpret_cast(moduleData.data()), moduleData.size()); file.close(); std::cout << "[WardenModuleManager] Cached module to: " << cachePath << std::endl; return true; } bool WardenModuleManager::loadCachedModule(const std::vector& md5Hash, std::vector& moduleDataOut) { std::string cachePath = getCachePath(md5Hash); if (!std::filesystem::exists(cachePath)) { return false; } std::ifstream file(cachePath, std::ios::binary | std::ios::ate); if (!file) { return false; } size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); moduleDataOut.resize(fileSize); file.read(reinterpret_cast(moduleDataOut.data()), fileSize); file.close(); std::cout << "[WardenModuleManager] Loaded cached module (" << fileSize << " bytes)" << std::endl; return true; } std::string WardenModuleManager::getCachePath(const std::vector& md5Hash) { // Convert MD5 hash to hex string for filename std::string hexHash; hexHash.reserve(md5Hash.size() * 2); for (uint8_t byte : md5Hash) { char buf[3]; snprintf(buf, sizeof(buf), "%02x", byte); hexHash += buf; } return cacheDirectory_ + "/" + hexHash + ".wdn"; } } // namespace game } // namespace wowee