From 88d047d2fbd7b668869c1a4fc96331c11e2aaca7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 30 Mar 2026 22:38:05 -0700 Subject: [PATCH] feat: implement Warden API binding / IAT patching for module imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the last major Warden stub — the import table parser that resolves Windows API calls in loaded modules. This is the critical missing piece for strict servers like Warmane. Implementation: - Parse Warden module import table from decompressed data (after relocation entries): alternating libraryName\0 / functionName\0 pairs, terminated by null library name - For each import, look up the emulator's pre-registered stub address (VirtualAlloc, GetTickCount, ReadProcessMemory, etc.) - Auto-stub unrecognized APIs with a no-op returning 0 — prevents module crashes on unimplemented Windows functions - Patch each IAT slot (sequential dwords at module image base) with the resolved stub address - Add WardenEmulator::getAPIAddress() public accessor for IAT lookups - Fix initialization order: bindAPIs() now runs inside initializeModule() after emulator setup but before entry point call The full Warden pipeline is now: RC4 decrypt → RSA verify → zlib decompress → parse executable → relocate → create emulator → register API hooks → bind imports (IAT patch) → call entry point → extract exported functions (packetHandler, tick, generateRC4Keys, unload). --- include/game/warden_emulator.hpp | 4 + src/game/warden_emulator.cpp | 8 ++ src/game/warden_module.cpp | 146 +++++++++++++++++++------------ 3 files changed, 104 insertions(+), 54 deletions(-) diff --git a/include/game/warden_emulator.hpp b/include/game/warden_emulator.hpp index 4fe30e27..9271c978 100644 --- a/include/game/warden_emulator.hpp +++ b/include/game/warden_emulator.hpp @@ -138,6 +138,10 @@ public: */ std::vector readData(uint32_t address, size_t size); + // Look up an already-registered API stub address by DLL and function name. + // Returns 0 if not found. Used by WardenModule::bindAPIs() for IAT patching. + uint32_t getAPIAddress(const std::string& dllName, const std::string& funcName) const; + private: uc_engine* uc_; // Unicorn engine instance uint32_t moduleBase_; // Module base address diff --git a/src/game/warden_emulator.cpp b/src/game/warden_emulator.cpp index a30a6dd6..0fa5bff8 100644 --- a/src/game/warden_emulator.cpp +++ b/src/game/warden_emulator.cpp @@ -216,6 +216,13 @@ uint32_t WardenEmulator::hookAPI(const std::string& dllName, return stubAddr; } +uint32_t WardenEmulator::getAPIAddress(const std::string& dllName, const std::string& funcName) const { + auto libIt = apiAddresses_.find(dllName); + if (libIt == apiAddresses_.end()) return 0; + auto funcIt = libIt->second.find(funcName); + return (funcIt != libIt->second.end()) ? funcIt->second : 0; +} + void WardenEmulator::setupCommonAPIHooks() { LOG_INFO("WardenEmulator: Setting up common Windows API hooks..."); @@ -614,6 +621,7 @@ bool WardenEmulator::freeMemory(uint32_t) { return false; } uint32_t WardenEmulator::getRegister(int) { return 0; } void WardenEmulator::setRegister(int, uint32_t) {} void WardenEmulator::setupCommonAPIHooks() {} +uint32_t WardenEmulator::getAPIAddress(const std::string&, const std::string&) const { return 0; } uint32_t WardenEmulator::writeData(const void*, size_t) { return 0; } std::vector WardenEmulator::readData(uint32_t, size_t) { return {}; } void WardenEmulator::hookCode(uc_engine*, uint64_t, uint32_t, void*) {} diff --git a/src/game/warden_module.cpp b/src/game/warden_module.cpp index 428831c5..b7a16f22 100644 --- a/src/game/warden_module.cpp +++ b/src/game/warden_module.cpp @@ -115,13 +115,10 @@ bool WardenModule::load(const std::vector& moduleData, 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 + // Step 7+8: Initialize module (creates emulator) then bind APIs (patches IAT). + // API binding must happen after emulator setup (needs stub addresses) but before + // the module entry point is called (needs resolved imports). Both are handled + // inside initializeModule(). if (!initializeModule()) { LOG_ERROR("WardenModule: Module initialization failed; continuing with stub callbacks"); } @@ -780,64 +777,99 @@ bool WardenModule::bindAPIs() { LOG_INFO("WardenModule: Binding Windows APIs for module..."); - // Common Windows APIs used by Warden modules: + // The Warden module import table lives in decompressedData_ immediately after + // the relocation entries (which are terminated by a 0x0000 delta). Format: // - // kernel32.dll: - // - VirtualAlloc, VirtualFree, VirtualProtect - // - GetTickCount, GetCurrentThreadId, GetCurrentProcessId - // - Sleep, SwitchToThread - // - CreateThread, ExitThread - // - GetModuleHandleA, GetProcAddress - // - ReadProcessMemory, WriteProcessMemory + // Repeated library blocks until null library name: + // string libraryName\0 + // Repeated function entries until null function name: + // string functionName\0 // - // user32.dll: - // - GetForegroundWindow, GetWindowTextA - // - // ntdll.dll: - // - NtQueryInformationProcess, NtQuerySystemInformation + // Each imported function corresponds to a sequential IAT slot at the start + // of the module image (first N dwords). We patch each with the emulator's + // stub address so calls into Windows APIs land on our Unicorn hooks. - #ifdef _WIN32 - // On Windows: Use GetProcAddress to resolve imports - LOG_INFO("WardenModule: Platform: Windows - using GetProcAddress"); + if (relocDataOffset_ == 0 || relocDataOffset_ >= decompressedData_.size()) { + LOG_WARNING("WardenModule: No relocation/import data — skipping API binding"); + return true; + } - HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); - HMODULE user32 = GetModuleHandleA("user32.dll"); - HMODULE ntdll = GetModuleHandleA("ntdll.dll"); + // Skip past relocation entries (delta-encoded uint16 pairs, 0x0000 terminated) + size_t pos = relocDataOffset_; + while (pos + 2 <= decompressedData_.size()) { + uint16_t delta = decompressedData_[pos] | (decompressedData_[pos + 1] << 8); + pos += 2; + if (delta == 0) break; + } - if (!kernel32 || !user32 || !ntdll) { - LOG_ERROR("WardenModule: Failed to get module handles"); - return false; + if (pos >= decompressedData_.size()) { + LOG_INFO("WardenModule: No import data after relocations"); + return true; + } + + // Parse import table + uint32_t iatSlotIndex = 0; + int totalImports = 0; + int resolvedImports = 0; + + auto readString = [&](size_t& p) -> std::string { + std::string s; + while (p < decompressedData_.size() && decompressedData_[p] != 0) { + s.push_back(static_cast(decompressedData_[p])); + p++; } + if (p < decompressedData_.size()) p++; // skip null terminator + return s; + }; - // 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) + while (pos < decompressedData_.size()) { + std::string libraryName = readString(pos); + if (libraryName.empty()) break; // null library name = end of imports - 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"); + // Read functions for this library + while (pos < decompressedData_.size()) { + std::string functionName = readString(pos); + if (functionName.empty()) break; // null function name = next library - #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) + totalImports++; - 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)"); + // Look up the emulator's stub address for this API + uint32_t resolvedAddr = 0; + #ifdef HAVE_UNICORN + if (emulator_) { + // Check if this API was pre-registered in setupCommonAPIHooks() + resolvedAddr = emulator_->getAPIAddress(libraryName, functionName); + if (resolvedAddr == 0) { + // Not pre-registered — create a no-op stub that returns 0. + // Prevents module crashes on unimplemented APIs (returns + // 0 / NULL / FALSE / S_OK for most Windows functions). + resolvedAddr = emulator_->hookAPI(libraryName, functionName, + [](WardenEmulator&, const std::vector&) -> uint32_t { + return 0; + }); + LOG_DEBUG("WardenModule: Auto-stubbed ", libraryName, "!", functionName); + } + } + #endif - // 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 + // Patch IAT slot in module image + if (resolvedAddr != 0) { + uint32_t iatOffset = iatSlotIndex * 4; + if (iatOffset + 4 <= moduleSize_) { + uint8_t* slot = static_cast(moduleMemory_) + iatOffset; + std::memcpy(slot, &resolvedAddr, 4); + resolvedImports++; + LOG_DEBUG("WardenModule: IAT[", iatSlotIndex, "] = ", libraryName, + "!", functionName, " → 0x", std::hex, resolvedAddr, std::dec); + } + } + iatSlotIndex++; + } + } - return true; // Return true to continue (stub implementation) + LOG_INFO("WardenModule: Bound ", resolvedImports, "/", totalImports, + " API imports (", iatSlotIndex, " IAT slots patched)"); + return true; } bool WardenModule::initializeModule() { @@ -952,9 +984,15 @@ bool WardenModule::initializeModule() { return false; } - // Setup Windows API hooks + // Setup Windows API hooks (VirtualAlloc, GetTickCount, ReadProcessMemory, etc.) emulator_->setupCommonAPIHooks(); + // Bind module imports: parse the import table from decompressed data and + // patch each IAT slot with the emulator's stub address. Must happen after + // setupCommonAPIHooks() (which registers the stubs) and before calling the + // module entry point (which uses the resolved imports). + bindAPIs(); + { char addrBuf[32]; std::snprintf(addrBuf, sizeof(addrBuf), "0x%X", moduleBase_);