From 4b0726d97788edff5d83476cb52057f409d623af Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:01:32 +0000 Subject: Update silver-bun to `72c74b4` (#664) Bumps the vendored silver-bun library to the newest commit in upstream Co-authored-by: F1F7Y Co-authored-by: IcePixelx <41352111+IcePixelx@users.noreply.github.com> --- primedev/thirdparty/silver-bun/CMakeLists.txt | 9 + primedev/thirdparty/silver-bun/memaddr.cpp | 368 ++++++++++++++++++++ primedev/thirdparty/silver-bun/memaddr.h | 154 +++++++++ primedev/thirdparty/silver-bun/module.cpp | 474 ++++++++++++++++++++++++++ primedev/thirdparty/silver-bun/module.h | 76 +++++ primedev/thirdparty/silver-bun/utils.cpp | 127 +++++++ primedev/thirdparty/silver-bun/utils.h | 18 + 7 files changed, 1226 insertions(+) create mode 100644 primedev/thirdparty/silver-bun/CMakeLists.txt create mode 100644 primedev/thirdparty/silver-bun/memaddr.cpp create mode 100644 primedev/thirdparty/silver-bun/memaddr.h create mode 100644 primedev/thirdparty/silver-bun/module.cpp create mode 100644 primedev/thirdparty/silver-bun/module.h create mode 100644 primedev/thirdparty/silver-bun/utils.cpp create mode 100644 primedev/thirdparty/silver-bun/utils.h (limited to 'primedev/thirdparty/silver-bun') diff --git a/primedev/thirdparty/silver-bun/CMakeLists.txt b/primedev/thirdparty/silver-bun/CMakeLists.txt new file mode 100644 index 00000000..c39dd8bc --- /dev/null +++ b/primedev/thirdparty/silver-bun/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library( + silver-bun STATIC + "memaddr.cpp" + "memaddr.h" + "module.cpp" + "module.h" + "utils.cpp" + "utils.h" + ) diff --git a/primedev/thirdparty/silver-bun/memaddr.cpp b/primedev/thirdparty/silver-bun/memaddr.cpp new file mode 100644 index 00000000..929d1682 --- /dev/null +++ b/primedev/thirdparty/silver-bun/memaddr.cpp @@ -0,0 +1,368 @@ +//===========================================================================// +// +// Purpose: Implementation of the CMemory class. +// +// Original commit: https://github.com/IcePixelx/silver-bun/commit/72c74b455bf4d02b424096ad2f30cd65535f814c +// +//===========================================================================// + +#include "memaddr.h" +#include "utils.h" + +//----------------------------------------------------------------------------- +// Purpose: check array of opcodes starting from current address +// Input : &vOpcodeArray - +// Output : true if equal, false otherwise +//----------------------------------------------------------------------------- +bool CMemory::CheckOpCodes(const std::vector& vOpcodeArray) const +{ + uintptr_t ref = ptr; + + // Loop forward in the ptr class member. + for (auto [byteAtCurrentAddress, i] = std::tuple{ uint8_t(), (size_t)0 }; i < vOpcodeArray.size(); i++, ref++) + { + byteAtCurrentAddress = *reinterpret_cast(ref); + + // If byte at ptr doesn't equal in the byte array return false. + if (byteAtCurrentAddress != vOpcodeArray[i]) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Checx if memory is readable +// Input : nSize - +//----------------------------------------------------------------------------- +bool CMemory::IsMemoryReadable(const size_t nSize) const +{ + static SYSTEM_INFO sysInfo; + if (!sysInfo.dwPageSize) + GetSystemInfo(&sysInfo); + + MEMORY_BASIC_INFORMATION memInfo; + if (!VirtualQuery(reinterpret_cast(GetPtr()), &memInfo, sizeof(memInfo))) + return false; + + return memInfo.RegionSize >= nSize && memInfo.State & MEM_COMMIT && !(memInfo.Protect & PAGE_NOACCESS); +} + +//----------------------------------------------------------------------------- +// Purpose: patch size with nop opcodes +// Input : nSize - +//----------------------------------------------------------------------------- +void CMemory::NOP(const size_t nSize) const +{ + std::vector vOpcodeArray; + vOpcodeArray.resize(nSize); + memset(vOpcodeArray.data(), 0x90, nSize); + Patch(vOpcodeArray); +} + +//----------------------------------------------------------------------------- +// Purpose: patch array of opcodes +// Input : *pszOpcodes - +//----------------------------------------------------------------------------- +void CMemory::Patch(const char* pszOpcodes) const +{ + const std::vector vOpcodeArray = Utils::StringPatternToBytes(pszOpcodes); + Patch(vOpcodeArray); +} + +//----------------------------------------------------------------------------- +// Purpose: patch array of opcodes starting from current address +// Input : *pOpcodeArray - +// nSize - +//----------------------------------------------------------------------------- +void CMemory::Patch(const uint8_t* pOpcodeArray, const size_t nSize) const +{ + const std::vector vOpcodeArray(pOpcodeArray, pOpcodeArray + nSize * sizeof(uint8_t)); + Patch(vOpcodeArray); +} + +//----------------------------------------------------------------------------- +// Purpose: patch array of opcodes starting from current address +// Input : &vOpcodeArray - +//----------------------------------------------------------------------------- +void CMemory::Patch(const std::vector& vOpcodeArray) const +{ + DWORD oldProt = NULL; + + SIZE_T dwSize = vOpcodeArray.size(); + VirtualProtect(reinterpret_cast(ptr), dwSize, PAGE_EXECUTE_READWRITE, &oldProt); // Patch page to be able to read and write to it. + + for (size_t i = 0; i < vOpcodeArray.size(); i++) + { + *reinterpret_cast(ptr + i) = vOpcodeArray[i]; // Write opcodes to Address. + } + + dwSize = vOpcodeArray.size(); + VirtualProtect(reinterpret_cast(ptr), dwSize, oldProt, &oldProt); // Restore protection. +} + +//----------------------------------------------------------------------------- +// Purpose: patch string constant at current address +// Input : *szString - +//----------------------------------------------------------------------------- +void CMemory::PatchString(const char* szString) const +{ + DWORD oldProt = NULL; + SIZE_T dwSize = strlen(szString); + + VirtualProtect(reinterpret_cast(ptr), dwSize, PAGE_EXECUTE_READWRITE, &oldProt); // Patch page to be able to read and write to it. + + for (size_t i = 0; i < dwSize; i++) + { + *reinterpret_cast(ptr + i) = szString[i]; // Write string to Address. + } + + VirtualProtect(reinterpret_cast(ptr), dwSize, oldProt, &oldProt); // Restore protection. +} + +//----------------------------------------------------------------------------- +// Purpose: find array of bytes in process memory +// Input : *szPattern - +// searchDirect - +// opCodesToScan - +// occurrence - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::FindPattern(const char* szPattern, const Direction searchDirect, const int opCodesToScan, const ptrdiff_t occurrence) const +{ + uint8_t* pScanBytes = reinterpret_cast(ptr); // Get the base of the module. + + const std::vector PatternBytes = Utils::PatternToBytes(szPattern); // Convert our pattern to a byte array. + const std::pair bytesInfo = std::make_pair(PatternBytes.size(), PatternBytes.data()); // Get the size and data of our bytes. + ptrdiff_t occurrences = 0; + + for (long i = 01; i < opCodesToScan + bytesInfo.first; i++) + { + bool bFound = true; + int nMemOffset = searchDirect == Direction::DOWN ? i : -i; + + for (DWORD j = 0ul; j < bytesInfo.first; j++) + { + // If either the current byte equals to the byte in our pattern or our current byte in the pattern is a wildcard + // our if clause will be false. + uint8_t currentByte = *(pScanBytes + nMemOffset + j); + _mm_prefetch(reinterpret_cast(static_cast(currentByte + nMemOffset + 64)), _MM_HINT_T0); // precache some data in L1. + if (currentByte != bytesInfo.second[j] && bytesInfo.second[j] != -1) + { + bFound = false; + break; + } + } + + if (bFound) + { + occurrences++; + if (occurrence == occurrences) + { + return CMemory(&*(pScanBytes + nMemOffset)); + } + } + } + + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: find array of bytes in process memory starting from current address +// Input : *szPattern - +// searchDirect - +// opCodesToScan - +// occurrence - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::FindPatternSelf(const char* szPattern, const Direction searchDirect, const int opCodesToScan, const ptrdiff_t occurrence) +{ + uint8_t* pScanBytes = reinterpret_cast(ptr); // Get the base of the module. + + const std::vector PatternBytes = Utils::PatternToBytes(szPattern); // Convert our pattern to a byte array. + const std::pair bytesInfo = std::make_pair(PatternBytes.size(), PatternBytes.data()); // Get the size and data of our bytes. + ptrdiff_t occurrences = 0; + + for (long i = 01; i < opCodesToScan + bytesInfo.first; i++) + { + bool bFound = true; + int nMemOffset = searchDirect == Direction::DOWN ? i : -i; + + for (DWORD j = 0ul; j < bytesInfo.first; j++) + { + // If either the current byte equals to the byte in our pattern or our current byte in the pattern is a wildcard + // our if clause will be false. + uint8_t currentByte = *(pScanBytes + nMemOffset + j); + _mm_prefetch(reinterpret_cast(static_cast(currentByte + nMemOffset + 64)), _MM_HINT_T0); // precache some data in L1. + if (currentByte != bytesInfo.second[j] && bytesInfo.second[j] != -1) + { + bFound = false; + break; + } + } + + if (bFound) + { + occurrences++; + if (occurrence == occurrences) + { + ptr = uintptr_t(&*(pScanBytes + nMemOffset)); + return *this; + } + } + } + + ptr = uintptr_t(); + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: ResolveRelativeAddress wrapper +// Input : opcodeOffset - +// nextInstructionOffset - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::FollowNearCall(const ptrdiff_t opcodeOffset, const ptrdiff_t nextInstructionOffset) const +{ + return ResolveRelativeAddress(opcodeOffset, nextInstructionOffset); +} + +//----------------------------------------------------------------------------- +// Purpose: ResolveRelativeAddressSelf wrapper +// Input : opcodeOffset - +// nextInstructionOffset - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::FollowNearCallSelf(const ptrdiff_t opcodeOffset, const ptrdiff_t nextInstructionOffset) +{ + return ResolveRelativeAddressSelf(opcodeOffset, nextInstructionOffset); +} + +//----------------------------------------------------------------------------- +// Purpose: resolves the relative pointer to offset +// Input : registerOffset - +// nextInstructionOffset - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::ResolveRelativeAddress(const ptrdiff_t registerOffset, const ptrdiff_t nextInstructionOffset) const +{ + // Skip register. + const uintptr_t skipRegister = ptr + registerOffset; + + // Get 4-byte long relative Address. + const int32_t relativeAddress = *reinterpret_cast(skipRegister); + + // Get location of next instruction. + const uintptr_t nextInstruction = ptr + nextInstructionOffset; + + // Get function location via adding relative Address to next instruction. + return CMemory(nextInstruction + relativeAddress); +} + +//----------------------------------------------------------------------------- +// Purpose: resolves the relative pointer to offset from current address +// Input : registerOffset - +// nextInstructionOffset - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CMemory::ResolveRelativeAddressSelf(const ptrdiff_t registerOffset, const ptrdiff_t nextInstructionOffset) +{ + // Skip register. + const uintptr_t skipRegister = ptr + registerOffset; + + // Get 4-byte long relative Address. + const int32_t relativeAddress = *reinterpret_cast(skipRegister); + + // Get location of next instruction. + const uintptr_t nextInstruction = ptr + nextInstructionOffset; + + // Get function location via adding relative Address to next instruction. + ptr = nextInstruction + relativeAddress; + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: resolve all 'call' references to ptr +// (This is very slow only use for mass patching.) +// Input : sectionBase - +// sectionSize - +// Output : std::vector +//----------------------------------------------------------------------------- +std::vector CMemory::FindAllCallReferences(const uintptr_t sectionBase, const size_t sectionSize) +{ + std::vector referencesInfo = {}; + + uint8_t* pTextStart = reinterpret_cast(sectionBase); + for (size_t i = 0ull; i < sectionSize - 0x5; i++, _mm_prefetch(reinterpret_cast(pTextStart + 64), _MM_HINT_NTA)) + { + if (pTextStart[i] == 0xE8) + { + CMemory memAddr = CMemory(&pTextStart[i]); + if (!memAddr.Offset(0x1).CheckOpCodes({ 0x00, 0x00, 0x00, 0x00 })) // Check if its not a dynamic resolved call. + { + if (memAddr.FollowNearCall() == *this) + referencesInfo.push_back(memAddr); + } + } + } + + return referencesInfo; +} + +//----------------------------------------------------------------------------- +// Purpose: patch virtual method to point to a user set function +// Input : virtualTable - +// pHookMethod - +// methodIndex - +// ppOriginalMethod - +// Output : void** via ppOriginalMethod +//----------------------------------------------------------------------------- +void CMemory::HookVirtualMethod(const uintptr_t virtualTable, const void* pHookMethod, const ptrdiff_t methodIndex, void** ppOriginalMethod) +{ + DWORD oldProt = NULL; + + // Calculate delta to next virtual method. + const uintptr_t virtualMethod = virtualTable + (methodIndex * sizeof(ptrdiff_t)); + + // Preserve original function. + const uintptr_t originalFunction = *reinterpret_cast(virtualMethod); + + // Set page for current virtual method to execute n read n write. + VirtualProtect(reinterpret_cast(virtualMethod), sizeof(virtualMethod), PAGE_EXECUTE_READWRITE, &oldProt); + + // Set virtual method to our hook. + *reinterpret_cast(virtualMethod) = reinterpret_cast(pHookMethod); + + // Restore original page. + VirtualProtect(reinterpret_cast(virtualMethod), sizeof(virtualMethod), oldProt, &oldProt); + + // Move original function into argument. + *ppOriginalMethod = reinterpret_cast(originalFunction); +} + +//----------------------------------------------------------------------------- +// Purpose: patch iat entry to point to a user set function +// Input : pImportedMethod - +// pHookMethod - +// ppOriginalMethod - +// Output : void** via ppOriginalMethod +//----------------------------------------------------------------------------- +void CMemory::HookImportedFunction(const uintptr_t pImportedMethod, const void* pHookMethod, void** ppOriginalMethod) +{ + DWORD oldProt = NULL; + + // Preserve original function. + const uintptr_t originalFunction = *reinterpret_cast(pImportedMethod); + + // Set page for current iat entry to execute n read n write. + VirtualProtect(reinterpret_cast(pImportedMethod), sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProt); + + // Set method to our hook. + *reinterpret_cast(pImportedMethod) = reinterpret_cast(pHookMethod); + + // Restore original page. + VirtualProtect(reinterpret_cast(pImportedMethod), sizeof(void*), oldProt, &oldProt); + + // Move original function into argument. + *ppOriginalMethod = reinterpret_cast(originalFunction); +} diff --git a/primedev/thirdparty/silver-bun/memaddr.h b/primedev/thirdparty/silver-bun/memaddr.h new file mode 100644 index 00000000..3060c0cd --- /dev/null +++ b/primedev/thirdparty/silver-bun/memaddr.h @@ -0,0 +1,154 @@ +//===========================================================================// +// +// Purpose: Implementation of the CMemory class. +// +// Original commit: https://github.com/IcePixelx/silver-bun/commit/72c74b455bf4d02b424096ad2f30cd65535f814c +// +//===========================================================================// +#pragma once + +#include +#include +#include +#include +#include + +class CMemory +{ +public: + enum class Direction : int + { + DOWN = 0, + UP, + }; + + CMemory(void) = default; + CMemory(const uintptr_t ptr) : ptr(ptr) {} + CMemory(const void* ptr) : ptr(uintptr_t(ptr)) {} + + inline operator uintptr_t(void) const + { + return ptr; + } + + inline operator void*(void) const + { + return reinterpret_cast(ptr); + } + + inline operator bool(void) const + { + return ptr != NULL; + } + + inline bool operator!= (const CMemory& addr) const + { + return ptr != addr.ptr; + } + + inline bool operator== (const CMemory& addr) const + { + return ptr == addr.ptr; + } + + inline bool operator== (const uintptr_t& addr) const + { + return ptr == addr; + } + + inline uintptr_t GetPtr(void) const + { + return ptr; + } + + template inline T GetValue(void) const + { + return *reinterpret_cast(ptr); + } + + template inline T GetVirtualFunctionIndex(void) const + { + return *reinterpret_cast(ptr) / 8; + } + + template inline T CCast(void) const + { + return (T)ptr; + } + + template inline T RCast(void) const + { + return reinterpret_cast(ptr); + } + + inline CMemory Offset(ptrdiff_t offset) const + { + return CMemory(ptr + offset); + } + + inline CMemory OffsetSelf(ptrdiff_t offset) + { + ptr += offset; + return *this; + } + + inline CMemory Deref(int deref = 1) const + { + uintptr_t reference = ptr; + + while (deref--) + { + if (reference) + reference = *reinterpret_cast(reference); + } + + return CMemory(reference); + } + + inline CMemory DerefSelf(int deref = 1) + { + while (deref--) + { + if (ptr) + ptr = *reinterpret_cast(ptr); + } + + return *this; + } + + inline CMemory WalkVTable(ptrdiff_t vfuncIndex) + { + uintptr_t reference = ptr + (sizeof(uintptr_t) * vfuncIndex); + return CMemory(reference); + } + + inline CMemory WalkVTableSelf(ptrdiff_t vfuncIndex) + { + ptr += (sizeof(uintptr_t) * vfuncIndex); + return *this; + } + + bool CheckOpCodes(const std::vector& vOpcodeArray) const; + bool IsMemoryReadable(const size_t nSize) const; + + void NOP(const size_t nSize) const; + void Patch(const char* pszOpcodes) const; + void Patch(const uint8_t* pOpcodeArray, const size_t nSize) const; + void Patch(const std::vector& vOpcodeArray) const; + void PatchString(const char* szString) const; + + CMemory FindPattern(const char* szPattern, const Direction searchDirect = Direction::DOWN, const int opCodesToScan = 512, const ptrdiff_t occurrence = 1) const; + CMemory FindPatternSelf(const char* szPattern, const Direction searchDirect = Direction::DOWN, const int opCodesToScan = 512, const ptrdiff_t occurrence = 1); + std::vector FindAllCallReferences(const uintptr_t sectionBase, const size_t sectionSize); + + CMemory FollowNearCall(const ptrdiff_t opcodeOffset = 0x1, const ptrdiff_t nextInstructionOffset = 0x5) const; + CMemory FollowNearCallSelf(const ptrdiff_t opcodeOffset = 0x1, const ptrdiff_t nextInstructionOffset = 0x5); + CMemory ResolveRelativeAddress(const ptrdiff_t registerOffset = 0x0, const ptrdiff_t nextInstructionOffset = 0x4) const; + CMemory ResolveRelativeAddressSelf(const ptrdiff_t registerOffset = 0x0, const ptrdiff_t nextInstructionOffset = 0x4); + + static void HookVirtualMethod(const uintptr_t virtualTable, const void* pHookMethod, const ptrdiff_t methodIndex, void** ppOriginalMethod); + static void HookImportedFunction(const uintptr_t pImportedMethod, const void* pHookMethod, void** ppOriginalMethod); + +private: + uintptr_t ptr = 0; +}; diff --git a/primedev/thirdparty/silver-bun/module.cpp b/primedev/thirdparty/silver-bun/module.cpp new file mode 100644 index 00000000..4b9330e0 --- /dev/null +++ b/primedev/thirdparty/silver-bun/module.cpp @@ -0,0 +1,474 @@ +//===========================================================================// +// +// Purpose: Implementation of the CModule class. +// +// Original commit: https://github.com/IcePixelx/silver-bun/commit/72c74b455bf4d02b424096ad2f30cd65535f814c +// +//===========================================================================// + +#include "module.h" +#include "utils.h" + +//----------------------------------------------------------------------------- +// Purpose: constructor +// Input : *szModuleName - +//----------------------------------------------------------------------------- +CModule::CModule(HMODULE hModule) +{ + m_pModuleBase = reinterpret_cast(hModule); + + CHAR szModuleName[MAX_PATH]; + DWORD dwSize = GetModuleFileNameA(hModule, szModuleName, sizeof(szModuleName)); + m_ModuleName = strrchr(szModuleName, '\\') + 1; + + + Init(); + LoadSections(); +} + +//----------------------------------------------------------------------------- +// Purpose: constructor +// Input : *szModuleName - +//----------------------------------------------------------------------------- +CModule::CModule(const char* szModuleName) +{ + m_pModuleBase = reinterpret_cast(GetModuleHandleA(szModuleName)); + m_ModuleName = szModuleName; + + Init(); + LoadSections(); +} + +//----------------------------------------------------------------------------- +// Purpose: initializes module descriptors +//----------------------------------------------------------------------------- +void CModule::Init() +{ + m_pDOSHeader = reinterpret_cast(m_pModuleBase); + m_pNTHeaders = reinterpret_cast(m_pModuleBase + m_pDOSHeader->e_lfanew); + m_nModuleSize = static_cast(m_pNTHeaders->OptionalHeader.SizeOfImage); + + const IMAGE_SECTION_HEADER* hSection = IMAGE_FIRST_SECTION(m_pNTHeaders); // Get first image section. + + for (WORD i = 0; i < m_pNTHeaders->FileHeader.NumberOfSections; i++) // Loop through the sections. + { + const IMAGE_SECTION_HEADER& hCurrentSection = hSection[i]; // Get current section. + m_ModuleSections.push_back(ModuleSections_t(reinterpret_cast(hCurrentSection.Name), + static_cast(m_pModuleBase + hCurrentSection.VirtualAddress), hCurrentSection.SizeOfRawData)); // Push back a struct with the section data. + } +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the default executable segments +//----------------------------------------------------------------------------- +void CModule::LoadSections() +{ + m_ExecutableCode = GetSectionByName(".text"); + m_ExceptionTable = GetSectionByName(".pdata"); + m_RunTimeData = GetSectionByName(".data"); + m_ReadOnlyData = GetSectionByName(".rdata"); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets memory at relative offset +// Input : nOffset - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::Offset(const uintptr_t nOffset) const +{ + return CMemory(m_pModuleBase + nOffset); +} + +//----------------------------------------------------------------------------- +// Purpose: find array of bytes in process memory using SIMD instructions +// Input : *pPattern - +// *szMask - +// *moduleSection - +// nOccurrence - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::FindPatternSIMD(const uint8_t* pPattern, const char* szMask, + const ModuleSections_t* moduleSection, const size_t nOccurrence) const +{ + if (!m_ExecutableCode.IsSectionValid()) + return CMemory(); + + const bool bSectionValid = moduleSection ? moduleSection->IsSectionValid() : false; + + const uintptr_t nBase = bSectionValid ? moduleSection->m_pSectionBase : m_ExecutableCode.m_pSectionBase; + const uintptr_t nSize = bSectionValid ? moduleSection->m_nSectionSize : m_ExecutableCode.m_nSectionSize; + + const size_t nMaskLen = strlen(szMask); + const uint8_t* pData = reinterpret_cast(nBase); + const uint8_t* pEnd = pData + nSize - nMaskLen; + + size_t nOccurrenceCount = 0; + int nMasks[64]; // 64*16 = enough masks for 1024 bytes. + const int iNumMasks = static_cast(ceil(static_cast(nMaskLen) / 16.f)); + + memset(nMasks, '\0', iNumMasks * sizeof(int)); + for (intptr_t i = 0; i < iNumMasks; ++i) + { + for (intptr_t j = strnlen(szMask + i * 16, 16) - 1; j >= 0; --j) + { + if (szMask[i * 16 + j] == 'x') + { + _bittestandset(reinterpret_cast(&nMasks[i]), static_cast(j)); + } + } + } + const __m128i xmm1 = _mm_loadu_si128(reinterpret_cast(pPattern)); + __m128i xmm2, xmm3, msks; + for (; pData != pEnd; _mm_prefetch(reinterpret_cast(++pData + 64), _MM_HINT_NTA)) + { + if (pPattern[0] == pData[0]) + { + xmm2 = _mm_loadu_si128(reinterpret_cast(pData)); + msks = _mm_cmpeq_epi8(xmm1, xmm2); + if ((_mm_movemask_epi8(msks) & nMasks[0]) == nMasks[0]) + { + for (uintptr_t i = 1; i < static_cast(iNumMasks); ++i) + { + xmm2 = _mm_loadu_si128(reinterpret_cast((pData + i * 16))); + xmm3 = _mm_loadu_si128(reinterpret_cast((pPattern + i * 16))); + msks = _mm_cmpeq_epi8(xmm2, xmm3); + if ((_mm_movemask_epi8(msks) & nMasks[i]) == nMasks[i]) + { + if ((i + 1) == iNumMasks) + { + if (nOccurrenceCount == nOccurrence) + { + return static_cast(const_cast(pData)); + } + nOccurrenceCount++; + } + } + else + { + goto cont; + } + } + if (nOccurrenceCount == nOccurrence) + { + return static_cast((&*(const_cast(pData)))); + } + nOccurrenceCount++; + } + }cont:; + } + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: find a string pattern in process memory using SIMD instructions +// Input : *szPattern - +// *moduleSection - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::FindPatternSIMD(const char* szPattern, const ModuleSections_t* moduleSection) const +{ + const std::pair, std::string> patternInfo = Utils::PatternToMaskedBytes(szPattern); + return FindPatternSIMD(patternInfo.first.data(), patternInfo.second.c_str(), moduleSection); +} + +//----------------------------------------------------------------------------- +// Purpose: find address of reference to string constant in executable memory +// Input : *szString - +// bNullTerminator - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::FindString(const char* szString, const ptrdiff_t nOccurrence, bool bNullTerminator) const +{ + if (!m_ExecutableCode.IsSectionValid()) + return CMemory(); + + const CMemory stringAddress = FindStringReadOnly(szString, bNullTerminator); // Get Address for the string in the .rdata section. + + if (!stringAddress) + return CMemory(); + + uint8_t* pLatestOccurrence = nullptr; + uint8_t* pTextStart = reinterpret_cast(m_ExecutableCode.m_pSectionBase); // Get the start of the .text section. + ptrdiff_t dOccurrencesFound = 0; + CMemory resultAddress; + + for (size_t i = 0ull; i < m_ExecutableCode.m_nSectionSize - 0x5; i++) + { + byte byte = pTextStart[i]; + if (byte == 0x8D) // 0x8D = LEA + { + const CMemory skipOpCode = CMemory(reinterpret_cast(&pTextStart[i])).OffsetSelf(0x2); // Skip next 2 opcodes, those being the instruction and the register. + const int32_t relativeAddress = skipOpCode.GetValue(); // Get 4-byte long string relative Address + const uintptr_t nextInstruction = skipOpCode.Offset(0x4).GetPtr(); // Get location of next instruction. + const CMemory potentialLocation = CMemory(nextInstruction + relativeAddress); // Get potential string location. + + if (potentialLocation == stringAddress) + { + dOccurrencesFound++; + if (nOccurrence == dOccurrencesFound) + { + return CMemory(&pTextStart[i]); + } + + pLatestOccurrence = &pTextStart[i]; // Stash latest occurrence. + } + } + } + + return CMemory(pLatestOccurrence); +} + +//----------------------------------------------------------------------------- +// Purpose: find address of input string constant in read only memory +// Input : *szString - +// bNullTerminator - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::FindStringReadOnly(const char* szString, bool bNullTerminator) const +{ + if (!m_ReadOnlyData.IsSectionValid()) + return CMemory(); + + const std::vector vBytes = Utils::StringToBytes(szString, bNullTerminator); // Convert our string to a byte array. + const std::pair bytesInfo = std::make_pair(vBytes.size(), vBytes.data()); // Get the size and data of our bytes. + + const uint8_t* pBase = reinterpret_cast(m_ReadOnlyData.m_pSectionBase); // Get start of .rdata section. + + for (size_t i = 0ull; i < m_ReadOnlyData.m_nSectionSize - bytesInfo.first; i++) + { + bool bFound = true; + + // If either the current byte equals to the byte in our pattern or our current byte in the pattern is a wildcard + // our if clause will be false. + for (size_t j = 0ull; j < bytesInfo.first; j++) + { + if (pBase[i + j] != bytesInfo.second[j] && bytesInfo.second[j] != -1) + { + bFound = false; + break; + } + } + + if (bFound) + { + return CMemory(&pBase[i]); + } + } + + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: find 'free' page in r/w/x sections +// Input : nSize - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::FindFreeDataPage(const size_t nSize) const +{ + auto checkDataSection = [](const void* address, const std::size_t size) + { + MEMORY_BASIC_INFORMATION membInfo = { 0 }; + + VirtualQuery(address, &membInfo, sizeof(membInfo)); + + if (membInfo.AllocationBase && membInfo.BaseAddress && membInfo.State == MEM_COMMIT && !(membInfo.Protect & PAGE_GUARD) && membInfo.Protect != PAGE_NOACCESS) + { + if ((membInfo.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE)) && membInfo.RegionSize >= size) + { + return ((membInfo.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE)) && membInfo.RegionSize >= size) ? true : false; + } + } + return false; + }; + + // This is very unstable, this doesn't check for the actual 'page' sizes. + // Also can be optimized to search per 'section'. + const uintptr_t endOfModule = m_pModuleBase + m_pNTHeaders->OptionalHeader.SizeOfImage - sizeof(uintptr_t); + for (uintptr_t currAddr = endOfModule; m_pModuleBase < currAddr; currAddr -= sizeof(uintptr_t)) + { + if (*reinterpret_cast(currAddr) == 0 && checkDataSection(reinterpret_cast(currAddr), nSize)) + { + bool bIsGoodPage = true; + uint32_t nPageCount = 0; + + for (; nPageCount < nSize && bIsGoodPage; nPageCount += sizeof(uintptr_t)) + { + const uintptr_t pageData = *reinterpret_cast(currAddr + nPageCount); + if (pageData != 0) + bIsGoodPage = false; + } + + if (bIsGoodPage && nPageCount >= nSize) + return currAddr; + } + } + + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: get address of a virtual method table by rtti type descriptor name +// Input : *szTableName - +// nRefIndex - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::GetVirtualMethodTable(const char* szTableName, const size_t nRefIndex) +{ + if (!m_ReadOnlyData.IsSectionValid()) // Process decided to rename the readonlydata section if this fails. + return CMemory(); + + ModuleSections_t moduleSection(".data", m_RunTimeData.m_pSectionBase, m_RunTimeData.m_nSectionSize); + + const auto tableNameInfo = Utils::StringToMaskedBytes(szTableName, false); + CMemory rttiTypeDescriptor = FindPatternSIMD(tableNameInfo.first.data(), tableNameInfo.second.c_str(), &moduleSection).OffsetSelf(-0x10); + if (!rttiTypeDescriptor) + return CMemory(); + + uintptr_t scanStart = m_ReadOnlyData.m_pSectionBase; // Get the start address of our scan. + + const uintptr_t scanEnd = (m_ReadOnlyData.m_pSectionBase + m_ReadOnlyData.m_nSectionSize) - 0x4; // Calculate the end of our scan. + const uintptr_t rttiTDRva = rttiTypeDescriptor.GetPtr() - m_pModuleBase; // The RTTI gets referenced by a 4-Byte RVA address. We need to scan for that address. + while (scanStart < scanEnd) + { + moduleSection = { ".rdata", scanStart, m_ReadOnlyData.m_nSectionSize }; + CMemory reference = FindPatternSIMD(reinterpret_cast(&rttiTDRva), "xxxx", &moduleSection, nRefIndex); + if (!reference) + break; + + CMemory referenceOffset = reference.Offset(-0xC); + if (referenceOffset.GetValue() != 1) // Check if we got a RTTI Object Locator for this reference by checking if -0xC is 1, which is the 'signature' field which is always 1 on x64. + { + scanStart = reference.Offset(0x4).GetPtr(); // Set location to current reference + 0x4 so we avoid pushing it back again into the vector. + continue; + } + + moduleSection = { ".rdata", m_ReadOnlyData.m_pSectionBase, m_ReadOnlyData.m_nSectionSize }; + return FindPatternSIMD(reinterpret_cast(&referenceOffset), "xxxxxxxx", &moduleSection).OffsetSelf(0x8); + } + + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: get address of imported function in this module +// Input : *szModuleName - +// *szFunctionName - +// bGetFunctionReference - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::GetImportedFunction(const char* szModuleName, const char* szFunctionName, const bool bGetFunctionReference) const +{ + if (!m_pDOSHeader || m_pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) // Is dosHeader valid? + return CMemory(); + + if (!m_pNTHeaders || m_pNTHeaders->Signature != IMAGE_NT_SIGNATURE) // Is ntHeader valid? + return CMemory(); + + // Get the location of IMAGE_IMPORT_DESCRIPTOR for this module by adding the IMAGE_DIRECTORY_ENTRY_IMPORT relative virtual address onto our module base address. + IMAGE_IMPORT_DESCRIPTOR* pImageImportDescriptors = reinterpret_cast(m_pModuleBase + m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + if (!pImageImportDescriptors) + return CMemory(); + + for (IMAGE_IMPORT_DESCRIPTOR* pIID = pImageImportDescriptors; pIID->Name != 0; pIID++) + { + // Get virtual relative Address of the imported module name. Then add module base Address to get the actual location. + const char* szImportedModuleName = reinterpret_cast(reinterpret_cast(m_pModuleBase + pIID->Name)); + + if (_stricmp(szImportedModuleName, szModuleName) == 0) // Is this our wanted imported module?. + { + // Original First Thunk to get function name. + IMAGE_THUNK_DATA* pOgFirstThunk = reinterpret_cast(m_pModuleBase + pIID->OriginalFirstThunk); + + // To get actual function address. + IMAGE_THUNK_DATA* pFirstThunk = reinterpret_cast(m_pModuleBase + pIID->FirstThunk); + for (; pOgFirstThunk->u1.AddressOfData; ++pOgFirstThunk, ++pFirstThunk) + { + // Get image import by name. + const IMAGE_IMPORT_BY_NAME* pImageImportByName = reinterpret_cast(m_pModuleBase + pOgFirstThunk->u1.AddressOfData); + + if (strcmp(pImageImportByName->Name, szFunctionName) == 0) // Is this our wanted imported function? + { + // Grab function address from firstThunk. +#if _WIN64 + uintptr_t* pFunctionAddress = &pFirstThunk->u1.Function; +#else + uintptr_t* pFunctionAddress = reinterpret_cast(&pFirstThunk->u1.Function); +#endif // #if _WIN64 + + // Reference or address? + return bGetFunctionReference ? CMemory(pFunctionAddress) : CMemory(*pFunctionAddress); // Return as CMemory class. + } + } + + } + } + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: get address of exported function in this module +// Input : *szFunctionName - +// bNullTerminator - +// Output : CMemory +//----------------------------------------------------------------------------- +CMemory CModule::GetExportedFunction(const char* szFunctionName) const +{ + if (!m_pDOSHeader || m_pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) // Is dosHeader valid? + return CMemory(); + + if (!m_pNTHeaders || m_pNTHeaders->Signature != IMAGE_NT_SIGNATURE) // Is ntHeader valid? + return CMemory(); + + // Get the location of IMAGE_EXPORT_DIRECTORY for this module by adding the IMAGE_DIRECTORY_ENTRY_EXPORT relative virtual address onto our module base address. + const IMAGE_EXPORT_DIRECTORY* pImageExportDirectory = reinterpret_cast(m_pModuleBase + m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if (!pImageExportDirectory) + return CMemory(); + + // Are there any exported functions? + if (!pImageExportDirectory->NumberOfFunctions) + return CMemory(); + + // Get the location of the functions via adding the relative virtual address from the struct into our module base address. + const DWORD* pAddressOfFunctions = reinterpret_cast(m_pModuleBase + pImageExportDirectory->AddressOfFunctions); + if (!pAddressOfFunctions) + return CMemory(); + + // Get the names of the functions via adding the relative virtual address from the struct into our module base Address. + const DWORD* pAddressOfName = reinterpret_cast(m_pModuleBase + pImageExportDirectory->AddressOfNames); + if (!pAddressOfName) + return CMemory(); + + // Get the ordinals of the functions via adding the relative virtual Address from the struct into our module base address. + DWORD* pAddressOfOrdinals = reinterpret_cast(m_pModuleBase + pImageExportDirectory->AddressOfNameOrdinals); + if (!pAddressOfOrdinals) + return CMemory(); + + for (DWORD i = 0; i < pImageExportDirectory->NumberOfNames; i++) // Iterate through all the functions. + { + // Get virtual relative Address of the function name. Then add module base Address to get the actual location. + const char* ExportFunctionName = reinterpret_cast(reinterpret_cast(m_pModuleBase + pAddressOfName[i])); + + if (strcmp(ExportFunctionName, szFunctionName) == 0) // Is this our wanted exported function? + { + // Get the function ordinal. Then grab the relative virtual address of our wanted function. Then add module base address so we get the actual location. + return CMemory(m_pModuleBase + pAddressOfFunctions[reinterpret_cast(pAddressOfOrdinals)[i]]); // Return as CMemory class. + } + } + return CMemory(); +} + +//----------------------------------------------------------------------------- +// Purpose: get the module section by name (example: '.rdata', '.text') +// Input : *szSectionName - +// Output : ModuleSections_t +//----------------------------------------------------------------------------- +CModule::ModuleSections_t CModule::GetSectionByName(const char* szSectionName) const +{ + for (const ModuleSections_t& section : m_ModuleSections) + { + if (section.m_SectionName.compare(szSectionName) == 0) + return section; + } + + return ModuleSections_t(); +} diff --git a/primedev/thirdparty/silver-bun/module.h b/primedev/thirdparty/silver-bun/module.h new file mode 100644 index 00000000..5683ee14 --- /dev/null +++ b/primedev/thirdparty/silver-bun/module.h @@ -0,0 +1,76 @@ +//===========================================================================// +// +// Purpose: Implementation of the CModule class. +// +// Original commit: https://github.com/IcePixelx/silver-bun/commit/72c74b455bf4d02b424096ad2f30cd65535f814c +// +//===========================================================================// +#pragma once + +#include "memaddr.h" +#include +#include +#include +#include +#include +#include +#include + +class CModule +{ +public: + struct ModuleSections_t + { + ModuleSections_t(void) = default; + ModuleSections_t(const char* sectionName, uintptr_t pSectionBase, size_t nSectionSize) : + m_SectionName(sectionName), m_pSectionBase(pSectionBase), m_nSectionSize(nSectionSize) {} + + inline bool IsSectionValid(void) const { return m_nSectionSize != 0; } + + std::string m_SectionName; // Name of section. + uintptr_t m_pSectionBase; // Start address of section. + size_t m_nSectionSize; // Size of section. + }; + + CModule(void) = default; + CModule(HMODULE hModule); + CModule(const char* szModuleName); + + void Init(); + void LoadSections(); + + CMemory Offset(const uintptr_t nOffset) const; + + CMemory FindPatternSIMD(const char* szPattern, const ModuleSections_t* moduleSection = nullptr) const; + CMemory FindString(const char* szString, const ptrdiff_t occurrence = 1, bool nullTerminator = false) const; + CMemory FindStringReadOnly(const char* szString, bool nullTerminator) const; + CMemory FindFreeDataPage(const size_t nSize) const; + + CMemory GetVirtualMethodTable(const char* szTableName, const size_t nRefIndex = 0); + CMemory GetImportedFunction(const char* szModuleName, const char* szFunctionName, const bool bGetFunctionReference) const; + CMemory GetExportedFunction(const char* szFunctionName) const; + ModuleSections_t GetSectionByName(const char* szSectionName) const; + + inline const std::vector& GetSections() const { return m_ModuleSections; } + inline uintptr_t GetModuleBase(void) const { return m_pModuleBase; } + inline DWORD GetModuleSize(void) const { return m_nModuleSize; } + inline const std::string& GetModuleName(void) const { return m_ModuleName; } + inline uintptr_t GetRVA(const uintptr_t nAddress) const { return (nAddress - GetModuleBase()); } + + IMAGE_NT_HEADERS64* m_pNTHeaders; + IMAGE_DOS_HEADER* m_pDOSHeader; + + ModuleSections_t m_ExecutableCode; + ModuleSections_t m_ExceptionTable; + ModuleSections_t m_RunTimeData; + ModuleSections_t m_ReadOnlyData; + +private: + CMemory FindPatternSIMD(const uint8_t* pPattern, const char* szMask, + const ModuleSections_t* moduleSection = nullptr, const size_t nOccurrence = 0) const; + + std::string m_ModuleName; + uintptr_t m_pModuleBase; + DWORD m_nModuleSize; + std::vector m_ModuleSections; +}; diff --git a/primedev/thirdparty/silver-bun/utils.cpp b/primedev/thirdparty/silver-bun/utils.cpp new file mode 100644 index 00000000..6f7b397e --- /dev/null +++ b/primedev/thirdparty/silver-bun/utils.cpp @@ -0,0 +1,127 @@ +#include "utils.h" + +namespace Utils +{ + //---------------------------------------------------------------------------------------- + // Purpose: For converting a string pattern to an array of bytes. Doesnt support wildcards + //---------------------------------------------------------------------------------------- + std::vector StringPatternToBytes(const char* szInput) + { + const char* pszPatternStart = const_cast(szInput); + const char* pszPatternEnd = pszPatternStart + strlen(szInput); + std::vector vBytes; + + for (const char* pszCurrentByte = pszPatternStart; pszCurrentByte < pszPatternEnd; ++pszCurrentByte) + { + vBytes.push_back(strtoul(pszCurrentByte, const_cast(&pszCurrentByte), 16)); + } + return vBytes; + }; + + //---------------------------------------------------------------------------------------- + // Purpose: For converting a string pattern with wildcards to an array of bytes. + //---------------------------------------------------------------------------------------- + std::vector PatternToBytes(const char* szInput) + { + const char* pszPatternStart = const_cast(szInput); + const char* pszPatternEnd = pszPatternStart + strlen(szInput); + std::vector vBytes; + + for (const char* pszCurrentByte = pszPatternStart; pszCurrentByte < pszPatternEnd; ++pszCurrentByte) + { + if (*pszCurrentByte == '?') + { + ++pszCurrentByte; + if (*pszCurrentByte == '?') + { + ++pszCurrentByte; // Skip double wildcard. + } + vBytes.push_back(-1); // Push the byte back as invalid. + } + else + { + vBytes.push_back(strtoul(pszCurrentByte, const_cast(&pszCurrentByte), 16)); + } + } + return vBytes; + }; + + //---------------------------------------------------------------------------------------- + // Purpose: For converting a string pattern with wildcards to an array of bytes and mask. + //---------------------------------------------------------------------------------------- + std::pair, std::string> PatternToMaskedBytes(const char* szInput) + { + const char* pszPatternStart = const_cast(szInput); + const char* pszPatternEnd = pszPatternStart + strlen(szInput); + + std::vector vBytes; + std::string svMask; + + for (const char* pszCurrentByte = pszPatternStart; pszCurrentByte < pszPatternEnd; ++pszCurrentByte) + { + if (*pszCurrentByte == '?') + { + ++pszCurrentByte; + if (*pszCurrentByte == '?') + { + ++pszCurrentByte; // Skip double wildcard. + } + vBytes.push_back(0); // Push the byte back as invalid. + svMask += '?'; + } + else + { + vBytes.push_back(uint8_t(strtoul(pszCurrentByte, const_cast(&pszCurrentByte), 16))); + svMask += 'x'; + } + } + return make_pair(vBytes, svMask); + }; + + //---------------------------------------------------------------------------------------- + // Purpose: For converting a string to an array of bytes. + //---------------------------------------------------------------------------------------- + std::vector StringToBytes(const char* szInput, bool bNullTerminator) + { + const char* pszStringStart = const_cast(szInput); + const char* pszStringEnd = pszStringStart + strlen(szInput); + std::vector vBytes; + + for (const char* pszCurrentByte = pszStringStart; pszCurrentByte < pszStringEnd; ++pszCurrentByte) + { + // Dereference character and push back the byte. + vBytes.push_back(*pszCurrentByte); + } + + if (bNullTerminator) + { + vBytes.push_back('\0'); + } + return vBytes; + }; + + //---------------------------------------------------------------------------------------- + // Purpose: For converting a string to an array of masked bytes. + //---------------------------------------------------------------------------------------- + std::pair, std::string> StringToMaskedBytes(const char* szInput, bool bNullTerminator) + { + const char* pszStringStart = const_cast(szInput); + const char* pszStringEnd = pszStringStart + strlen(szInput); + std::vector vBytes; + std::string svMask; + + for (const char* pszCurrentByte = pszStringStart; pszCurrentByte < pszStringEnd; ++pszCurrentByte) + { + // Dereference character and push back the byte. + vBytes.push_back(*pszCurrentByte); + svMask += 'x'; + } + + if (bNullTerminator) + { + vBytes.push_back(0x0); + svMask += 'x'; + } + return make_pair(vBytes, svMask); + }; +} diff --git a/primedev/thirdparty/silver-bun/utils.h b/primedev/thirdparty/silver-bun/utils.h new file mode 100644 index 00000000..15e43a92 --- /dev/null +++ b/primedev/thirdparty/silver-bun/utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Utils +{ + std::vector StringPatternToBytes(const char* szInput); + std::vector PatternToBytes(const char* szInput); + std::pair, std::string> PatternToMaskedBytes(const char* szInput); + std::vector StringToBytes(const char* szInput, bool bNullTerminator); + std::pair, std::string> StringToMaskedBytes(const char* szInput, bool bNullTerminator); +} + +typedef const unsigned char* rsig_t; -- cgit v1.2.3