Skip to the content.

Warden Implementation

Status: Partial - the module is loaded, decrypted, unpacked, relocated, its imports bound and its entry point run under Unicorn, and the WardenFuncList it returns is read back. The module’s own packet handler is not used for checks: WardenModule::processCheckRequest has no caller, and WardenHandler builds every reply itself (see Check Responses). WoW Version: 3.3.5a (build 12340); the handler also has paths for 1.12.1 and Turtle WoW-derived realms


Overview

Warden is WoW’s client integrity checking system. The server sends encrypted modules containing native x86 code; the client is expected to load and execute them, then return check results.

Wowee runs the x86 module through Unicorn Engine CPU emulation - in an emulated environment with Windows API hooks, without Wine or a Windows OS. The check replies are built by the client rather than by the module: memory reads are served from a WoW.exe image on disk, and hash requests from pre-computed .cr files.


Loading Pipeline (8 steps)

1. MD5       - Verify module checksum matches server challenge
2. RC4       - Decrypt module payload
3. RSA-2048  - Verify module signature (Blizzard's key, or the realm's - see Crypto Layer below)
4. zlib      - Decompress module
5. Parse     - Unpack Warden's own image format (copy/skip pair stream, or the native header Turtle-derived realms use)
6. Relocate  - Apply base relocations to load address
7. Bind      - Resolve imports (Windows API stubs + Warden callbacks)
8. Init      - Call module entry point via Unicorn Engine

No step’s failure stops the load: WardenModule::load logs it and continues with what it has. A module that did not unpack into an image is not emulated at all, and the handler answers from its own code.


Unicorn Engine Execution

The module entry point is called inside an Unicorn x86 emulator with:

Hooked APIs are VirtualAlloc, VirtualFree, GetTickCount, Sleep, GetCurrentThreadId, GetCurrentProcessId and ReadProcessMemory; any other import is auto-stubbed to return 0. Each hook returns a plausible value without accessing real process memory.

A module whose signature did not verify is run anyway, so a private server’s own module gets its chance. When it faults, that is logged as a warning rather than an error (WardenEmulator::setFailuresExpected), since it is the expected outcome.


Module Cache

After download, the raw (encrypted) module is written to disk:

~/.local/share/wowee/warden_cache/<MD5>.wdn     (%APPDATA%\wowee\warden_cache on Windows)

Nothing reads it back: the client answers every MODULE_USE with MODULE_MISSING, so the module is downloaded and loaded again each session.

The same directory holds <MD5>.cr files - pre-computed challenge/response entries for a module (a 17-byte header carrying the module’s nine check opcodes, then 68-byte entries of seed, reply and the next RC4 key pair). WardenHandler::loadWardenCRFile reads one on MODULE_USE, and a HASH_REQUEST whose seed matches an entry is answered from it.


Crypto Layer

Algorithm Purpose
RC4 Encrypt/decrypt Warden traffic (separate in/out ciphers)
MD5 Module identity hash
SHA1 HMAC, check hashes and SHA1Randx key derivation
RSA-2048 Module signature verification

The signature is checked against Blizzard’s retail modulus (kRetailModulus in src/game/warden_module.cpp, the same across 1.12.1, 2.4.3 and 3.3.5a). A server that signs its own module can name its key as wardenRsaModulus in the expansion profile - 512 hex characters for the 256-byte modulus - and the module is checked against that instead. A mismatch is logged as a warning and loading continues.


Opcodes

Sub-opcodes inside the decrypted payload (include/game/warden_constants.hpp):

Direction Value Name
S->C 0x00 MODULE_USE (module hash, key, size)
S->C 0x01 MODULE_CACHE (module data chunk)
S->C 0x02 CHEAT_CHECKS_REQUEST
S->C 0x03 MODULE_INITIALIZE (no reply)
S->C 0x05 HASH_REQUEST (16-byte seed)
C->S 0x00 MODULE_MISSING
C->S 0x01 MODULE_OK
C->S 0x02 CHEAT_CHECKS_RESULT
C->S 0x04 HASH_RESULT

Check Responses

Check type opcodes differ per module; they are read from the .cr file header and XORed with the request’s last byte. WardenHandler::handleWardenData answers each one:

Check type Reply
READ_MEMORY (MEM) 0x00 + bytes read from the WoW.exe image, or 0xE9 if unmapped
FIND_MEM_IMAGE_CODE_BY_HASH / FIND_CODE_BY_HASH (PAGE_A/B) 0x4A if an HMAC-SHA1 pattern search of the image finds it, else 0x00
HASH_CLIENT_FILE (MPQ) 0x00 + SHA1 of the file via the asset manager, else 0x01
GET_LUA_VARIABLE (LUA) 0x01 (not found)
FIND_MODULE_BY_NAME (MODULE) 0x4A for standard system DLLs, 0x00 otherwise
FIND_DRIVER_BY_NAME (DRIVER) 0x00 (not found)
API_CHECK (PROC) 0x01 (not found)
CHECK_TIMING_VALUES (TIMING) 0x01 + millisecond tick count

The reply is [0x02][uint16 length][uint32 checksum][results], the checksum being the XOR of the five words of SHA1(results).

WardenMemory loads the WoW.exe image the memory and code checks read from. It looks in $WOWEE_INTEGRITY_DIR, a few home-directory locations, and Data/misc / Data/expansions/<id>/misc, and fakes KUSER_SHARED_DATA. Without it, memory checks answer 0xE9.

A HASH_REQUEST with no matching .cr entry gets no reply on WotLK/TBC and Turtle (AzerothCore tolerates the silence and rejects a wrong hash; Turtle-derived realms close the connection) - WOWEE_WARDEN_TURTLE_NO_CR=fallback overrides that for Turtle. 1.12.1 realms are sent a SHA1 of the module image as a fallback.


Key Files

include/game/warden_handler.hpp      - Packet handler interface
src/game/warden_handler.cpp          - handleWardenData, .cr loading, check replies
include/game/warden_constants.hpp    - Sub-opcodes, memory ranges, result codes
include/game/warden_module.hpp       - Module loader interface
src/game/warden_module.cpp           - 8-step pipeline
include/game/warden_emulator.hpp     - Emulator interface
src/game/warden_emulator.cpp         - Unicorn Engine executor + API hooks
include/game/warden_crypto.hpp       - Crypto interface
src/game/warden_crypto.cpp           - RC4 / key derivation
include/game/warden_memory.hpp       - PE image + memory patch interface
src/game/warden_memory.cpp           - WoW.exe PE loader, runtime globals patching, code-pattern search

Performance


Dependencies

Requires libunicorn-dev (Unicorn Engine); CMake defines HAVE_UNICORN when it is found. The client compiles without it and skips emulation. Check replies are built by WardenHandler either way, so they do not depend on it.


References


Last Updated: 2026-09-21