Warden: WoW's Anti-Cheat Is a Scanner the Server Loads Into Your Client

Updated October 16, 2026: A pseudonymous researcher has published a design-level writeup of Warden, the anti-cheat that has ridden inside World of Warcraft clients since the game's 2004 launch, mapping its module pool, its RC4 key chain, and its nine check types for the 3.3.5a client. The same writeup documents how a packet dumper reads the whole Warden conversation, and why the tool's hook has to sit at the client's Warden handler to survive the login burst.

By Indie Kings | October 16, 2026

The sentence a reader repeats to a friend: Warden does not scan anything by itself. The client ships an almost empty loader, the server hands it a small signed program at every login, and the server then drives that program with commands, so the anti-cheat packets are a live feed of what Blizzard-era servers poke at in your process.

The writeup under review, Writing a Warden Packet Dumper for 3.3.5a, went up on backend-development.net on October 4, 2026, tagged warden, anti-cheat, world of warcraft, wotlk, 3.3.5a, reverse engineering, packet analysis, rc4, game hacking, memory hacking, cmangos, private server, c#, and hardware breakpoints. The author publishes under a pseudonym and nothing on the site verifies who they are, so treat every claim here as attributed, not confirmed. The writeup opens with an educational-purposes-only disclaimer. Using tools like the one it describes against a live game breaches the game's terms of service, and the writeup itself targets 3.3.5a-era clients and private server cores such as CMaNGOS.

Linux terminal screenshot

Image: Linux terminal screenshot. Credit: Wikimedia Commons.

The image above is a generic terminal screenshot, not from the writeup or the game.

What Warden Actually Is

Per the writeup, most people picture Warden as a monolithic scanner baked into WoW.exe. The author says that is wrong. The client ships almost nothing: Warden is a tiny loader in the game process that waits for the server to hand it a module and then follows orders.

Those orders read less like a test and more like a command stream. The author's list includes "read these 8 bytes at this address and send them back," "hash this file," "is this driver loaded," and "what's the value of this Lua variable." The client runs each command and reports the answer. The judgment about whether any answer means cheating happens entirely server side.

He frames the result as a remote-controlled scanner running inside your game, with the server as the brain and the module as the hands. Every interesting choice, which addresses get read and which files get hashed, is decided server side and pushed down the wire. That framing is also his reason for the whole project: the packets are a live feed of what the anti-cheat is actually looking for.

The Module Pool

The module is a small blob, a few kilobytes of compressed and encrypted x86 code that gets mapped into the client. Per the writeup, a server does not ship one module. It ships a pool, and on the author's setup that pool held 72 usable Windows modules plus one underpowered Mac module, with one module handed out at random per session.

The variety is the defense. Each module scrambles its own internals, so the numeric opcodes that mean "read memory" or "hash a file" differ in every module, and function offsets move around. Anything hardcoded against one module's layout breaks the moment the server hands out a different one at the next login. The author calls the variety the point.

On the server side, per the writeup, each module is three files: a .bin holding the compressed and RC4-encrypted binary that actually gets sent, a key file, and a .cr file carrying that module's opcode table plus a thousand pre-baked challenge/response pairs. The server reads those files. The client never sees them.

A Login, Step by Step

All Warden traffic rides two wrappers, the writeup says: SMSG_WARDEN_DATA from server to client and CMSG_WARDEN_DATA from client to server, with the first byte of each payload naming a sub-opcode. The handshake the author walked through looks like this.

StepPacketDirectionWhat it carries, per the writeup
1MODULE_USEServer to clientModule MD5, a 16-byte RC4 key, and the module size
2MODULE_MISSINGClient to server"Never seen it, send it", only if not cached
3MODULE_CACHEServer to clientThe module binary in chunks around 500 bytes, repeated
4MODULE_OK or MODULE_FAILEDClient to serverWhether the module loaded
5HASH_REQUESTServer to clientA 16-byte seed picked from a pre-baked entry
6HASH_RESULTClient to serverA 20-byte response that proves the right module loaded
7MODULE_INITIALIZEServer to clientFunction pointers the module needs, for Lua reads, file access, and timing
8CHEAT_CHECKS_REQUEST and CHEAT_CHECKS_RESULTBoth, in a loopBatches of checks and their answers, for the whole session

The writeup names the function pointers handed over in step 7: FrameScript_GetText for Lua reads, the SFile family for file hashing, and GetTickCount for timing. The seed in step 5 comes from one of that module's thousand pre-baked challenge/response pairs, and the module's 20-byte answer is checked against the known answer on the server. That exchange proves the right module really loaded, and, as covered below, it also re-keys the crypto. The author notes the step 8 loop is roughly 99 percent of what you see in a normal session, with the first seven steps just arming the module.

The Crypto Chain

Both directions are RC4, and the keys are born at login. Per the writeup, when you authenticate over SRP6 the client and server share a large session key. Warden runs that key through a SHA-1-based PRNG the author calls SHA1Randx and squeezes out two 16-byte RC4 keys, one for the server-to-client stream and one for client-to-server. A single XOR byte, the first byte of the input key, is kept alongside them to mask a couple of packet fields.

The challenge exchange in the handshake then does something the author calls sneaky: once the 20-byte response checks out, both streams get re-keyed from the client and server keys stored in that challenge/response entry, and the XOR byte is replaced with the first byte of the new client key. So the crypto protecting the module transfer is not the crypto protecting the scan loop, a detail the author says bit him during his first dumper attempt.

He also describes the cipher as the stock WoW SARC4 variant, RC4 with two running index bytes tacked onto the 256-byte S-box, making each key state 0x102 bytes. The client keeps both live key states in memory, hanging off one module pointer, and that is the writeup's central observation: the keys are sitting right there in memory, so grabbing a copy is enough to decrypt a copy of any packet.

The Nine Checks

A single CHEAT_CHECKS_REQUEST is a list, and per the writeup there are nine check types. The author calls this list the real meat of the system and the vocabulary the server uses to poke at your client. The writeup groups the nine across eight named families, one of which has two variants on the wire.

Check familyWire opcodes named in the writeupWhat it does
MemoryCheckREAD_MEMORYReads N bytes at a flat address or at an offset from a named module and sends them back; the writeup says this is how patched code and detours get caught
PageCheckFIND_CODE_BY_HASH, FIND_MEM_IMAGE_CODE_BY_HASHHashes an executable memory region and compares it against a known pattern, optionally restricted to pages backed by an on-disk module image
MpqCheckHASH_CLIENT_FILEOpens a file inside the client's MPQ archives, computes a SHA-1, and sends the hash; catches altered maps, models, and DBC files
ModuleCheckFIND_MODULE_BY_NAMEAsks whether a DLL with a given name is loaded; the name travels as an HMAC, not plaintext
DriverCheckFIND_DRIVER_BY_NAMEChecks for a driver or device object; the writeup calls this the VM and kernel-cheat sniff, naming VirtualBox's VBoxMiniRdrDN and VMware's vmmemctl as examples
ProcCheckAPI_CHECKWalks an API export, skips NOPs, and hashes the jump target if it finds a JMP; the classic use is catching a hooked EndScene
LuaCheckGET_LUA_VARIABLEReads a Lua variable's value on the theory that cheat addons leave fingerprints in the Lua state
TimingCheckCHECK_TIMING_VALUESCompares the game's internal clock against the system tick count several times; mismatch raises suspicion of speedhacks or emulation

On top of the raw results, the writeup says the server runs its own heuristics: flagging a suspicious EndScene hook, including logic to deobfuscate one known bot's hook stub, noting hypervisor drivers, and fingerprinting the machine from SYSTEM_INFO data it reads out. All of that judgment stays server side. On the wire it is just the nine primitives going back and forth.

Request and Reply Formats

The decrypted request format, as laid out in the writeup, starts with a sub-opcode byte, then a string table of length-prefixed strings that ends with a zero-length entry, then a run of per-scan records where only the opcode byte is masked by the XOR byte, then a final terminator byte, also XOR-masked. The string table carries the plaintext bits the scans reference by index, such as module names, file paths, and Lua variable names. Everything else, seeds, hashes, offsets, and lengths, sits in clear.

The masked opcode is the decoding headache. Because each module permutes its opcodes, a MemoryCheck might appear as one value one session and a different value the next. The author's answer was to pull the opcode table out of all 72 modules into a single JSON lookup keyed by module MD5, which is also the module's filename and the hash announced in MODULE_USE. He then recovers the XOR byte by brute force until the packet walk lands exactly on the module's terminator byte, and steps through the scans, each of which has a fixed body size.

The reply format has no labels at all. Per the writeup, a decrypted reply holds a sub-opcode byte, a two-byte length, a four-byte checksum, then one result per check in the order the request asked them. Result size varies by type: a memory read returns a status byte plus the bytes read, an MPQ check returns a status byte plus a 20-byte SHA-1, and the yes-no checks return a single byte. The consequence the author draws: you cannot parse a reply without the request that caused it, and if the results do not land exactly on the length your decode is wrong.

How the Packet Dumper Works

The tool itself, which the author calls Warden Dumper and wrote in C#, follows a one-line idea: see the packet bytes, read the key from memory, decrypt a copy, print. The writeup stresses no injection and no protocol tampering, describing the result as a one-way mirror. This section reports the design only, and this site does not link or endorse the tooling.

Everything hangs off two hooks. At the inbound side, the dumper targets the client's Warden packet handler, which the writeup names as Packet_SMSG_WARDEN_DATA. At the outbound side, it targets the sender the module uses to ship replies, which the writeup names as Warden::Data. The architecture reason the inbound spot matters is the vtable call the handler makes into the module: the payload it sees is still encrypted, giving the tool a consistent place to snapshot both the ciphertext and the key state. The writeup also notes the dumper performs one client-side change to keep the process from detecting an attached debugger, aimed at the IsDebuggerPresent API, and calls that its only modification.

For attaching, the author chose hardware breakpoints on the debug registers rather than patching instruction bytes, so there is nothing in the code for Warden to trip over. One rule he repeats: always decrypt a copy of a snapshot of the key. Decrypting against the client's real key state advances that cipher and desyncs the session, which he points out would break the very thing being watched.

The instructive failure story in the writeup is the login burst. His first version hooked a generic receive function instead of the Warden handler, and while the module streamed down in a flurry of MODULE_CACHE packets at login, everything decoded to garbage. The cause combines the handshake's re-keying with timing: at a plain receive point, packets pile up faster than the module decrypts them, so the key state read from memory no longer matched the packet in hand. Steady-state checks were fine because they arrive one every few seconds. Moving the hook to the Warden handler fixed it, because there receiving and decrypting are the same event, one packet per call, in order.

As a bonus, the dumper rebuilds the module itself. When the server streams an uncached module down in MODULE_CACHE chunks, the tool stitches the chunks back together up to the size announced in MODULE_USE, hashes the result, and writes it out under its MD5 name. Per the writeup, since modules are named after their own MD5, a clean dump matches the on-disk file exactly. The author notes the addresses involved are specific to the 3.3.5a client, build 12340.

FAQ

What is Warden in World of Warcraft?
Per the October 2026 writeup, Warden is not a scanner baked into the client. It is a small loader in the game process that receives a signed module from the server at login and then runs whatever commands that module delivers, reporting results to the server.

Which side decides if you are cheating?
The server, entirely. The client only executes the nine check primitives and sends back results, and the writeup notes the server layers its own heuristics, like EndScene hook flagging and machine fingerprinting, on top of those answers.

Why does Warden use a pool of modules?
Each module in the pool, 72 Windows modules plus one Mac module on the author's setup, permutes its own opcodes and internal offsets. Variety means anything hardcoded against one module's layout breaks at the next login, which the author calls the point.

What kinds of checks does Warden run?
The writeup counts nine check types across eight named families: memory reads, code page hashing, MPQ file hashing, module and driver name lookups, API hook detection, Lua variable reads, and timing comparisons.

Is Warden traffic encrypted?
Yes, both directions use RC4 keys derived from the login's SRP6 session key through a SHA-1-based PRNG, and the challenge exchange re-keys both streams mid-login. The writeup's countermeasure was reading the live key states from client memory, at 0x102 bytes each.

Is running a packet dumper against a game's rules?
Using such tools against a live game breaches its terms of service. This writeup targets 3.3.5a-era clients and private server cores such as CMaNGOS, and the author frames the writeup itself as educational only.

Our Take

In our view, the most durable lesson here is the architecture, not the addresses. Warden's client half is deliberately almost empty, and nearly all of its intelligence lives on the server, which is why a 22-year-old anti-cheat design has never depended on the client keeping secrets it does not have. We also think the writeup is a fair warning to anyone rebuilding 3.3.5a-era servers today: private-server security inherits Blizzard-era client anti-cheat whether the operators want it or not, and the module pool design is specifically built so third-party tooling rots between logins. We cannot vouch for the anonymous author's accuracy, and readers should assume nothing here is independently verified.

Bottom Line

The October 4, 2026 writeup maps Warden end to end for the 3.3.5a client: a server-driven scanner with a pool of 72 Windows modules holding permuted opcodes, an SRP6-derived RC4 key chain that re-keys itself after the login challenge, nine check types ranging from memory reads to driver sniffing, and request and reply packet formats that only parse when you keep them paired. Its documented dumper design hinges on two hooks, one at Packet_SMSG_WARDEN_DATA and one at Warden::Data, hardware breakpoints, and a hook point chosen so inbound packets are decrypted one at a time in order, the detail that kept the login burst readable. Every fact above traces to a pseudonymous, unverified author, and the tool this describes is not linked here.

Sources

Share