From 82d0b211fb50454adcb21df4a9620bd23b56f24a Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Feb 2026 02:49:58 -0800 Subject: [PATCH] Implement Warden Phase 4: Executable Loader (partial) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added module memory allocation and skip/copy parsing: Executable Format Parser: - Read 4-byte little-endian final code size - Parse alternating skip/copy sections (2-byte length + data) - Skip sections: advance offset without copying - Copy sections: memcpy x86 code to allocated memory - Boundary validation and sanity checks (max 5MB code) Memory Allocation: - Linux: mmap() with PROT_READ|WRITE|EXEC permissions - Windows: VirtualAlloc() with PAGE_EXECUTE_READWRITE - Proper cleanup in unload() (munmap/VirtualFree) - Zero-initialize allocated memory Address Relocations (STUB): - Framework in place for delta-encoded offset parsing - Needs real Warden module data to implement correctly - Currently returns true to continue loading pipeline Load Pipeline Status: ✅ Step 1-5: MD5, RC4, RSA, zlib, exe parsing ⚠️ Step 6: Relocations (stub - needs real module) ⏳ Step 7-8: API binding, initialization Progress: 4/7 phases underway (~1.5 months remaining) Next: Phase 5 (API Binding) - kernel32.dll/user32.dll imports --- docs/WARDEN_MODULE_ARCHITECTURE.md | 37 +++-- src/game/warden_module.cpp | 227 ++++++++++++++++++++++++----- 2 files changed, 215 insertions(+), 49 deletions(-) diff --git a/docs/WARDEN_MODULE_ARCHITECTURE.md b/docs/WARDEN_MODULE_ARCHITECTURE.md index 874eb1bd..1c14267e 100644 --- a/docs/WARDEN_MODULE_ARCHITECTURE.md +++ b/docs/WARDEN_MODULE_ARCHITECTURE.md @@ -102,13 +102,13 @@ Step 1: Verify MD5 ✅ Implemented (uses auth::Crypto::md5) Step 2: RC4 Decrypt ✅ Implemented (standalone RC4 in WardenModule) Step 3: RSA Verify ✅ Implemented (OpenSSL, placeholder modulus) Step 4: zlib Decompress ✅ Implemented (zlib library) -Step 5: Parse Exe ❌ TODO (custom skip/copy format) -Step 6: Relocations ❌ TODO (delta-encoded offsets) +Step 5: Parse Exe ✅ Implemented (custom skip/copy format, mmap/VirtualAlloc) +Step 6: Relocations ⚠️ STUB (needs real module data for delta decoding) Step 7: Bind APIs ❌ TODO (kernel32.dll, user32.dll imports) Step 8: Initialize ❌ TODO (call module entry point) ``` -**Current Behavior**: Steps 1-4 succeed (validation layer complete), steps 5-8 are logged as "NOT IMPLEMENTED". Module is marked as NOT loaded (`loaded_ = false`) until execution layer is complete. +**Current Behavior**: Steps 1-6 implemented (steps 5-6 need real module data), steps 7-8 are logged as "NOT IMPLEMENTED". Module is marked as NOT loaded (`loaded_ = false`) until execution layer is complete. --- @@ -356,17 +356,22 @@ SHA1(module_data + "MAIEV.MOD") padded with 0xBB bytes - PRGA (Pseudo-Random Generation Algorithm) - Used for module decryption (separate from WardenCrypto) -### Phase 4: Executable Loader (TODO - 2-3 weeks) +### Phase 4: Executable Loader (PARTIALLY COMPLETE ⚠️) -- [ ] Parse custom skip/copy executable format +- [x] Parse custom skip/copy executable format - Read alternating skip/copy sections (2-byte length + data) - - Allocate executable memory region + - Allocate executable memory region (mmap on Linux, VirtualAlloc on Windows) - Copy code sections to memory -- [ ] Implement address relocation + - Sanity checks (max 5MB code size, boundary validation) +- [x] Memory allocation with execution permissions + - Linux: mmap with PROT_READ | PROT_WRITE | PROT_EXEC + - Windows: VirtualAlloc with PAGE_EXECUTE_READWRITE + - Proper cleanup in unload() (munmap/VirtualFree) +- [ ] Implement address relocation (STUB) - Parse delta-encoded offsets (multi-byte with high-bit continuation) - Fix absolute references relative to module base address - Update pointer tables -- [ ] Set memory permissions (VirtualProtect equivalent) + - ⚠️ Needs real Warden module data to implement correctly ### Phase 5: API Binding (TODO - 1 week) @@ -408,12 +413,12 @@ SHA1(module_data + "MAIEV.MOD") padded with 0xBB bytes |-------|----------|------------|--------| | Phase 1: Crypto | - | ⭐⭐ | ✅ DONE | | Phase 2: Foundation | - | ⭐ | ✅ DONE | -| Phase 3: Validation | 1 week | ⭐⭐⭐ | ✅ DONE | -| Phase 4: Executable Loader | 2-3 weeks | ⭐⭐⭐⭐⭐ | 🔜 NEXT | -| Phase 5: API Binding | 1 week | ⭐⭐⭐ | ⏳ TODO | +| Phase 3: Validation | - | ⭐⭐⭐ | ✅ DONE | +| Phase 4: Executable Loader | Partial | ⭐⭐⭐⭐⭐ | ⚠️ PARTIAL (needs real module) | +| Phase 5: API Binding | 1 week | ⭐⭐⭐ | 🔜 NEXT | | Phase 6: Execution Engine | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⏳ TODO | | Phase 7: Testing | 1-2 weeks | ⭐⭐⭐⭐ | ⏳ TODO | -| **TOTAL** | **~2 months remaining** | **Very High** | **3/7 done** | +| **TOTAL** | **~1.5 months remaining** | **Very High** | **4/7 underway** | --- @@ -535,6 +540,8 @@ sendWardenResponse(encrypted); --- **Last Updated**: 2026-02-12 -**Status**: Phase 3 (Validation Layer) COMPLETE ✅ -**Next Step**: Phase 4 (Executable Loader) - Parse skip/copy format, allocate memory -**Remaining**: ~2 months (phases 4-7) +**Status**: Phase 4 (Executable Loader) PARTIAL ⚠️ +**What Works**: Module parsing, memory allocation, skip/copy sections +**What's Stubbed**: Relocations (needs real module data to test) +**Next Step**: Phase 5 (API Binding) - Resolve kernel32.dll/user32.dll imports +**Remaining**: ~1.5 months (phases 5-7) diff --git a/src/game/warden_module.cpp b/src/game/warden_module.cpp index 807b05fd..15e3873e 100644 --- a/src/game/warden_module.cpp +++ b/src/game/warden_module.cpp @@ -9,6 +9,11 @@ #include #include +#ifndef _WIN32 + #include + #include +#endif + namespace wowee { namespace game { @@ -66,22 +71,16 @@ bool WardenModule::load(const std::vector& moduleData, } // Step 5: Parse custom executable format - // TODO: Parse skip/copy section structure - // - Read alternating [2-byte length][data] sections - // - Skip sections are ignored, copy sections loaded - std::cout << "[WardenModule] ⏸ Executable format parsing (NOT IMPLEMENTED)" << std::endl; - // if (!parseExecutableFormat(decompressedData_)) { - // return false; - // } + if (!parseExecutableFormat(decompressedData_)) { + std::cerr << "[WardenModule] Executable format parsing failed!" << std::endl; + return false; + } // Step 6: Apply relocations - // TODO: Fix absolute address references - // - Delta-encoded offsets with high-bit continuation - // - Update all pointers relative to module base - std::cout << "[WardenModule] ⏸ Address relocations (NOT IMPLEMENTED)" << std::endl; - // if (!applyRelocations()) { - // return false; - // } + if (!applyRelocations()) { + std::cerr << "[WardenModule] Address relocations failed!" << std::endl; + return false; + } // Step 7: Bind APIs // TODO: Resolve kernel32.dll, user32.dll imports @@ -157,13 +156,28 @@ void WardenModule::generateRC4Keys(uint8_t* packet) { void WardenModule::unload() { if (moduleMemory_) { - // TODO: Free executable memory region - // - Call module's Unload() function first - // - Free allocated memory - // - Clear function pointers + // 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(); @@ -404,26 +418,171 @@ bool WardenModule::decompressZlib(const std::vector& compressed, } bool WardenModule::parseExecutableFormat(const std::vector& exeData) { - // TODO: Parse custom skip/copy executable format - // - // Format: - // [4 bytes] Final code size - // [2 bytes] Skip section length - // [N bytes] Code to skip - // [2 bytes] Copy section length - // [M bytes] Code to copy - // ... (alternating skip/copy until end) - // - // Allocate moduleMemory_ and populate with copy sections + if (exeData.size() < 4) { + std::cerr << "[WardenModule] Executable data too small for header" << std::endl; + return false; + } - return false; // Not implemented + // 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 sections + size_t pos = 4; // Skip 4-byte size header + size_t destOffset = 0; + bool isSkipSection = true; // Alternates: skip, copy, skip, copy, ... + int sectionCount = 0; + + while (pos + 2 <= exeData.size()) { + // Read 2-byte section length (little-endian) + uint16_t sectionLength = exeData[pos] | (exeData[pos + 1] << 8); + pos += 2; + + if (sectionLength == 0) { + break; // End of sections + } + + if (pos + sectionLength > exeData.size()) { + std::cerr << "[WardenModule] Section extends beyond data bounds" << std::endl; + #ifdef _WIN32 + VirtualFree(moduleMemory_, 0, MEM_RELEASE); + #else + munmap(moduleMemory_, moduleSize_); + #endif + moduleMemory_ = nullptr; + return false; + } + + if (isSkipSection) { + // Skip section - advance destination offset without copying + destOffset += sectionLength; + std::cout << "[WardenModule] Skip section: " << sectionLength << " bytes (dest offset now " + << destOffset << ")" << std::endl; + } else { + // Copy section - copy code to module memory + if (destOffset + sectionLength > 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, + sectionLength + ); + + std::cout << "[WardenModule] Copy section: " << sectionLength << " bytes to offset " + << destOffset << std::endl; + + destOffset += sectionLength; + } + + pos += sectionLength; + isSkipSection = !isSkipSection; // Alternate + sectionCount++; + } + + std::cout << "[WardenModule] ✓ Parsed " << sectionCount << " sections, final offset: " + << destOffset << "/" << finalCodeSize << std::endl; + + if (destOffset != finalCodeSize) { + std::cerr << "[WardenModule] WARNING: Final offset " << destOffset + << " doesn't match expected size " << finalCodeSize << std::endl; + } + + return true; } bool WardenModule::applyRelocations() { - // TODO: Fix absolute address references - // - Delta-encoded offsets (multi-byte with high-bit continuation) - // - Update pointers relative to moduleMemory_ base address - return false; // Not implemented + if (!moduleMemory_ || moduleSize_ == 0) { + std::cerr << "[WardenModule] No module memory allocated for relocations" << std::endl; + return false; + } + + // Relocations are embedded in the decompressed data after the executable sections + // Format: Delta-encoded offsets with high-bit continuation + // + // Each offset is encoded as variable-length bytes: + // - If high bit (0x80) is set, read next byte and combine + // - Continue until byte without high bit + // - Final value is delta from previous relocation offset + // + // For each relocation offset: + // - Read 4-byte pointer at that offset + // - Add module base address to make it absolute + // - Write back to memory + + std::cout << "[WardenModule] Applying relocations to module at " << moduleMemory_ << std::endl; + + // NOTE: Relocation data format and location varies by module + // Without a real module to test against, we can't implement this accurately + // This is a placeholder that would need to be filled in with actual logic + // once we have real Warden module data to analyze + + std::cout << "[WardenModule] ⚠ Relocation application is STUB (needs real module data)" << std::endl; + std::cout << "[WardenModule] Would parse delta-encoded offsets and fix absolute references" << std::endl; + + // Placeholder: Assume no relocations or already position-independent + // Real implementation would: + // 1. Find relocation table in module data + // 2. Decode delta offsets + // 3. For each offset: *(uint32_t*)(moduleMemory + offset) += (uintptr_t)moduleMemory_ + + return true; // Return true to continue (stub implementation) } bool WardenModule::bindAPIs() {