The injector Documentation :
wow_capture — Technical Documentation
Hardware-breakpoint based capture tool for the .zdata RCE payload used by Warmane to detect third-party tools inside Wow.exe 3.3.5.12340.
1. Purpose
wow_capture is a diagnostic instrument. Its single job is to observe what code Warmane actually executes inside the Wow.exe process, without modifying the client binary on disk and without letting Warmane notice it is being observed. It answers one yes/no question: “Does Warmane's anti-bot detection run as code inside Wow.exe via the .zdata RCE mechanism, or does it use some other channel?” The answer determines which class of countermeasure is actually needed.
It consists of two small 32-bit native binaries that live next to Wow.exe:
• wow_capture_launcher.exe — a launcher that starts Wow.exe in a suspended state, injects the capture DLL, then resumes it.
• wow_capture.dll — the capture payload itself: installs a vectored exception handler, arms a hardware breakpoint on the .zdata section's first byte, and dumps the 4 KB section to disk every time anything executes inside it.
2. Background: the .zdata RCE mechanism
Wow.exe 3.3.5.12340 contains a non-standard PE section called .zdata, intentionally created by Blizzard. It is 4 KB in size (one page), located at virtual address 0x009D1000, and in its original unpatched form has the characteristics 0xE0000040 — readable, writable, and executable (RWX). On disk the section is a block of zero bytes; it exists purely as a pre-allocated in-memory staging area that server-delivered code can be written into and executed from.
On retail this mechanism was used by Blizzard's Warden anti-cheat to stage scan modules. Private servers such as Warmane re-use the same RWX page as a remote-code-execution channel for their own anti-bot detection: the server sends a shellcode blob, the client writes it into .zdata, jumps to it, the shellcode inspects the process (checking for hardware breakpoints, debug ports, loaded modules, specific memory patterns), and reports results back. If the shellcode is prevented from running — for example by stripping the execute bit via the well-known RCEPatcher — the client crashes or hangs, and the server bans on the disconnect pattern.
A static header patch therefore does not solve the problem. The correct first step is to see, empirically and in full, what the server actually sends. That is what wow_capture does.
3. Architecture overview
At a high level the tool works as follows:
[wow_capture_launcher.exe]
|
| 1. CreateProcessW(Wow.exe, CREATE_SUSPENDED)
v
[Wow.exe, suspended, 1 thread]
|
| 2. VirtualAllocEx + WriteProcessMemory
| (write UTF-16 path 'wow_capture.dll')
|
| 3. CreateRemoteThread(target=LoadLibraryW,
| arg=remote path ptr)
v
[Wow.exe now has wow_capture.dll mapped]
|
| 4. DllMain runs:
| - installs VEH
| - arms DR3 (execute) on all threads at 0x009D1000
| - starts monitor thread (re-arms every 200 ms)
|
| 5. ResumeThread(main)
v
[Wow.exe runs normally; user logs in, plays]
|
| 6. Warmane writes RCE payload into .zdata and jumps to it
| 7. CPU fetches instruction at 0x009D1000
| -> HWBP DR3 matches -> #DB exception
| 8. VEH fires:
| - dumps full .zdata to wow_rce_dump_<ts>.bin
| - logs the hit
| - sets EFlags.RF (Resume Flag)
| 9. EXCEPTION_CONTINUE_EXECUTION
v
[Warmane's payload executes normally, Warmane sees expected reply,
DR3 stays armed for subsequent hits]
4. Component 1: the launcher
4.1 Why suspended creation
Standard DLL injection via CreateRemoteThread on an already-running process always has a race: the target's main thread starts executing WinMain the moment CreateProcessW returns, and by the time you allocate remote memory and spawn the injection thread, Wow.exe has already run thousands of instructions. For this tool that race is fatal — we need our VEH and HWBPs armed before any network traffic reaches Warmane. So the launcher passes CREATE_SUSPENDED to CreateProcessW, which hands back a process whose main thread is parked at the very first instruction of the image entrypoint. Nothing Blizzard-side has executed yet.
4.2 Injection via LoadLibraryW
The injection itself is the classic three-step technique:
• Allocate a small writable region inside Wow.exe using VirtualAllocEx, large enough to hold the full wide-character path to wow_capture.dll.
• Copy the path into that region with WriteProcessMemory.
• Create a remote thread whose entrypoint is LoadLibraryW and whose single argument is the pointer to the remote path string. Windows' thread entry ABI is DWORD WINAPI fn(LPVOID), which matches LoadLibraryW's signature exactly — so the remote thread, when it runs, behaves identically to having called LoadLibraryW(L"wow_capture.dll") from within Wow.exe.
This works because kernel32.dll is mapped at the same base address in every same-bitness process on a given Windows session (it is a known DLL with shared image). GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW") in the launcher therefore returns the exact address at which LoadLibraryW lives inside Wow.exe too.
4.3 Waiting and resuming
WaitForSingleObject(hRemoteThread, INFINITE) blocks until LoadLibraryW returns, which in turn happens only after our DLL's DllMain has finished. GetExitCodeThread retrieves LoadLibraryW's return value — the base address of the loaded DLL, or NULL on failure. A NULL return means something went wrong (missing DLL, missing dependency, DllMain returned FALSE), and the launcher aborts by terminating the suspended process. On success it frees the remote path buffer and calls ResumeThread on the main thread — only then does Wow.exe actually start executing its entrypoint, with our VEH already installed and HWBPs already armed.
5. Component 2: the capture DLL
5.1 The vectored exception handler
A vectored exception handler (VEH) is a process-wide callback installed via AddVectoredExceptionHandler. Unlike structured exception handling (SEH), which is per-frame and requires __try/__except blocks on the call stack, a VEH is global: it sees every exception raised in the process, on any thread, before any SEH handler gets a chance. That property matters here for two reasons:
• The instruction that triggers our HWBP lives in .zdata, not in any __try block we control — only a process-wide handler can intercept the resulting #DB exception.
• We want first right of refusal, so the firstHandler=1 flag passed to AddVectoredExceptionHandler places our VEH at the head of the chain.
The handler's body is deliberately minimal. It filters on EXCEPTION_SINGLE_STEP (code 0x80000004, which is how x86 reports hardware-breakpoint hits) and on the fault address being inside the .zdata range. Anything else is passed through with EXCEPTION_CONTINUE_SEARCH, so legitimate Blizzard exception machinery — Lua error recovery, Warden's own scan handlers, crash dump generation — is entirely unaffected.
5.2 Hardware breakpoints (DR0–DR7)
The x86 debug registers offer four hardware breakpoints, each capable of firing on execute, write, or read/write accesses at any 1, 2, 4, or 8-byte aligned address. Their state is held in registers DR0–DR3 (the four breakpoint addresses) and DR7 (the control register, encoding enable bits, access types, and lengths).
Relevant DR7 bit layout:
bit 0 : L0 local enable for DR0
bit 1 : G0 global enable for DR0
bit 2 : L1 ...
bit 3 : G1
bit 4 : L2
bit 5 : G2
bit 6 : L3 local enable for DR3
bit 7 : G3 global enable for DR3
bits 16-17 : RW0 access type for DR0 (00=exec, 01=write, 11=rw)
bits 18-19 : LEN0 length for DR0 (00=1B, 01=2B, 10=8B, 11=4B)
bits 20-21 : RW1
bits 22-23 : LEN1
bits 24-25 : RW2
bits 26-27 : LEN2
bits 28-29 : RW3
bits 30-31 : LEN3
For our purposes we want DR3 to fire on execute at 0x009D1000, so RW3=00, LEN3=00 (length must be 1 byte in execute mode), L3=1. That gives DR7 |= 0x40 with the high nibble bits 28–31 cleared to zero.
5.3 Why DR3 and not DR0
WRobot itself uses hardware breakpoints for its memory hook infrastructure and tends to claim lower debug registers first. Using DR3 leaves DR0–DR2 free for any future coexistence with WRobot in the same wow.exe instance. For the initial capture test no WRobot is needed and no conflict is possible.
5.4 Per-thread nature and the monitor loop
A critical subtlety of hardware breakpoints is that they are per-thread, not per-process: each thread has its own copy of the debug registers. Setting DR3 on thread A has no effect on thread B, and a newly created thread inherits nothing. Wow.exe is aggressively multi-threaded and creates threads throughout its lifetime (render, sound, network, job workers).
To get full coverage, the DLL takes two complementary actions:
• On load (while the main thread is still parked by the launcher's CREATE_SUSPENDED), it enumerates all existing threads in the current process via CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) and arms DR3 on each of them using SuspendThread / GetThreadContext / SetThreadContext / ResumeThread.
• It then starts a dedicated monitor thread that repeats the same enumeration every 200 ms. Any newly spawned thread is armed on the next tick. New threads will not trigger an HWBP during their first ≤20 0 ms of life, but since Warmane's payload typically runs on long-lived threads (not freshly created ones), this is acceptable.
5.5 Arming without clobbering other state
GetThreadContext with ContextFlags = CONTEXT_DEBUG_REGISTERS reads the current DR0–DR7 values. The arming code checks whether DR3 already equals our target address — if yes, it is already armed from a previous pass and nothing is written, avoiding useless SetThreadContext calls. Otherwise it writes only DR3 and the relevant DR7 bits, preserving whatever the lower breakpoints contain.
5.6 The Resume Flag trick
When a HWBP on execute fires, the exception is raised before the target instruction retires. If the VEH simply returns EXCEPTION_CONTINUE_EXECUTION without any extra handling, the CPU re-fetches the same instruction, re-matches the HWBP, and raises the same exception again — an infinite loop. The x86 architecture provides a purpose-built escape hatch: EFlags.RF (the Resume Flag, bit 16). When RF is set on return from an exception, the CPU temporarily suppresses instruction-breakpoint matching for exactly one instruction, then automatically clears RF as that instruction retires.
This gives us the behaviour we actually want: the very first instruction of Warmane's shellcode runs once unhindered, the shellcode then executes through the rest of .zdata normally, and if anything later re-enters .zdata at the same address (another call, another payload overwrite) the HWBP fires again and produces a new dump. DR3 stays armed the entire time.
5.7 Dumping the payload
Inside the handler, dump_zdata reads the full 4 KB of .zdata into a local buffer byte-by-byte via a volatile pointer (wrapped in __try/__except as a cheap safety net) and writes that buffer to a new file. The filename encodes a millisecond-precision timestamp and a monotonically-incrementing hit counter, so multiple fires during one session produce distinct files and can be ordered precisely.
6. Why hardware breakpoints, not hooks or software breakpoints
Several alternative instrumentation techniques exist. Each was rejected in favour of HWBP for specific reasons:
• Software breakpoint (`INT3`, opcode `0xCC`) — would require writing 0xCC into .zdata, which is exactly what Warmane's payload is going to overwrite a moment later. We would either clobber their write or have our breakpoint clobbered first. Either way, no capture.
• Inline hook (patch a few bytes with a `JMP`) — same problem. Whatever we write into .zdata is ephemeral.
• Page permission flip via `VirtualProtect` — setting .zdata to PAGE_READWRITE and catching the access violation on execute would work, but the ensuing AV is a guest state change that Warmane's payload, or its caller, could conceivably detect. HWBPs are invisible to normal code: they are debug-register state, not memory state, and no VirtualQuery or pointer-read reveals them.
• API hook on `RtlExitUserThread` / `VirtualAlloc` / similar — too far removed from the actual event of interest and would miss payloads delivered without those APIs.
HWBPs are the right tool precisely because they trigger on the CPU's instruction fetch stage, leave zero footprint in memory, and cost literally nothing (one comparator per breakpoint, checked in hardware).
7. Output files
All output is written next to the DLL, which is the Wow.exe directory.
7.1 wow_capture_log.txt
A plain-text UTF-16LE log. One line per event, formatted as:
[YYYY-MM-DD HH:MM:SS.mmm tid=NNNN] message
Expected lines during a healthy session:
• One loaded; .zdata: Base=... Size=... Protect=... State=... line immediately after injection. If Protect reads 0x40 (PAGE_EXECUTE_READWRITE) the client is unpatched; 0x04/0x02 (PAGE_READWRITE/PAGE_READONLY) means the header patch stripped the execute bit.
• Zero or more HWBP hit #N @ 0x009D1000 ... lines — one per capture, with a monotonically increasing N.
7.2 wow_rce_dump_<timestamp>_hit<N>.bin
Raw binary dump of the full 4 KB .zdata section at the instant the HWBP fired. Filename format:
wow_rce_dump_YYYYMMDD_HHMMSS_mmm_hitN.bin
Analyse these with a disassembler of your choice. Good first commands:
objdump -D -b binary -m i386 -M intel \
wow_rce_dump_<...>.bin | less
# or in Ghidra/IDA: import as raw x86, base 0x009D1000
# quick visual scan for obvious API names:
strings -a -n 5 wow_rce_dump_<...>.bin
Typical giveaways of anti-bot logic in the dump: references to NtQueryInformationProcess, GetThreadContext, CheckRemoteDebuggerPresent, or string fragments of window titles and process names the scanner is looking for.
8. Building from source
The build is plain MSVC; no external dependencies. From a developer command prompt with the 32-bit toolchain active:
cd "...\wow_capture"
build.bat
build.bat produces wow_capture.dll and wow_capture_launcher.exe one directory up, next to Wow.exe. The _build_with_vcvars.bat wrapper sets up PATH / INCLUDE / LIB manually for the specific VS 2022 BuildTools install on this machine and then invokes build.bat; it avoids the dependency on vswhere.exe that the stock vcvars32 chain has.
The toolchain assumptions are encoded at the top of the wrapper and are easy to bump if the VS or Windows SDK version changes.
9. Running the tool
• Make sure WRobot is not running for the first capture. This rules out debug-register conflicts with WRobot's own hooks and establishes the baseline behaviour of the server against a stock client.
• Double-click wow_capture_launcher.exe (or run it from a console to see the diagnostic lines). Wow.exe starts through the launcher with the capture DLL already mapped.
• Log into Warmane and play normally. No special actions required; the capture is entirely passive.
• Wait until something observable happens: either a disconnection, a ban, or simply your own decision to end the session after a defined period (say two hours).
• Close Wow.exe. Inspect the Wow.exe directory for wow_capture_log.txt and any wow_rce_dump_*.bin files.
10. Interpreting the results
There are three possible outcomes:
10.1 No dump files, no ban for the whole session
Warmane's anti-bot detection is not running as code inside Wow.exe via .zdata at all. The detection path is something else — most likely behavioural analysis (movement entropy, action timing, playtime, GM whisper responses) or server-side packet heuristics. In this case the entire .zdata research avenue is a dead end; effort should move to humanising WRobot's in-game behaviour instead of hooking the client.
10.2 One or more dump files, eventually a ban
Warmane does use the .zdata RCE and we now have the exact payload on disk. Disassemble the dumps, identify the API calls and memory reads the shellcode performs, and use that knowledge to build a targeted VEH that stubs out whatever check the payload is making — for example, returning a zeroed CONTEXT from GetThreadContext, or flipping NtQueryInformationProcess(ProcessDebugPort) replies to zero. The capture tool itself can be extended to do this in place by replacing the dump-and-continue handler with a patch-and-continue handler.
10.3 No dump files and the log line is missing
Something went wrong at injection time. Check that wow_capture.dll sits next to wow_capture_launcher.exe, that both are 32-bit (launcher output reports the loaded base on success), and that no antivirus has quarantined the DLL. Running the launcher from a console window surfaces the diagnostic text.
11. Limitations and caveats
• First ~200 ms per thread: new threads created by Wow.exe after load are not armed until the monitor's next tick. Highly improbable that Warmane's payload runs on a throw-away thread in its first 200 ms of existence, but theoretically possible.
• HWBP is single-address: we trap on the first byte of .zdata only. If the server ever wrote payloads that started at a non-zero offset within the section, we would miss the trap on the first call. In practice payloads start at the section base because that is the address the server hands to its client-side dispatcher.
• No analysis of the payload in flight: the capture is passive. It snapshots .zdata, lets the code run, and moves on. If the payload mutates itself in memory during execution, we see only the pre-execution state. This is fine for 99%% of real-world shellcodes but would miss a self-modifying staged loader.
• Coexistence with WRobot: WRobot uses some DR registers itself. Running this tool *with* WRobot attached may produce partial coverage or conflicts. Recommended procedure is to first capture without WRobot, disassemble the payload, then design a coexistence strategy informed by what the payload actually does.
• Antivirus noise: CreateRemoteThread + LoadLibraryW is a textbook injection pattern and some real-time scanners flag it. Whitelist the Wow.exe directory or disable scanning for these two binaries if you hit false positives.
12. File inventory
• Wow.exe — the stock client, unpatched (restored from its original 0xE0000040 .zdata characteristics).
• wow_capture_launcher.exe — 32-bit launcher, placed directly in the Wow.exe directory.
• wow_capture.dll — 32-bit capture DLL, placed directly in the Wow.exe directory.
• wow_capture/wow_launcher.c — launcher source.
• wow_capture/wow_capture.c — DLL source.
• wow_capture/build.bat — MSVC build script, outputs binaries one level up.
• wow_capture/_build_with_vcvars.bat — environment-bootstrapping wrapper for the MSVC 2022 BuildTools install on this machine.
By
Pepa ·