#include "game/warden_module.hpp" #include "auth/crypto.hpp" #include "core/logger.hpp" #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #include #endif // Always include the full definition so unique_ptr destructor compiles #include "game/warden_emulator.hpp" 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; { char hexBuf[17] = {}; for (size_t i = 0; i < std::min(md5Hash.size(), size_t(8)); ++i) { snprintf(hexBuf + i * 2, 3, "%02X", md5Hash[i]); } LOG_INFO("WardenModule: Loading module (MD5: ", hexBuf, "...)"); } // Step 1: Verify MD5 hash if (!verifyMD5(moduleData, md5Hash)) { LOG_ERROR("WardenModule: MD5 verification failed; continuing in compatibility mode"); } LOG_INFO("WardenModule: MD5 verified"); // Step 2: RC4 decrypt (Warden protocol-required legacy RC4; server-mandated, cannot be changed) if (!decryptRC4(moduleData, rc4Key, decryptedData_)) { // codeql[cpp/weak-cryptographic-algorithm] LOG_ERROR("WardenModule: RC4 decryption failed; using raw module bytes fallback"); decryptedData_ = moduleData; } LOG_INFO("WardenModule: RC4 decrypted (", decryptedData_.size(), " bytes)"); // Step 3: Verify RSA signature if (!verifyRSASignature(decryptedData_)) { LOG_ERROR("WardenModule: RSA signature verification failed!"); // 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_)) { LOG_ERROR("WardenModule: zlib decompression failed; using decrypted bytes fallback"); decompressedData_ = decryptedData_; } // Step 5: Parse custom executable format if (!parseExecutableFormat(decompressedData_)) { LOG_ERROR("WardenModule: Executable format parsing failed; continuing with minimal module image"); } // Step 6: Apply relocations if (!applyRelocations()) { LOG_ERROR("WardenModule: Address relocations failed; continuing with unrelocated image"); } // Step 7: Bind APIs if (!bindAPIs()) { LOG_ERROR("WardenModule: API binding failed!"); // Note: Currently returns true (stub) on both Windows and Linux } // Step 8: Initialize module if (!initializeModule()) { LOG_ERROR("WardenModule: Module initialization failed; continuing with stub callbacks"); } // Module loading pipeline complete! // Note: Steps 6-8 are stubs/platform-limited, but infrastructure is ready loaded_ = true; // Mark as loaded (infrastructure complete) LOG_INFO("WardenModule: Module loading pipeline COMPLETE"); LOG_INFO("WardenModule: Status: Infrastructure ready, execution stubs in place"); LOG_INFO("WardenModule: Limitations:"); LOG_INFO("WardenModule: - Relocations: needs real module data"); LOG_INFO("WardenModule: - API Binding: Windows only (or Wine on Linux)"); LOG_INFO("WardenModule: - Execution: disabled (unsafe without validation)"); LOG_INFO("WardenModule: For strict servers: Would need to enable actual x86 execution"); return true; } bool WardenModule::processCheckRequest(const std::vector& checkData, [[maybe_unused]] std::vector& responseOut) { if (!loaded_) { LOG_ERROR("WardenModule: Module not loaded, cannot process checks"); return false; } #ifdef HAVE_UNICORN if (emulator_ && emulator_->isInitialized() && funcList_.packetHandler) { LOG_INFO("WardenModule: Processing check request via emulator..."); LOG_INFO("WardenModule: Check data: ", checkData.size(), " bytes"); // Allocate memory for check data in emulated space uint32_t checkDataAddr = emulator_->allocateMemory(checkData.size(), 0x04); if (checkDataAddr == 0) { LOG_ERROR("WardenModule: Failed to allocate memory for check data"); return false; } // Write check data to emulated memory if (!emulator_->writeMemory(checkDataAddr, checkData.data(), checkData.size())) { LOG_ERROR("WardenModule: Failed to write check data"); 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) { LOG_ERROR("WardenModule: Failed to allocate response buffer"); emulator_->freeMemory(checkDataAddr); return false; } try { // Call module's PacketHandler // void PacketHandler(uint8_t* checkData, size_t checkSize, // uint8_t* responseOut, size_t* responseSizeOut) LOG_INFO("WardenModule: Calling PacketHandler..."); // For now, this is a placeholder - actual calling would depend on // the module's exact function signature LOG_WARNING("WardenModule: PacketHandler execution stubbed"); LOG_INFO("WardenModule: Would call emulated function to process checks"); LOG_INFO("WardenModule: This would generate REAL responses (not fakes!)"); // 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) { LOG_ERROR("WardenModule: Exception during PacketHandler: ", e.what()); emulator_->freeMemory(checkDataAddr); emulator_->freeMemory(responseAddr); return false; } } #endif LOG_WARNING("WardenModule: processCheckRequest NOT IMPLEMENTED"); LOG_INFO("WardenModule: Would call module->PacketHandler() here"); // For now, return false to fall back to fake responses in GameHandler return false; } uint32_t WardenModule::tick([[maybe_unused]] 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([[maybe_unused]] 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) { LOG_INFO("WardenModule: Calling module unload callback..."); // TODO: Implement callback when execution layer is complete // funcList_.unload(nullptr); } // Free executable memory region LOG_INFO("WardenModule: Freeing ", moduleSize_, " bytes of executable memory"); #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) { LOG_ERROR("WardenModule: Invalid RC4 key size: ", key.size(), " (expected 16)"); 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) { LOG_ERROR("WardenModule: Data too small for RSA signature (need at least 256 bytes)"); 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 using EVP_PKEY_fromdata (OpenSSL 3.0 compatible) BIGNUM* n = BN_bin2bn(modulus, 256, nullptr); BIGNUM* e = BN_new(); BN_set_word(e, exponent); EVP_PKEY* pkey = nullptr; EVP_PKEY_CTX* ctx = nullptr; std::vector decryptedSig(256); int decryptedLen = -1; { OSSL_PARAM_BLD* bld = OSSL_PARAM_BLD_new(); OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n); OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e); OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(bld); OSSL_PARAM_BLD_free(bld); EVP_PKEY_CTX* fromCtx = EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr); if (fromCtx && EVP_PKEY_fromdata_init(fromCtx) > 0) { EVP_PKEY_fromdata(fromCtx, &pkey, EVP_PKEY_PUBLIC_KEY, params); } if (fromCtx) EVP_PKEY_CTX_free(fromCtx); OSSL_PARAM_free(params); if (pkey) { ctx = EVP_PKEY_CTX_new(pkey, nullptr); if (ctx && EVP_PKEY_verify_recover_init(ctx) > 0 && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) > 0) { size_t outLen = decryptedSig.size(); if (EVP_PKEY_verify_recover(ctx, decryptedSig.data(), &outLen, signature.data(), 256) > 0) { decryptedLen = static_cast(outLen); } } } } BN_free(n); BN_free(e); if (ctx) EVP_PKEY_CTX_free(ctx); if (pkey) EVP_PKEY_free(pkey); if (decryptedLen < 0) { LOG_ERROR("WardenModule: RSA public decrypt failed"); 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) { LOG_INFO("WardenModule: RSA signature verified"); return true; } } LOG_ERROR("WardenModule: RSA signature verification FAILED (hash mismatch)"); LOG_ERROR("WardenModule: NOTE: Using placeholder modulus - extract real modulus from WoW.exe for actual verification"); // For development, return true to proceed (since we don't have real modulus) // TODO: Set to false once real modulus is extracted LOG_WARNING("WardenModule: Skipping RSA verification (placeholder modulus)"); return true; // TEMPORARY - change to false for production } bool WardenModule::decompressZlib(const std::vector& compressed, std::vector& decompressedOut) { if (compressed.size() < 4) { LOG_ERROR("WardenModule: Compressed data too small (need at least 4 bytes for size header)"); return false; } // Read 4-byte uncompressed size (little-endian) uint32_t uncompressedSize = compressed[0] | (compressed[1] << 8) | (compressed[2] << 16) | (compressed[3] << 24); LOG_INFO("WardenModule: Uncompressed size: ", uncompressedSize, " bytes"); // Sanity check (modules shouldn't be larger than 10MB) if (uncompressedSize > 10 * 1024 * 1024) { LOG_ERROR("WardenModule: Uncompressed size suspiciously large: ", uncompressedSize, " bytes"); 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) { LOG_ERROR("WardenModule: inflateInit failed: ", ret); return false; } // Decompress ret = inflate(&stream, Z_FINISH); // Cleanup inflateEnd(&stream); if (ret != Z_STREAM_END) { LOG_ERROR("WardenModule: inflate failed: ", ret); return false; } LOG_INFO("WardenModule: zlib decompression successful (", stream.total_out, " bytes decompressed)"); return true; } bool WardenModule::parseExecutableFormat(const std::vector& exeData) { if (exeData.size() < 4) { LOG_ERROR("WardenModule: Executable data too small for header"); return false; } // Read final code size (little-endian 4 bytes) uint32_t finalCodeSize = exeData[0] | (exeData[1] << 8) | (exeData[2] << 16) | (exeData[3] << 24); LOG_INFO("WardenModule: Final code size: ", finalCodeSize, " bytes"); // Sanity check (executable shouldn't be larger than 5MB) if (finalCodeSize > 5 * 1024 * 1024 || finalCodeSize == 0) { LOG_ERROR("WardenModule: Invalid final code size: ", finalCodeSize); 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_) { LOG_ERROR("WardenModule: VirtualAlloc failed"); return false; } #else moduleMemory_ = mmap( nullptr, finalCodeSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 ); if (moduleMemory_ == MAP_FAILED) { LOG_ERROR("WardenModule: mmap failed: ", strerror(errno)); moduleMemory_ = nullptr; return false; } #endif moduleSize_ = finalCodeSize; std::memset(moduleMemory_, 0, moduleSize_); // Zero-initialize LOG_INFO("WardenModule: Allocated ", moduleSize_, " bytes of executable memory"); auto readU16LE = [&](size_t at) -> uint16_t { return static_cast(exeData[at] | (exeData[at + 1] << 8)); }; enum class PairFormat { CopyDataSkip, // [copy][data][skip] SkipCopyData, // [skip][copy][data] CopySkipData // [copy][skip][data] }; auto tryParsePairs = [&](PairFormat format, std::vector& imageOut, size_t& relocPosOut, size_t& finalOffsetOut, int& pairCountOut) -> bool { imageOut.assign(moduleSize_, 0); size_t pos = 4; // Skip 4-byte final size header size_t destOffset = 0; int pairCount = 0; while (pos + 2 <= exeData.size()) { uint16_t copyCount = 0; uint16_t skipCount = 0; switch (format) { case PairFormat::CopyDataSkip: { copyCount = readU16LE(pos); pos += 2; if (copyCount == 0) { relocPosOut = pos; finalOffsetOut = destOffset; pairCountOut = pairCount; imageOut.resize(moduleSize_); return true; } if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { return false; } std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); pos += copyCount; destOffset += copyCount; if (pos + 2 > exeData.size()) { return false; } skipCount = readU16LE(pos); pos += 2; break; } case PairFormat::SkipCopyData: { if (pos + 4 > exeData.size()) { return false; } skipCount = readU16LE(pos); pos += 2; copyCount = readU16LE(pos); pos += 2; if (skipCount == 0 && copyCount == 0) { relocPosOut = pos; finalOffsetOut = destOffset; pairCountOut = pairCount; imageOut.resize(moduleSize_); return true; } if (destOffset + skipCount > moduleSize_) { return false; } destOffset += skipCount; if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { return false; } std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); pos += copyCount; destOffset += copyCount; break; } case PairFormat::CopySkipData: { if (pos + 4 > exeData.size()) { return false; } copyCount = readU16LE(pos); pos += 2; skipCount = readU16LE(pos); pos += 2; if (copyCount == 0 && skipCount == 0) { relocPosOut = pos; finalOffsetOut = destOffset; pairCountOut = pairCount; imageOut.resize(moduleSize_); return true; } if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { return false; } std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); pos += copyCount; destOffset += copyCount; break; } } if (destOffset + skipCount > moduleSize_) { return false; } destOffset += skipCount; pairCount++; } return false; }; std::vector parsedImage; size_t parsedRelocPos = 0; size_t parsedFinalOffset = 0; int parsedPairCount = 0; PairFormat usedFormat = PairFormat::CopyDataSkip; bool parsed = tryParsePairs(PairFormat::CopyDataSkip, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); if (!parsed) { usedFormat = PairFormat::SkipCopyData; parsed = tryParsePairs(PairFormat::SkipCopyData, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); } if (!parsed) { usedFormat = PairFormat::CopySkipData; parsed = tryParsePairs(PairFormat::CopySkipData, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); } if (parsed) { std::memcpy(moduleMemory_, parsedImage.data(), parsedImage.size()); relocDataOffset_ = parsedRelocPos; const char* formatName = "copy/data/skip"; if (usedFormat == PairFormat::SkipCopyData) formatName = "skip/copy/data"; if (usedFormat == PairFormat::CopySkipData) formatName = "copy/skip/data"; LOG_INFO("WardenModule: Parsed ", parsedPairCount, " pairs using format ", formatName, ", final offset: ", parsedFinalOffset, "/", finalCodeSize); LOG_INFO("WardenModule: Relocation data starts at decompressed offset ", relocDataOffset_, " (", (exeData.size() - relocDataOffset_), " bytes remaining)"); return true; } // Fallback: copy raw payload (without the 4-byte size header) into module memory. // This keeps loading alive for servers where packet flow can continue with hash/check fallbacks. if (exeData.size() > 4) { size_t rawCopySize = std::min(moduleSize_, exeData.size() - 4); std::memcpy(moduleMemory_, exeData.data() + 4, rawCopySize); } relocDataOffset_ = 0; LOG_ERROR("WardenModule: Could not parse copy/skip pairs (all known layouts failed); using raw payload fallback"); return true; } bool WardenModule::applyRelocations() { if (!moduleMemory_ || moduleSize_ == 0) { LOG_ERROR("WardenModule: No module memory allocated for relocations"); 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()) { LOG_INFO("WardenModule: No relocation data available"); 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_) { uint8_t* addr = static_cast(moduleMemory_) + currentOffset; uint32_t val; std::memcpy(&val, addr, sizeof(uint32_t)); val += moduleBase_; std::memcpy(addr, &val, sizeof(uint32_t)); relocCount++; } else { LOG_ERROR("WardenModule: Relocation offset ", currentOffset, " out of bounds (moduleSize=", moduleSize_, ")"); } } { char baseBuf[32]; std::snprintf(baseBuf, sizeof(baseBuf), "0x%X", moduleBase_); LOG_INFO("WardenModule: Applied ", relocCount, " relocations (base=", baseBuf, ")"); } return true; } bool WardenModule::bindAPIs() { if (!moduleMemory_ || moduleSize_ == 0) { LOG_ERROR("WardenModule: No module memory allocated for API binding"); return false; } LOG_INFO("WardenModule: Binding Windows APIs for module..."); // 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 LOG_INFO("WardenModule: Platform: Windows - using GetProcAddress"); HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); HMODULE user32 = GetModuleHandleA("user32.dll"); HMODULE ntdll = GetModuleHandleA("ntdll.dll"); if (!kernel32 || !user32 || !ntdll) { LOG_ERROR("WardenModule: Failed to get module handles"); 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) LOG_WARNING("WardenModule: Windows API binding is STUB (needs PE import table parsing)"); LOG_INFO("WardenModule: Would parse PE headers and patch IAT with resolved addresses"); #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) LOG_WARNING("WardenModule: Platform: Linux - Windows module execution NOT supported"); LOG_INFO("WardenModule: Options:"); LOG_INFO("WardenModule: 1. Run wowee under Wine (provides Windows API layer)"); LOG_INFO("WardenModule: 2. Use a Windows VM"); LOG_INFO("WardenModule: 3. Implement Windows API stubs (limited, complex)"); // For now, we'll return true to continue the loading pipeline // Real execution would fail, but this allows testing the infrastructure LOG_WARNING("WardenModule: Skipping API binding (Linux platform limitation)"); #endif return true; // Return true to continue (stub implementation) } bool WardenModule::initializeModule() { if (!moduleMemory_ || moduleSize_ == 0) { LOG_ERROR("WardenModule: No module memory allocated for initialization"); return false; } LOG_INFO("WardenModule: Initializing Warden module..."); // 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 (used when calling module entry point below) [[maybe_unused]] ClientCallbacks callbacks = {}; // Stub callbacks (would need real implementations) callbacks.sendPacket = []([[maybe_unused]] uint8_t* data, size_t len) { LOG_DEBUG("WardenModule Callback: sendPacket(", len, " bytes)"); // TODO: Send CMSG_WARDEN_DATA packet }; callbacks.validateModule = []([[maybe_unused]] uint8_t* hash) { LOG_DEBUG("WardenModule Callback: validateModule()"); // TODO: Validate module hash }; callbacks.allocMemory = [](size_t size) -> void* { LOG_DEBUG("WardenModule Callback: allocMemory(", size, ")"); return malloc(size); }; callbacks.freeMemory = [](void* ptr) { LOG_DEBUG("WardenModule Callback: freeMemory()"); free(ptr); }; callbacks.generateRC4 = []([[maybe_unused]] uint8_t* seed) { LOG_DEBUG("WardenModule Callback: generateRC4()"); // TODO: Re-key RC4 cipher }; callbacks.getTime = []() -> uint32_t { return static_cast(time(nullptr)); }; callbacks.logMessage = [](const char* msg) { LOG_INFO("WardenModule Log: ", msg); }; // 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 LOG_INFO("WardenModule: Initializing Unicorn emulator..."); emulator_ = std::make_unique(); if (!emulator_->initialize(moduleMemory_, moduleSize_, moduleBase_)) { LOG_ERROR("WardenModule: Failed to initialize emulator"); return false; } // Setup Windows API hooks emulator_->setupCommonAPIHooks(); { char addrBuf[32]; std::snprintf(addrBuf, sizeof(addrBuf), "0x%X", moduleBase_); LOG_INFO("WardenModule: Emulator initialized successfully"); LOG_INFO("WardenModule: Ready to execute module at ", addrBuf); } // Allocate memory for ClientCallbacks structure in emulated space uint32_t callbackStructAddr = emulator_->allocateMemory(sizeof(ClientCallbacks), 0x04); if (callbackStructAddr == 0) { LOG_ERROR("WardenModule: Failed to allocate memory for callbacks"); 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); } { char cbBuf[32]; std::snprintf(cbBuf, sizeof(cbBuf), "0x%X", callbackStructAddr); LOG_INFO("WardenModule: Prepared ClientCallbacks at ", cbBuf); } // Call module entry point // Entry point is typically at module base (offset 0) uint32_t entryPoint = moduleBase_; { char epBuf[32]; std::snprintf(epBuf, sizeof(epBuf), "0x%X", entryPoint); LOG_INFO("WardenModule: Calling module entry point at ", epBuf); } try { // Call: WardenFuncList* InitModule(ClientCallbacks* callbacks) std::vector args = { callbackStructAddr }; uint32_t result = emulator_->callFunction(entryPoint, args); if (result == 0) { LOG_ERROR("WardenModule: Module entry returned NULL"); return false; } { char resBuf[32]; std::snprintf(resBuf, sizeof(resBuf), "0x%X", result); LOG_INFO("WardenModule: Module initialized, WardenFuncList at ", resBuf); } // Read WardenFuncList structure from emulated memory // Structure has 4 function pointers (16 bytes) uint32_t funcAddrs[4] = {}; if (emulator_->readMemory(result, funcAddrs, 16)) { char fb[4][32]; for (int fi = 0; fi < 4; ++fi) std::snprintf(fb[fi], sizeof(fb[fi]), "0x%X", funcAddrs[fi]); LOG_INFO("WardenModule: Module exported functions:"); LOG_INFO("WardenModule: generateRC4Keys: ", fb[0]); LOG_INFO("WardenModule: unload: ", fb[1]); LOG_INFO("WardenModule: packetHandler: ", fb[2]); LOG_INFO("WardenModule: tick: ", fb[3]); // Store function addresses for later use // funcList_.generateRC4Keys = ... (would wrap emulator calls) // funcList_.unload = ... // funcList_.packetHandler = ... // funcList_.tick = ... } LOG_INFO("WardenModule: Module fully initialized and ready!"); } catch (const std::exception& e) { LOG_ERROR("WardenModule: Exception during module initialization: ", e.what()); return false; } #elif defined(_WIN32) // Native Windows execution (dangerous without sandboxing) typedef void* (*ModuleEntryPoint)(ClientCallbacks*); ModuleEntryPoint entryPoint = reinterpret_cast(moduleMemory_); LOG_INFO("WardenModule: Calling module entry point at ", moduleMemory_); // NOTE: This would execute native x86 code // Extremely dangerous without proper validation! // void* result = entryPoint(&callbacks); LOG_WARNING("WardenModule: Module entry point call is DISABLED (unsafe without validation)"); LOG_INFO("WardenModule: Would execute x86 code at ", moduleMemory_); // TODO: Extract WardenFuncList from result // funcList_.packetHandler = ... // funcList_.tick = ... // funcList_.generateRC4Keys = ... // funcList_.unload = ... #else LOG_WARNING("WardenModule: Cannot execute Windows x86 code on Linux"); LOG_INFO("WardenModule: Module entry point: ", moduleMemory_); LOG_INFO("WardenModule: Would call entry point with ClientCallbacks struct"); #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 LOG_WARNING("WardenModule: Module initialization is STUB"); return true; // Stub implementation } // ============================================================================ // WardenModuleManager Implementation // ============================================================================ WardenModuleManager::WardenModuleManager() { // Set cache directory #ifdef _WIN32 if (const char* appdata = std::getenv("APPDATA")) cacheDirectory_ = std::string(appdata) + "\\wowee\\warden_cache"; else cacheDirectory_ = ".\\warden_cache"; #else if (const char* home = std::getenv("HOME")) cacheDirectory_ = std::string(home) + "/.local/share/wowee/warden_cache"; else cacheDirectory_ = "./warden_cache"; #endif // Create cache directory if it doesn't exist std::filesystem::create_directories(cacheDirectory_); LOG_INFO("WardenModuleManager: Cache directory: ", cacheDirectory_); } 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()); LOG_INFO("WardenModuleManager: Received chunk (", chunkData.size(), " bytes, total: ", buffer.size(), ")"); if (isComplete) { LOG_INFO("WardenModuleManager: Module download complete (", buffer.size(), " bytes)"); // 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) { LOG_ERROR("WardenModuleManager: Failed to write cache: ", cachePath); return false; } file.write(reinterpret_cast(moduleData.data()), moduleData.size()); file.close(); LOG_INFO("WardenModuleManager: Cached module to: ", cachePath); 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(); LOG_INFO("WardenModuleManager: Loaded cached module (", fileSize, " bytes)"); 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