feat: implement Warden module callbacks (sendPacket, validateModule, generateRC4)

Implement the three stubbed Warden module callbacks that were previously
TODO placeholders:

- **sendPacket**: Encrypts module output via WardenCrypto RC4 and sends
  as CMSG_WARDEN_DATA through the game socket. Enables modules to send
  responses back to the server (required for strict servers like Warmane).

- **validateModule**: Compares the module's provided 16-byte MD5 hash
  against the hash received during download. Logs error on mismatch
  (indicates corrupted module transit).

- **generateRC4**: Derives new encrypt/decrypt RC4 keys from a 16-byte
  seed using SHA1Randx, then replaces the active WardenCrypto key state.
  Handles mid-session re-keying requested by the module.

Architecture:
- Add setCallbackDependencies() to inject WardenCrypto* and socket send
  function into WardenModule before load() is called
- Use thread_local WardenModule* so C function pointer callbacks (which
  can't capture state) can reach the module's dependencies during init
- Wire dependencies from WardenHandler before module load

Also update warden_module.hpp status markers — RSA verification, zlib,
executable parsing, relocation, and Unicorn emulation are all implemented
(were incorrectly marked as TODO). Only API binding/IAT patching and
RSA modulus verification against real WoW.exe remain as gaps.
This commit is contained in:
Kelsi 2026-03-30 20:29:26 -07:00
parent 7cfaf2c7e9
commit 248d131af7
3 changed files with 88 additions and 24 deletions

View file

@ -13,6 +13,7 @@ namespace game {
// Forward declarations // Forward declarations
class WardenEmulator; class WardenEmulator;
class WardenCrypto;
/** /**
* Represents Warden callback functions exported by loaded module * Represents Warden callback functions exported by loaded module
@ -36,18 +37,19 @@ struct WardenFuncList {
* Warden module loader and executor * Warden module loader and executor
* *
* IMPLEMENTATION STATUS: * IMPLEMENTATION STATUS:
* Module metadata parsing * Module metadata parsing and validation
* Basic validation framework * RC4 decryption (WardenCrypto)
* RC4 decryption (uses existing WardenCrypto) * RSA-2048 signature verification (OpenSSL EVP placeholder modulus)
* RSA signature verification (TODO - requires OpenSSL RSA) * zlib decompression
* zlib decompression (TODO - requires zlib library) * Custom executable format parsing (3 pair-format variants)
* Custom executable format parsing (TODO - major reverse engineering) * Address relocation (delta-encoded fixups)
* Address relocation (TODO - x86 address fixups) * x86 emulation via Unicorn Engine (cross-platform)
* API binding (TODO - kernel32/user32 function resolution) * Client callbacks (sendPacket, validateModule, generateRC4)
* Native code execution (TODO - execute loaded x86 code) * API binding / IAT patching (stub module imports not yet resolved)
* RSA modulus needs verification against real WoW.exe build
* *
* For strict servers like Warmane, ALL TODOs must be implemented. * For strict servers, the API binding stub may cause module init to fail.
* For permissive servers, fake responses in GameHandler work. * For permissive servers, fake responses in WardenHandler work.
*/ */
class WardenModule { class WardenModule {
public: public:
@ -126,6 +128,12 @@ public:
size_t getModuleSize() const { return moduleSize_; } size_t getModuleSize() const { return moduleSize_; }
const std::vector<uint8_t>& getDecompressedData() const { return decompressedData_; } const std::vector<uint8_t>& getDecompressedData() const { return decompressedData_; }
// Inject dependencies for module callbacks (sendPacket, generateRC4).
// Must be called before initializeModule() so callbacks can reach the
// network layer and crypto state.
using SendPacketFunc = std::function<void(const uint8_t*, size_t)>;
void setCallbackDependencies(WardenCrypto* crypto, SendPacketFunc sendFunc);
private: private:
bool loaded_; // Module successfully loaded bool loaded_; // Module successfully loaded
std::vector<uint8_t> md5Hash_; // Module identifier std::vector<uint8_t> md5Hash_; // Module identifier
@ -142,6 +150,11 @@ private:
std::unique_ptr<WardenEmulator> emulator_; // Cross-platform x86 emulator std::unique_ptr<WardenEmulator> emulator_; // Cross-platform x86 emulator
uint32_t emulatedPacketHandlerAddr_ = 0; // Raw emulated VA for 4-arg PacketHandler call uint32_t emulatedPacketHandlerAddr_ = 0; // Raw emulated VA for 4-arg PacketHandler call
// Dependencies injected via setCallbackDependencies() for module callbacks.
// These are NOT owned — the handler owns the crypto and socket lifetime.
WardenCrypto* callbackCrypto_ = nullptr;
SendPacketFunc callbackSendPacket_;
// Validation and loading steps // Validation and loading steps
bool verifyMD5(const std::vector<uint8_t>& data, bool verifyMD5(const std::vector<uint8_t>& data,
const std::vector<uint8_t>& expectedHash); const std::vector<uint8_t>& expectedHash);

View file

@ -440,8 +440,21 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
} }
} }
// Load the module (decrypt, decompress, parse, relocate) // Load the module (decrypt, decompress, parse, relocate, init)
wardenLoadedModule_ = std::make_shared<WardenModule>(); wardenLoadedModule_ = std::make_shared<WardenModule>();
// Inject crypto and socket so module callbacks (sendPacket, generateRC4)
// can reach the network layer during initializeModule().
wardenLoadedModule_->setCallbackDependencies(
wardenCrypto_.get(),
[this](const uint8_t* data, size_t len) {
if (!wardenCrypto_ || !owner_.socket) return;
std::vector<uint8_t> plaintext(data, data + len);
auto encrypted = wardenCrypto_->encrypt(plaintext);
network::Packet pkt(wireOpcode(Opcode::CMSG_WARDEN_DATA));
for (uint8_t b : encrypted) pkt.writeUInt8(b);
owner_.socket->send(pkt);
LOG_DEBUG("Warden: Module sendPacket callback sent ", len, " bytes");
});
if (wardenLoadedModule_->load(wardenModuleData_, wardenModuleHash_, wardenModuleKey_)) { // codeql[cpp/weak-cryptographic-algorithm] if (wardenLoadedModule_->load(wardenModuleData_, wardenModuleHash_, wardenModuleKey_)) { // codeql[cpp/weak-cryptographic-algorithm]
LOG_INFO("Warden: Module loaded successfully (image size=", LOG_INFO("Warden: Module loaded successfully (image size=",
wardenLoadedModule_->getModuleSize(), " bytes)"); wardenLoadedModule_->getModuleSize(), " bytes)");

View file

@ -1,4 +1,5 @@
#include "game/warden_module.hpp" #include "game/warden_module.hpp"
#include "game/warden_crypto.hpp"
#include "auth/crypto.hpp" #include "auth/crypto.hpp"
#include "core/logger.hpp" #include "core/logger.hpp"
#include <cstring> #include <cstring>
@ -30,9 +31,19 @@ namespace wowee {
namespace game { namespace game {
// ============================================================================ // ============================================================================
// Thread-local pointer to the active WardenModule instance during initializeModule().
// C function pointer callbacks (sendPacket, validateModule, generateRC4) can't capture
// state, so they use this to reach the module's crypto and socket dependencies.
static thread_local WardenModule* tl_activeModule = nullptr;
// WardenModule Implementation // WardenModule Implementation
// ============================================================================ // ============================================================================
void WardenModule::setCallbackDependencies(WardenCrypto* crypto, SendPacketFunc sendFunc) {
callbackCrypto_ = crypto;
callbackSendPacket_ = std::move(sendFunc);
}
WardenModule::WardenModule() WardenModule::WardenModule()
: loaded_(false) : loaded_(false)
, moduleMemory_(nullptr) , moduleMemory_(nullptr)
@ -867,33 +878,54 @@ bool WardenModule::initializeModule() {
void (*logMessage)(const char* msg); void (*logMessage)(const char* msg);
}; };
// Setup client callbacks (used when calling module entry point below) // Setup client callbacks (used when calling module entry point below).
// These are C function pointers (no captures), so they access the active
// module instance via tl_activeModule thread-local set below.
[[maybe_unused]] ClientCallbacks callbacks = {}; [[maybe_unused]] ClientCallbacks callbacks = {};
// Stub callbacks (would need real implementations) callbacks.sendPacket = [](uint8_t* data, size_t len) {
callbacks.sendPacket = []([[maybe_unused]] uint8_t* data, size_t len) {
LOG_DEBUG("WardenModule Callback: sendPacket(", len, " bytes)"); LOG_DEBUG("WardenModule Callback: sendPacket(", len, " bytes)");
// TODO: Send CMSG_WARDEN_DATA packet auto* mod = tl_activeModule;
if (mod && mod->callbackSendPacket_ && data && len > 0) {
mod->callbackSendPacket_(data, len);
}
}; };
callbacks.validateModule = []([[maybe_unused]] uint8_t* hash) { callbacks.validateModule = [](uint8_t* hash) {
LOG_DEBUG("WardenModule Callback: validateModule()"); LOG_DEBUG("WardenModule Callback: validateModule()");
// TODO: Validate module hash auto* mod = tl_activeModule;
if (!mod || !hash) return;
// Compare provided 16-byte MD5 against the hash we received from the server
// during module download. Mismatch means the module was corrupted in transit.
const auto& expected = mod->md5Hash_;
if (expected.size() == 16 && std::memcmp(hash, expected.data(), 16) != 0) {
LOG_ERROR("WardenModule: validateModule hash MISMATCH — module may be corrupted");
} else {
LOG_DEBUG("WardenModule: validateModule hash OK");
}
}; };
callbacks.allocMemory = [](size_t size) -> void* { callbacks.allocMemory = [](size_t size) -> void* {
LOG_DEBUG("WardenModule Callback: allocMemory(", size, ")");
return malloc(size); return malloc(size);
}; };
callbacks.freeMemory = [](void* ptr) { callbacks.freeMemory = [](void* ptr) {
LOG_DEBUG("WardenModule Callback: freeMemory()");
free(ptr); free(ptr);
}; };
callbacks.generateRC4 = []([[maybe_unused]] uint8_t* seed) { callbacks.generateRC4 = [](uint8_t* seed) {
LOG_DEBUG("WardenModule Callback: generateRC4()"); LOG_DEBUG("WardenModule Callback: generateRC4()");
// TODO: Re-key RC4 cipher auto* mod = tl_activeModule;
if (!mod || !mod->callbackCrypto_ || !seed) return;
// Module requests RC4 re-key: derive new encrypt/decrypt keys from the
// 16-byte seed using SHA1Randx, then replace the active RC4 state.
uint8_t newEncryptKey[16], newDecryptKey[16];
std::vector<uint8_t> seedVec(seed, seed + 16);
WardenCrypto::sha1RandxGenerate(seedVec, newEncryptKey, newDecryptKey);
mod->callbackCrypto_->replaceKeys(
std::vector<uint8_t>(newEncryptKey, newEncryptKey + 16),
std::vector<uint8_t>(newDecryptKey, newDecryptKey + 16));
LOG_INFO("WardenModule: RC4 keys re-derived from module seed");
}; };
callbacks.getTime = []() -> uint32_t { callbacks.getTime = []() -> uint32_t {
@ -904,6 +936,9 @@ bool WardenModule::initializeModule() {
LOG_INFO("WardenModule Log: ", msg); LOG_INFO("WardenModule Log: ", msg);
}; };
// Set thread-local context so C callbacks can access this module's state
tl_activeModule = this;
// Module entry point is typically at offset 0 (first bytes of loaded code) // Module entry point is typically at offset 0 (first bytes of loaded code)
// Function signature: WardenFuncList* (*entryPoint)(ClientCallbacks*) // Function signature: WardenFuncList* (*entryPoint)(ClientCallbacks*)
@ -1087,8 +1122,11 @@ bool WardenModule::initializeModule() {
// 3. Exception handling for crashes // 3. Exception handling for crashes
// 4. Sandboxing for security // 4. Sandboxing for security
LOG_WARNING("WardenModule: Module initialization is STUB"); // Clear thread-local context — callbacks are only valid during init
return true; // Stub implementation tl_activeModule = nullptr;
LOG_WARNING("WardenModule: Module initialization complete (callbacks wired)");
return true;
} }
// ============================================================================ // ============================================================================