mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-03 20:03:50 +00:00
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:
parent
248d131af7
commit
88d047d2fb
3 changed files with 104 additions and 54 deletions
|
|
@ -138,6 +138,10 @@ public:
|
|||
*/
|
||||
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:
|
||||
uc_engine* uc_; // Unicorn engine instance
|
||||
uint32_t moduleBase_; // Module base address
|
||||
|
|
|
|||
|
|
@ -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<uint8_t> WardenEmulator::readData(uint32_t, size_t) { return {}; }
|
||||
void WardenEmulator::hookCode(uc_engine*, uint64_t, uint32_t, void*) {}
|
||||
|
|
|
|||
|
|
@ -115,13 +115,10 @@ bool WardenModule::load(const std::vector<uint8_t>& 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");
|
||||
|
||||
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;
|
||||
if (relocDataOffset_ == 0 || relocDataOffset_ >= decompressedData_.size()) {
|
||||
LOG_WARNING("WardenModule: No relocation/import data — skipping API binding");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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;
|
||||
}
|
||||
|
||||
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");
|
||||
if (pos >= decompressedData_.size()) {
|
||||
LOG_INFO("WardenModule: No import data after relocations");
|
||||
return true;
|
||||
}
|
||||
|
||||
#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)
|
||||
// Parse import table
|
||||
uint32_t iatSlotIndex = 0;
|
||||
int totalImports = 0;
|
||||
int resolvedImports = 0;
|
||||
|
||||
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)");
|
||||
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;
|
||||
};
|
||||
|
||||
// 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)");
|
||||
while (pos < decompressedData_.size()) {
|
||||
std::string libraryName = readString(pos);
|
||||
if (libraryName.empty()) break; // null library name = end of imports
|
||||
|
||||
// Read functions for this library
|
||||
while (pos < decompressedData_.size()) {
|
||||
std::string functionName = readString(pos);
|
||||
if (functionName.empty()) break; // null function name = next library
|
||||
|
||||
totalImports++;
|
||||
|
||||
// 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>&) -> uint32_t {
|
||||
return 0;
|
||||
});
|
||||
LOG_DEBUG("WardenModule: Auto-stubbed ", libraryName, "!", functionName);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true; // Return true to continue (stub implementation)
|
||||
// Patch IAT slot in module image
|
||||
if (resolvedAddr != 0) {
|
||||
uint32_t iatOffset = iatSlotIndex * 4;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
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_);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue