feat: implement Warden API binding / IAT patching for module imports

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).
This commit is contained in:
Kelsi 2026-03-30 22:38:05 -07:00
parent 248d131af7
commit 88d047d2fb
3 changed files with 104 additions and 54 deletions

View file

@ -138,6 +138,10 @@ public:
*/ */
std::vector<uint8_t> readData(uint32_t address, size_t size); std::vector<uint8_t> 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: private:
uc_engine* uc_; // Unicorn engine instance uc_engine* uc_; // Unicorn engine instance
uint32_t moduleBase_; // Module base address uint32_t moduleBase_; // Module base address

View file

@ -216,6 +216,13 @@ uint32_t WardenEmulator::hookAPI(const std::string& dllName,
return stubAddr; 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() { void WardenEmulator::setupCommonAPIHooks() {
LOG_INFO("WardenEmulator: Setting up common Windows API hooks..."); 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; } uint32_t WardenEmulator::getRegister(int) { return 0; }
void WardenEmulator::setRegister(int, uint32_t) {} void WardenEmulator::setRegister(int, uint32_t) {}
void WardenEmulator::setupCommonAPIHooks() {} 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; } uint32_t WardenEmulator::writeData(const void*, size_t) { return 0; }
std::vector<uint8_t> WardenEmulator::readData(uint32_t, size_t) { return {}; } std::vector<uint8_t> WardenEmulator::readData(uint32_t, size_t) { return {}; }
void WardenEmulator::hookCode(uc_engine*, uint64_t, uint32_t, void*) {} void WardenEmulator::hookCode(uc_engine*, uint64_t, uint32_t, void*) {}

View file

@ -115,13 +115,10 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
LOG_ERROR("WardenModule: Address relocations failed; continuing with unrelocated image"); LOG_ERROR("WardenModule: Address relocations failed; continuing with unrelocated image");
} }
// Step 7: Bind APIs // Step 7+8: Initialize module (creates emulator) then bind APIs (patches IAT).
if (!bindAPIs()) { // API binding must happen after emulator setup (needs stub addresses) but before
LOG_ERROR("WardenModule: API binding failed!"); // the module entry point is called (needs resolved imports). Both are handled
// Note: Currently returns true (stub) on both Windows and Linux // inside initializeModule().
}
// Step 8: Initialize module
if (!initializeModule()) { if (!initializeModule()) {
LOG_ERROR("WardenModule: Module initialization failed; continuing with stub callbacks"); 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..."); 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: // Repeated library blocks until null library name:
// - VirtualAlloc, VirtualFree, VirtualProtect // string libraryName\0
// - GetTickCount, GetCurrentThreadId, GetCurrentProcessId // Repeated function entries until null function name:
// - Sleep, SwitchToThread // string functionName\0
// - CreateThread, ExitThread
// - GetModuleHandleA, GetProcAddress
// - ReadProcessMemory, WriteProcessMemory
// //
// user32.dll: // Each imported function corresponds to a sequential IAT slot at the start
// - GetForegroundWindow, GetWindowTextA // 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.
// ntdll.dll:
// - NtQueryInformationProcess, NtQuerySystemInformation
#ifdef _WIN32 if (relocDataOffset_ == 0 || relocDataOffset_ >= decompressedData_.size()) {
// On Windows: Use GetProcAddress to resolve imports LOG_WARNING("WardenModule: No relocation/import data — skipping API binding");
LOG_INFO("WardenModule: Platform: Windows - using GetProcAddress"); return true;
}
HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); // Skip past relocation entries (delta-encoded uint16 pairs, 0x0000 terminated)
HMODULE user32 = GetModuleHandleA("user32.dll"); size_t pos = relocDataOffset_;
HMODULE ntdll = GetModuleHandleA("ntdll.dll"); while (pos + 2 <= decompressedData_.size()) {
uint16_t delta = decompressedData_[pos] | (decompressedData_[pos + 1] << 8);
pos += 2;
if (delta == 0) break;
}
if (!kernel32 || !user32 || !ntdll) { if (pos >= decompressedData_.size()) {
LOG_ERROR("WardenModule: Failed to get module handles"); LOG_INFO("WardenModule: No import data after relocations");
return false; 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<char>(decompressedData_[p]));
p++;
} }
if (p < decompressedData_.size()) p++; // skip null terminator
return s;
};
// TODO: Parse module's import table while (pos < decompressedData_.size()) {
// - Find import directory in PE headers std::string libraryName = readString(pos);
// - For each imported DLL: if (libraryName.empty()) break; // null library name = end of imports
// - 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)"); // Read functions for this library
LOG_INFO("WardenModule: Would parse PE headers and patch IAT with resolved addresses"); while (pos < decompressedData_.size()) {
std::string functionName = readString(pos);
if (functionName.empty()) break; // null function name = next library
#else totalImports++;
// 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"); // Look up the emulator's stub address for this API
LOG_INFO("WardenModule: Options:"); uint32_t resolvedAddr = 0;
LOG_INFO("WardenModule: 1. Run wowee under Wine (provides Windows API layer)"); #ifdef HAVE_UNICORN
LOG_INFO("WardenModule: 2. Use a Windows VM"); if (emulator_) {
LOG_INFO("WardenModule: 3. Implement Windows API stubs (limited, complex)"); // 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>&) -> uint32_t {
return 0;
});
LOG_DEBUG("WardenModule: Auto-stubbed ", libraryName, "!", functionName);
}
}
#endif
// For now, we'll return true to continue the loading pipeline // Patch IAT slot in module image
// Real execution would fail, but this allows testing the infrastructure if (resolvedAddr != 0) {
LOG_WARNING("WardenModule: Skipping API binding (Linux platform limitation)"); uint32_t iatOffset = iatSlotIndex * 4;
#endif if (iatOffset + 4 <= moduleSize_) {
uint8_t* slot = static_cast<uint8_t*>(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() { bool WardenModule::initializeModule() {
@ -952,9 +984,15 @@ bool WardenModule::initializeModule() {
return false; return false;
} }
// Setup Windows API hooks // Setup Windows API hooks (VirtualAlloc, GetTickCount, ReadProcessMemory, etc.)
emulator_->setupCommonAPIHooks(); 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]; char addrBuf[32];
std::snprintf(addrBuf, sizeof(addrBuf), "0x%X", moduleBase_); std::snprintf(addrBuf, sizeof(addrBuf), "0x%X", moduleBase_);