varendew 5 Posted July 1, 2025 Share Posted July 1, 2025 I highly do not recommend using Wrobot on warmane. Nowadays, it seems that they have an anti bot software that could detect anyone using the program. Yesterday, I was just logging in to one of my accounts and tried to make a rotation profile for my feral druid. I only used the WRotation and tested it on the training dummy just for like 1-2 mins at a time. I logged today and just found out my account has been permanently banned. So be careful if ever you're trying this on warmane servers. Bot at your own risk. Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/ Share on other sites More sharing options...
nax 11 Posted July 2, 2025 Share Posted July 2, 2025 Warmane does detect wrobot. Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70226 Share on other sites More sharing options...
Pepa 8 Posted July 30, 2025 Share Posted July 30, 2025 They probably do this with the help of Blizzard Warden and wrobot leaves a unique signature that is the same for everyone with bot . I recommend creating a random fake hash report on running processes and not the static warden defaulting by wrobot . and other more randomized the combat rotations (wrotation) . from chat bot ( how detect wow bots ) : Detection Methods Behavioral Analysis Monitor movement patterns for repetitive, non-human-like behavior (perfect straight lines, identical timing intervals) Track combat patterns that are too consistent or optimal for human players Look for players who never take breaks or play for impossibly long periods Check for instant reactions that are faster than human response times Technical Detection Implement CAPTCHA challenges during gameplay at random intervals Monitor memory access patterns and detect common bot injection methods Check for multiple clients running from the same IP address Analyze network traffic patterns for automated requests Statistical Monitoring Track experience/gold gain rates that exceed normal human capabilities Monitor auction house activity for repetitive buying/selling patterns Flag accounts with unusual progression speeds or resource accumulation Prevention Measures Server-Side Protection Implement Warden-like anti-cheat systems that scan client memory Add randomized server delays to disrupt bot timing Use hardware fingerprinting to detect multiple accounts from same machine Implement rate limiting on certain actions (looting, crafting, etc.) Active Monitoring Set up automated alerts for suspicious activity patterns Have GMs periodically test suspected bots with direct interaction Monitor popular botting locations more closely Track players who ignore chat or don't respond to GM messages Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70250 Share on other sites More sharing options...
Pepa 8 Posted August 1, 2025 Share Posted August 1, 2025 I have information from a bribed member of the development team of the server Warmane. They apply a delayed ban to make it harder to bypass bot detection. Marked accounts in the pre-ban phase get kicked from the server as soon as they perform a get all scan using the auctioner addon Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70258 Share on other sites More sharing options...
nax 11 Posted August 1, 2025 Share Posted August 1, 2025 You deserve a refund. They’re not using the Auctioneer addon for detection at all — that’s just a cover. In reality, they’re exploiting GetThreadContext to extract breakpoints from WRobot through a remote code execution (RCE) technique. Additionally, they’re manipulating movement flags to gain unfair advantages. Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70259 Share on other sites More sharing options...
Pepa 8 Posted August 8, 2025 Share Posted August 8, 2025 On 8/1/2025 at 6:42 PM, nax said: You deserve a refund. They’re not using the Auctioneer addon for detection at all — that’s just a cover. In reality, they’re exploiting GetThreadContext to extract breakpoints from WRobot through a remote code execution (RCE) technique. Additionally, they’re manipulating movement flags to gain unfair advantages. Try on TEST account throught VPN and modify wow.exe with hexaeditor : Find: FF 75 08 FF 75 0C Replace: 6A 00 6A 00 90 90 Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70262 Share on other sites More sharing options...
Leitsgeheier 0 Posted December 17, 2025 Share Posted December 17, 2025 On 8/8/2025 at 8:28 PM, Pepa said: Try on TEST account throught VPN and modify wow.exe with hexaeditor : Find: FF 75 08 FF 75 0C Replace: 6A 00 6A 00 90 90 and then? they couldnt ban me again? ive got banned yesterday. too bad, i bouht a key for 1 year. and now after 3 day playing ... im doomed by Warmane. 2023 2024 i botted like hell,all accounts, nothing was happend. how can we protect us now for detecting? example autoquest, Wrotation etc. Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70900 Share on other sites More sharing options...
Move2 0 Posted December 21, 2025 Share Posted December 21, 2025 Easiest way to protect your accounts? Don't use wrobot on warmane Thats all you need... Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-70906 Share on other sites More sharing options...
Pepa 8 Posted 19 hours ago Share Posted 19 hours ago is this what it does: ?: Blizzard reserved a 4 KB RWX memory page inside wow.exe named .zdata Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-71032 Share on other sites More sharing options...
Pepa 8 Posted 19 hours ago Share Posted 19 hours ago Try this dll injector for what Warmane does with the wowo client, I don't play on a pvp server. USE VPN and soma thrash TESTing wow account !!! wow_launcher.c /* * wow_capture_launcher.exe - spawns Wow.exe suspended, injects * wow_capture.dll via CreateRemoteThread/LoadLibraryW, resumes. * * 32-bit. wow.exe path is hardcoded below - edit WOW_EXE_PATH if needed. */ #include <windows.h> #include <stdio.h> static void die(const wchar_t *what, DWORD err, HANDLE proc) { wprintf(L"[!] %s failed: %lu\n", what, err); if (proc) TerminateProcess(proc, 1); ExitProcess(1); } int main(void) { wchar_t self_dir[MAX_PATH]; wchar_t dll_path[MAX_PATH]; wchar_t wow_path[MAX_PATH]; wchar_t wow_dir [MAX_PATH]; /* directory of this launcher */ GetModuleFileNameW(NULL, self_dir, MAX_PATH); for (int i = (int)lstrlenW(self_dir) - 1; i >= 0; i--) { if (self_dir[i] == L'\\' || self_dir[i] == L'/') { self_dir[i + 1] = 0; break; } } lstrcpyW(dll_path, self_dir); lstrcatW(dll_path, L"wow_capture.dll"); /* verify DLL exists */ DWORD attr = GetFileAttributesW(dll_path); if (attr == INVALID_FILE_ATTRIBUTES) { wprintf(L"[!] wow_capture.dll not found next to launcher\n %s\n", dll_path); return 1; } /* Wow.exe is expected next to the launcher. */ lstrcpyW(wow_path, self_dir); lstrcatW(wow_path, L"Wow.exe"); /* Working directory = launcher's own directory, without trailing slash. */ lstrcpyW(wow_dir, self_dir); int wd_len = (int)lstrlenW(wow_dir); if (wd_len > 0 && (wow_dir[wd_len - 1] == L'\\' || wow_dir[wd_len - 1] == L'/')) wow_dir[wd_len - 1] = 0; /* Verify Wow.exe exists next to us. */ if (GetFileAttributesW(wow_path) == INVALID_FILE_ATTRIBUTES) { wprintf(L"[!] Wow.exe not found next to launcher\n %s\n", wow_path); return 1; } wprintf(L"[+] wow.exe = %s\n", wow_path); wprintf(L"[+] wow_capture = %s\n", dll_path); /* spawn suspended */ STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi = {0}; if (!CreateProcessW(wow_path, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, wow_dir, &si, &pi)) { die(L"CreateProcessW", GetLastError(), NULL); } wprintf(L"[+] wow.exe PID=%lu (suspended)\n", pi.dwProcessId); /* allocate remote buffer for DLL path */ SIZE_T path_bytes = (lstrlenW(dll_path) + 1) * sizeof(wchar_t); LPVOID remote = VirtualAllocEx(pi.hProcess, NULL, path_bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remote) die(L"VirtualAllocEx", GetLastError(), pi.hProcess); SIZE_T written = 0; if (!WriteProcessMemory(pi.hProcess, remote, dll_path, path_bytes, &written)) die(L"WriteProcessMemory", GetLastError(), pi.hProcess); /* kernel32 base is identical across same-bitness processes (shared), * so LoadLibraryW's address in this process == its address in wow.exe. */ HMODULE k32 = GetModuleHandleW(L"kernel32.dll"); LPTHREAD_START_ROUTINE loadlib = (LPTHREAD_START_ROUTINE)GetProcAddress(k32, "LoadLibraryW"); if (!loadlib) die(L"GetProcAddress(LoadLibraryW)", GetLastError(), pi.hProcess); HANDLE th = CreateRemoteThread(pi.hProcess, NULL, 0, loadlib, remote, 0, NULL); if (!th) die(L"CreateRemoteThread", GetLastError(), pi.hProcess); wprintf(L"[+] waiting for LoadLibraryW in target...\n"); WaitForSingleObject(th, INFINITE); DWORD dll_base = 0; GetExitCodeThread(th, &dll_base); CloseHandle(th); if (!dll_base) { wprintf(L"[!] LoadLibraryW returned NULL - DLL not loaded\n"); TerminateProcess(pi.hProcess, 1); return 1; } wprintf(L"[+] wow_capture.dll loaded at 0x%08X\n", dll_base); VirtualFreeEx(pi.hProcess, remote, 0, MEM_RELEASE); wprintf(L"[+] resuming main thread\n"); ResumeThread(pi.hThread); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); wprintf(L"[+] done. Play normally until ban.\n" L" Capture output: %s\n", self_dir); return 0; } wow_capture.c /* * wow_capture.dll - RCE payload capture for Wow.exe 3.3.5.12340 * * Injected into wow.exe at start by wow_capture_launcher.exe. * Installs a hardware breakpoint (DR3, execute) on the .zdata entry * (0x009D1000). When anything executes there - which is the RCE payload * Warmane writes into .zdata - the VEH fires, dumps the full 4 KB of * .zdata to disk with a timestamp, logs the hit, sets the Resume Flag, * and lets execution continue normally. DR3 stays armed, so subsequent * calls are also captured. * * Output files (next to the DLL): * wow_capture_log.txt - timestamped event log * wow_rce_dump_<ts>_hit<N>.bin - one 4 KB dump per HWBP hit */ #include <windows.h> #include <tlhelp32.h> #define ZDATA_VA 0x009D1000UL #define ZDATA_SIZE 0x1000UL static HMODULE g_self = NULL; static PVOID g_veh = NULL; static volatile LONG g_hit_count = 0; static CRITICAL_SECTION g_cs; /* ------------------------------------------------------------------ */ /* Paths / logging */ /* ------------------------------------------------------------------ */ static void dll_dir(wchar_t *out, DWORD cap) { GetModuleFileNameW(g_self, out, cap); for (int i = (int)lstrlenW(out) - 1; i >= 0; i--) { if (out[i] == L'\\' || out[i] == L'/') { out[i + 1] = 0; return; } } } static void log_line(const wchar_t *msg) { wchar_t path[MAX_PATH]; dll_dir(path, MAX_PATH); lstrcatW(path, L"wow_capture_log.txt"); HANDLE f = CreateFileW(path, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (f == INVALID_HANDLE_VALUE) return; SetFilePointer(f, 0, NULL, FILE_END); SYSTEMTIME st; GetLocalTime(&st); wchar_t buf[512]; int n = wsprintfW(buf, L"[%04d-%02d-%02d %02d:%02d:%02d.%03d tid=%lu] %s\r\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentThreadId(), msg); DWORD written; WriteFile(f, buf, n * sizeof(wchar_t), &written, NULL); CloseHandle(f); } static void dump_zdata(LONG hit_no) { wchar_t path[MAX_PATH]; dll_dir(path, MAX_PATH); SYSTEMTIME st; GetLocalTime(&st); wchar_t name[96]; wsprintfW(name, L"wow_rce_dump_%04d%02d%02d_%02d%02d%02d_%03d_hit%ld.bin", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, hit_no); lstrcatW(path, name); HANDLE f = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (f == INVALID_HANDLE_VALUE) return; /* Read via SEH-safe copy just in case a future patch makes .zdata * unreadable; on stock wow.exe .zdata is fully readable. */ BYTE tmp[ZDATA_SIZE]; __try { for (DWORD i = 0; i < ZDATA_SIZE; i++) tmp[i] = ((volatile BYTE *)ZDATA_VA)[i]; } __except (EXCEPTION_EXECUTE_HANDLER) { CloseHandle(f); return; } DWORD written; WriteFile(f, tmp, ZDATA_SIZE, &written, NULL); CloseHandle(f); } /* ------------------------------------------------------------------ */ /* Vectored exception handler */ /* ------------------------------------------------------------------ */ static LONG CALLBACK veh(EXCEPTION_POINTERS *ep) { if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_CONTINUE_SEARCH; DWORD addr = (DWORD)(DWORD_PTR)ep->ExceptionRecord->ExceptionAddress; if (addr < ZDATA_VA || addr >= ZDATA_VA + ZDATA_SIZE) return EXCEPTION_CONTINUE_SEARCH; EnterCriticalSection(&g_cs); LONG n = InterlockedIncrement(&g_hit_count); dump_zdata(n); wchar_t msg[256]; wsprintfW(msg, L"HWBP hit #%ld @ 0x%08X (dumped .zdata 4096B)", n, addr); log_line(msg); LeaveCriticalSection(&g_cs); /* Resume Flag: CPU skips the HWBP for exactly one instruction, * then DR3 stays armed for future hits. Shellcode executes * normally after this single-instruction grace period. Any * re-entry to .zdata later will trap again. */ ep->ContextRecord->EFlags |= 0x10000; return EXCEPTION_CONTINUE_EXECUTION; } /* ------------------------------------------------------------------ */ /* HWBP arming */ /* ------------------------------------------------------------------ */ static void arm_thread(DWORD tid) { HANDLE t = OpenThread( THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, tid); if (!t) return; SuspendThread(t); CONTEXT ctx; ZeroMemory(&ctx, sizeof(ctx)); ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (GetThreadContext(t, &ctx)) { if (ctx.Dr3 != ZDATA_VA) { ctx.Dr3 = ZDATA_VA; /* DR7: * L3 (bit 6) = 1 enable DR3 local * RW3 (bits 28-29) = 00 execute * LEN3 (bits 30-31) = 00 one byte (required for exec) */ ctx.Dr7 &= ~((DWORD)0xF0000000u); /* clear RW3 + LEN3 */ ctx.Dr7 &= ~((DWORD)(1u << 7)); /* clear G3 */ ctx.Dr7 |= ((DWORD)(1u << 6)); /* set L3 */ SetThreadContext(t, &ctx); } } ResumeThread(t); CloseHandle(t); } static void arm_all_threads(void) { DWORD self_tid = GetCurrentThreadId(); DWORD self_pid = GetCurrentProcessId(); HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (snap == INVALID_HANDLE_VALUE) return; THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(snap, &te)) { do { if (te.th32OwnerProcessID == self_pid && te.th32ThreadID != self_tid) { arm_thread(te.th32ThreadID); } } while (Thread32Next(snap, &te)); } CloseHandle(snap); } static DWORD WINAPI monitor_thread(LPVOID p) { (void)p; for (;;) { arm_all_threads(); Sleep(200); } } /* ------------------------------------------------------------------ */ /* DllMain */ /* ------------------------------------------------------------------ */ BOOL WINAPI DllMain(HINSTANCE h, DWORD reason, LPVOID r) { (void)r; if (reason != DLL_PROCESS_ATTACH) return TRUE; g_self = h; DisableThreadLibraryCalls(h); InitializeCriticalSection(&g_cs); g_veh = AddVectoredExceptionHandler(1, veh); /* Diagnostic: snapshot .zdata page state at load time. */ MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery((LPCVOID)ZDATA_VA, &mbi, sizeof(mbi))) { wchar_t msg[256]; wsprintfW(msg, L"loaded; .zdata: Base=0x%08X Size=0x%X Protect=0x%08X State=0x%08X", (DWORD)(DWORD_PTR)mbi.BaseAddress, (DWORD)mbi.RegionSize, (DWORD)mbi.Protect, (DWORD)mbi.State); log_line(msg); } else { log_line(L"loaded; VirtualQuery(.zdata) failed"); } /* Initial arm before launcher resumes the main thread. */ arm_all_threads(); CreateThread(NULL, 0, monitor_thread, NULL, 0, NULL); return TRUE; } Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-71033 Share on other sites More sharing options...
Pepa 8 Posted 19 hours ago Share Posted 19 hours ago 1 minute ago, Pepa said: Try this dll injector for what Warmane does with the wowo client, I don't play on a pvp server. wow_launcher.c /* * wow_capture_launcher.exe - spawns Wow.exe suspended, injects * wow_capture.dll via CreateRemoteThread/LoadLibraryW, resumes. * * 32-bit. wow.exe path is hardcoded below - edit WOW_EXE_PATH if needed. */ #include <windows.h> #include <stdio.h> static void die(const wchar_t *what, DWORD err, HANDLE proc) { wprintf(L"[!] %s failed: %lu\n", what, err); if (proc) TerminateProcess(proc, 1); ExitProcess(1); } int main(void) { wchar_t self_dir[MAX_PATH]; wchar_t dll_path[MAX_PATH]; wchar_t wow_path[MAX_PATH]; wchar_t wow_dir [MAX_PATH]; /* directory of this launcher */ GetModuleFileNameW(NULL, self_dir, MAX_PATH); for (int i = (int)lstrlenW(self_dir) - 1; i >= 0; i--) { if (self_dir[i] == L'\\' || self_dir[i] == L'/') { self_dir[i + 1] = 0; break; } } lstrcpyW(dll_path, self_dir); lstrcatW(dll_path, L"wow_capture.dll"); /* verify DLL exists */ DWORD attr = GetFileAttributesW(dll_path); if (attr == INVALID_FILE_ATTRIBUTES) { wprintf(L"[!] wow_capture.dll not found next to launcher\n %s\n", dll_path); return 1; } /* Wow.exe is expected next to the launcher. */ lstrcpyW(wow_path, self_dir); lstrcatW(wow_path, L"Wow.exe"); /* Working directory = launcher's own directory, without trailing slash. */ lstrcpyW(wow_dir, self_dir); int wd_len = (int)lstrlenW(wow_dir); if (wd_len > 0 && (wow_dir[wd_len - 1] == L'\\' || wow_dir[wd_len - 1] == L'/')) wow_dir[wd_len - 1] = 0; /* Verify Wow.exe exists next to us. */ if (GetFileAttributesW(wow_path) == INVALID_FILE_ATTRIBUTES) { wprintf(L"[!] Wow.exe not found next to launcher\n %s\n", wow_path); return 1; } wprintf(L"[+] wow.exe = %s\n", wow_path); wprintf(L"[+] wow_capture = %s\n", dll_path); /* spawn suspended */ STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi = {0}; if (!CreateProcessW(wow_path, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, wow_dir, &si, &pi)) { die(L"CreateProcessW", GetLastError(), NULL); } wprintf(L"[+] wow.exe PID=%lu (suspended)\n", pi.dwProcessId); /* allocate remote buffer for DLL path */ SIZE_T path_bytes = (lstrlenW(dll_path) + 1) * sizeof(wchar_t); LPVOID remote = VirtualAllocEx(pi.hProcess, NULL, path_bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remote) die(L"VirtualAllocEx", GetLastError(), pi.hProcess); SIZE_T written = 0; if (!WriteProcessMemory(pi.hProcess, remote, dll_path, path_bytes, &written)) die(L"WriteProcessMemory", GetLastError(), pi.hProcess); /* kernel32 base is identical across same-bitness processes (shared), * so LoadLibraryW's address in this process == its address in wow.exe. */ HMODULE k32 = GetModuleHandleW(L"kernel32.dll"); LPTHREAD_START_ROUTINE loadlib = (LPTHREAD_START_ROUTINE)GetProcAddress(k32, "LoadLibraryW"); if (!loadlib) die(L"GetProcAddress(LoadLibraryW)", GetLastError(), pi.hProcess); HANDLE th = CreateRemoteThread(pi.hProcess, NULL, 0, loadlib, remote, 0, NULL); if (!th) die(L"CreateRemoteThread", GetLastError(), pi.hProcess); wprintf(L"[+] waiting for LoadLibraryW in target...\n"); WaitForSingleObject(th, INFINITE); DWORD dll_base = 0; GetExitCodeThread(th, &dll_base); CloseHandle(th); if (!dll_base) { wprintf(L"[!] LoadLibraryW returned NULL - DLL not loaded\n"); TerminateProcess(pi.hProcess, 1); return 1; } wprintf(L"[+] wow_capture.dll loaded at 0x%08X\n", dll_base); VirtualFreeEx(pi.hProcess, remote, 0, MEM_RELEASE); wprintf(L"[+] resuming main thread\n"); ResumeThread(pi.hThread); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); wprintf(L"[+] done. Play normally until ban.\n" L" Capture output: %s\n", self_dir); return 0; } wow_capture.c /* * wow_capture.dll - RCE payload capture for Wow.exe 3.3.5.12340 * * Injected into wow.exe at start by wow_capture_launcher.exe. * Installs a hardware breakpoint (DR3, execute) on the .zdata entry * (0x009D1000). When anything executes there - which is the RCE payload * Warmane writes into .zdata - the VEH fires, dumps the full 4 KB of * .zdata to disk with a timestamp, logs the hit, sets the Resume Flag, * and lets execution continue normally. DR3 stays armed, so subsequent * calls are also captured. * * Output files (next to the DLL): * wow_capture_log.txt - timestamped event log * wow_rce_dump_<ts>_hit<N>.bin - one 4 KB dump per HWBP hit */ #include <windows.h> #include <tlhelp32.h> #define ZDATA_VA 0x009D1000UL #define ZDATA_SIZE 0x1000UL static HMODULE g_self = NULL; static PVOID g_veh = NULL; static volatile LONG g_hit_count = 0; static CRITICAL_SECTION g_cs; /* ------------------------------------------------------------------ */ /* Paths / logging */ /* ------------------------------------------------------------------ */ static void dll_dir(wchar_t *out, DWORD cap) { GetModuleFileNameW(g_self, out, cap); for (int i = (int)lstrlenW(out) - 1; i >= 0; i--) { if (out[i] == L'\\' || out[i] == L'/') { out[i + 1] = 0; return; } } } static void log_line(const wchar_t *msg) { wchar_t path[MAX_PATH]; dll_dir(path, MAX_PATH); lstrcatW(path, L"wow_capture_log.txt"); HANDLE f = CreateFileW(path, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (f == INVALID_HANDLE_VALUE) return; SetFilePointer(f, 0, NULL, FILE_END); SYSTEMTIME st; GetLocalTime(&st); wchar_t buf[512]; int n = wsprintfW(buf, L"[%04d-%02d-%02d %02d:%02d:%02d.%03d tid=%lu] %s\r\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentThreadId(), msg); DWORD written; WriteFile(f, buf, n * sizeof(wchar_t), &written, NULL); CloseHandle(f); } static void dump_zdata(LONG hit_no) { wchar_t path[MAX_PATH]; dll_dir(path, MAX_PATH); SYSTEMTIME st; GetLocalTime(&st); wchar_t name[96]; wsprintfW(name, L"wow_rce_dump_%04d%02d%02d_%02d%02d%02d_%03d_hit%ld.bin", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, hit_no); lstrcatW(path, name); HANDLE f = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (f == INVALID_HANDLE_VALUE) return; /* Read via SEH-safe copy just in case a future patch makes .zdata * unreadable; on stock wow.exe .zdata is fully readable. */ BYTE tmp[ZDATA_SIZE]; __try { for (DWORD i = 0; i < ZDATA_SIZE; i++) tmp[i] = ((volatile BYTE *)ZDATA_VA)[i]; } __except (EXCEPTION_EXECUTE_HANDLER) { CloseHandle(f); return; } DWORD written; WriteFile(f, tmp, ZDATA_SIZE, &written, NULL); CloseHandle(f); } /* ------------------------------------------------------------------ */ /* Vectored exception handler */ /* ------------------------------------------------------------------ */ static LONG CALLBACK veh(EXCEPTION_POINTERS *ep) { if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_CONTINUE_SEARCH; DWORD addr = (DWORD)(DWORD_PTR)ep->ExceptionRecord->ExceptionAddress; if (addr < ZDATA_VA || addr >= ZDATA_VA + ZDATA_SIZE) return EXCEPTION_CONTINUE_SEARCH; EnterCriticalSection(&g_cs); LONG n = InterlockedIncrement(&g_hit_count); dump_zdata(n); wchar_t msg[256]; wsprintfW(msg, L"HWBP hit #%ld @ 0x%08X (dumped .zdata 4096B)", n, addr); log_line(msg); LeaveCriticalSection(&g_cs); /* Resume Flag: CPU skips the HWBP for exactly one instruction, * then DR3 stays armed for future hits. Shellcode executes * normally after this single-instruction grace period. Any * re-entry to .zdata later will trap again. */ ep->ContextRecord->EFlags |= 0x10000; return EXCEPTION_CONTINUE_EXECUTION; } /* ------------------------------------------------------------------ */ /* HWBP arming */ /* ------------------------------------------------------------------ */ static void arm_thread(DWORD tid) { HANDLE t = OpenThread( THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, tid); if (!t) return; SuspendThread(t); CONTEXT ctx; ZeroMemory(&ctx, sizeof(ctx)); ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (GetThreadContext(t, &ctx)) { if (ctx.Dr3 != ZDATA_VA) { ctx.Dr3 = ZDATA_VA; /* DR7: * L3 (bit 6) = 1 enable DR3 local * RW3 (bits 28-29) = 00 execute * LEN3 (bits 30-31) = 00 one byte (required for exec) */ ctx.Dr7 &= ~((DWORD)0xF0000000u); /* clear RW3 + LEN3 */ ctx.Dr7 &= ~((DWORD)(1u << 7)); /* clear G3 */ ctx.Dr7 |= ((DWORD)(1u << 6)); /* set L3 */ SetThreadContext(t, &ctx); } } ResumeThread(t); CloseHandle(t); } static void arm_all_threads(void) { DWORD self_tid = GetCurrentThreadId(); DWORD self_pid = GetCurrentProcessId(); HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (snap == INVALID_HANDLE_VALUE) return; THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(snap, &te)) { do { if (te.th32OwnerProcessID == self_pid && te.th32ThreadID != self_tid) { arm_thread(te.th32ThreadID); } } while (Thread32Next(snap, &te)); } CloseHandle(snap); } static DWORD WINAPI monitor_thread(LPVOID p) { (void)p; for (;;) { arm_all_threads(); Sleep(200); } } /* ------------------------------------------------------------------ */ /* DllMain */ /* ------------------------------------------------------------------ */ BOOL WINAPI DllMain(HINSTANCE h, DWORD reason, LPVOID r) { (void)r; if (reason != DLL_PROCESS_ATTACH) return TRUE; g_self = h; DisableThreadLibraryCalls(h); InitializeCriticalSection(&g_cs); g_veh = AddVectoredExceptionHandler(1, veh); /* Diagnostic: snapshot .zdata page state at load time. */ MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery((LPCVOID)ZDATA_VA, &mbi, sizeof(mbi))) { wchar_t msg[256]; wsprintfW(msg, L"loaded; .zdata: Base=0x%08X Size=0x%X Protect=0x%08X State=0x%08X", (DWORD)(DWORD_PTR)mbi.BaseAddress, (DWORD)mbi.RegionSize, (DWORD)mbi.Protect, (DWORD)mbi.State); log_line(msg); } else { log_line(L"loaded; VirtualQuery(.zdata) failed"); } /* Initial arm before launcher resumes the main thread. */ arm_all_threads(); CreateThread(NULL, 0, monitor_thread, NULL, 0, NULL); return TRUE; } 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. Link to comment https://wrobot.eu/forums/topic/15691-banned-on-warmane/#findComment-71034 Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now