mirror of
https://github.com/Milxnor/Project-Reboot-3.0.git
synced 2026-01-13 02:42:22 +01:00
fix some playlist starter loot, cleanup some code, fix death bugs on 1.8 and 1.7.2, fix reloading not taking items on 1.8 and 1.7.2, fix looting on s9, fix some s15 & s16 builds, fix bug with higher version looting
1426 lines
50 KiB
C++
1426 lines
50 KiB
C++
// https://github.com/kem0x/memcury
|
|
|
|
#pragma once
|
|
|
|
/*
|
|
Memcury is a single-header file library for memory manipulation in C++.
|
|
|
|
Containers:
|
|
-PE::Address: A pointer container.
|
|
-PE::Section: Portable executable section container for internal usage.
|
|
|
|
Modules:
|
|
-Scanner:
|
|
-Constructors:
|
|
-Default: Takes a pointer to start the scanning from.
|
|
-FindPattern: Finds a pattern in memory.
|
|
-FindStringRef: Finds a string reference in memory, supports all types of strings.
|
|
-Functions:
|
|
-SetTargetModule: Sets the target module for the scanner.
|
|
-ScanFor: Scans for a byte(s) near the current address.
|
|
-FindFunctionBoundary: Finds the boundary of a function near the current address.
|
|
-RelativeOffset: Gets the relative offset of the current address.
|
|
-AbsoluteOffset: Gets the absolute offset of the current address.
|
|
-GetAs: Gets the current address as a type.
|
|
-Get: Gets the current address as an int64.
|
|
|
|
-TrampolineHook:
|
|
-Constructors:
|
|
-Default: Takes a pointer pointer to the target function and a pointer to the hook function.
|
|
-Functions:
|
|
-Commit: Commits the hook.
|
|
-Revert: Reverts the hook.
|
|
-Toggle: Toggles the hook on\off.
|
|
|
|
-VEHHook:
|
|
-Functions:
|
|
-Init: Initializes the VEH Hook system.
|
|
-AddHook: Adds a hook to the VEH Hook system.
|
|
-RemoveHook: Removes a hook from the VEH Hook system.
|
|
*/
|
|
|
|
#include <string>
|
|
#include <format>
|
|
#include <vector>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
#include <intrin.h>
|
|
#include <Windows.h>
|
|
#include <source_location>
|
|
#include <DbgHelp.h>
|
|
#pragma comment(lib, "Dbghelp.lib")
|
|
#include "../Project Reboot 3.0/log.h"
|
|
|
|
|
|
#define MemcuryAssert(cond) \
|
|
if (!(cond)) \
|
|
{ \
|
|
MessageBoxA(nullptr, #cond, __FUNCTION__, MB_ICONERROR | MB_OK); \
|
|
Memcury::Safety::FreezeCurrentThread(); \
|
|
}
|
|
|
|
#define MemcuryAssertM(cond, msg) \
|
|
if (!(cond)) \
|
|
{ \
|
|
MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \
|
|
}
|
|
|
|
#define MemcuryThrow(msg) \
|
|
MessageBoxA(nullptr, msg, __FUNCTION__, MB_ICONERROR | MB_OK); \
|
|
Memcury::Safety::FreezeCurrentThread();
|
|
|
|
namespace Memcury
|
|
{
|
|
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
|
|
|
inline auto GetCurrentModule() -> HMODULE
|
|
{
|
|
return reinterpret_cast<HMODULE>(&__ImageBase);
|
|
}
|
|
|
|
namespace Util
|
|
{
|
|
template <typename T>
|
|
constexpr static auto IsInRange(T value, T min, T max) -> bool
|
|
{
|
|
return value >= min && value < max;
|
|
}
|
|
|
|
constexpr auto StrHash(const char* str, int h = 0) -> unsigned int
|
|
{
|
|
return !str[h] ? 5381 : (StrHash(str, h + 1) * 33) ^ str[h];
|
|
}
|
|
|
|
inline auto IsSamePage(void* A, void* B) -> bool
|
|
{
|
|
MEMORY_BASIC_INFORMATION InfoA;
|
|
if (!VirtualQuery(A, &InfoA, sizeof(InfoA)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
MEMORY_BASIC_INFORMATION InfoB;
|
|
if (!VirtualQuery(B, &InfoB, sizeof(InfoB)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return InfoA.BaseAddress == InfoB.BaseAddress;
|
|
}
|
|
|
|
inline auto GetModuleStartAndEnd() -> std::pair<uintptr_t, uintptr_t>
|
|
{
|
|
auto HModule = GetCurrentModule();
|
|
auto NTHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>((uintptr_t)HModule + reinterpret_cast<PIMAGE_DOS_HEADER>((uintptr_t)HModule)->e_lfanew);
|
|
|
|
uintptr_t dllStart = (uintptr_t)HModule;
|
|
uintptr_t dllEnd = (uintptr_t)HModule + NTHeaders->OptionalHeader.SizeOfImage;
|
|
|
|
return { dllStart, dllEnd };
|
|
}
|
|
|
|
inline auto CopyToClipboard(std::string str)
|
|
{
|
|
auto mem = GlobalAlloc(GMEM_FIXED, str.size() + 1);
|
|
memcpy(mem, str.c_str(), str.size() + 1);
|
|
|
|
OpenClipboard(nullptr);
|
|
EmptyClipboard();
|
|
SetClipboardData(CF_TEXT, mem);
|
|
CloseClipboard();
|
|
|
|
GlobalFree(mem);
|
|
}
|
|
}
|
|
|
|
namespace Safety
|
|
{
|
|
enum class ExceptionMode
|
|
{
|
|
None,
|
|
CatchDllExceptionsOnly,
|
|
CatchAllExceptions
|
|
};
|
|
|
|
static auto FreezeCurrentThread() -> void
|
|
{
|
|
SuspendThread(GetCurrentThread());
|
|
}
|
|
|
|
static auto PrintStack(CONTEXT* ctx) -> void
|
|
{
|
|
STACKFRAME64 stack;
|
|
memset(&stack, 0, sizeof(STACKFRAME64));
|
|
|
|
auto process = GetCurrentProcess();
|
|
auto thread = GetCurrentThread();
|
|
|
|
SymInitialize(process, NULL, TRUE);
|
|
|
|
bool result;
|
|
DWORD64 displacement = 0;
|
|
|
|
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]{ 0 };
|
|
char name[256]{ 0 };
|
|
char module[256]{ 0 };
|
|
|
|
PSYMBOL_INFO symbolInfo = (PSYMBOL_INFO)buffer;
|
|
|
|
for (ULONG frame = 0;; frame++)
|
|
{
|
|
result = StackWalk64(
|
|
IMAGE_FILE_MACHINE_AMD64,
|
|
process,
|
|
thread,
|
|
&stack,
|
|
ctx,
|
|
NULL,
|
|
SymFunctionTableAccess64,
|
|
SymGetModuleBase64,
|
|
NULL);
|
|
|
|
if (!result)
|
|
break;
|
|
|
|
symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
symbolInfo->MaxNameLen = MAX_SYM_NAME;
|
|
SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, symbolInfo);
|
|
|
|
HMODULE hModule = NULL;
|
|
lstrcpyA(module, "");
|
|
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (const wchar_t*)(stack.AddrPC.Offset), &hModule);
|
|
|
|
if (hModule != NULL)
|
|
GetModuleFileNameA(hModule, module, 256);
|
|
|
|
printf("[%lu] Name: %s - Address: %p - Module: %s\n", frame, symbolInfo->Name, (void*)symbolInfo->Address, module);
|
|
}
|
|
}
|
|
|
|
template <ExceptionMode mode>
|
|
auto MemcuryGlobalHandler(EXCEPTION_POINTERS* ExceptionInfo) -> long
|
|
{
|
|
auto [dllStart, dllEnd] = Util::GetModuleStartAndEnd();
|
|
|
|
if constexpr (mode == ExceptionMode::CatchDllExceptionsOnly)
|
|
{
|
|
if (!Util::IsInRange(ExceptionInfo->ContextRecord->Rip, dllStart, dllEnd))
|
|
{
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
}
|
|
|
|
auto message = std::format("Memcury caught an exception at [{:x}]\nPress Yes if you want the address to be copied to your clipboard", ExceptionInfo->ContextRecord->Rip);
|
|
if (MessageBoxA(nullptr, message.c_str(), "Error", MB_ICONERROR | MB_YESNO) == IDYES)
|
|
{
|
|
std::string clip = std::format("{:x}", ExceptionInfo->ContextRecord->Rip);
|
|
Util::CopyToClipboard(clip);
|
|
}
|
|
|
|
PrintStack(ExceptionInfo->ContextRecord);
|
|
|
|
FreezeCurrentThread();
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
template <ExceptionMode mode>
|
|
static auto SetExceptionMode() -> void
|
|
{
|
|
SetUnhandledExceptionFilter(MemcuryGlobalHandler<mode>);
|
|
}
|
|
}
|
|
|
|
namespace Globals
|
|
{
|
|
constexpr const bool bLogging = true;
|
|
|
|
static const char* moduleName = nullptr;
|
|
}
|
|
|
|
namespace ASM
|
|
{
|
|
//@todo: this whole namespace needs a rework, should somehow make this more modern and less ugly.
|
|
enum MNEMONIC : uint8_t
|
|
{
|
|
JMP_REL8 = 0xEB,
|
|
JMP_REL32 = 0xE9,
|
|
JMP_EAX = 0xE0,
|
|
CALL = 0xE8,
|
|
LEA = 0x8D,
|
|
CDQ = 0x99,
|
|
CMOVL = 0x4C,
|
|
CMOVS = 0x48,
|
|
CMOVNS = 0x49,
|
|
NOP = 0x90,
|
|
INT3 = 0xCC,
|
|
RETN_REL8 = 0xC2,
|
|
RETN = 0xC3,
|
|
NONE = 0x00
|
|
};
|
|
|
|
constexpr int SIZE_OF_JMP_RELATIVE_INSTRUCTION = 5;
|
|
constexpr int SIZE_OF_JMP_ABSLOUTE_INSTRUCTION = 13;
|
|
|
|
constexpr auto MnemonicToString(MNEMONIC e) -> const char*
|
|
{
|
|
switch (e)
|
|
{
|
|
case JMP_REL8:
|
|
return "JMP_REL8";
|
|
case JMP_REL32:
|
|
return "JMP_REL32";
|
|
case JMP_EAX:
|
|
return "JMP_EAX";
|
|
case CALL:
|
|
return "CALL";
|
|
case LEA:
|
|
return "LEA";
|
|
case CDQ:
|
|
return "CDQ";
|
|
case CMOVL:
|
|
return "CMOVL";
|
|
case CMOVS:
|
|
return "CMOVS";
|
|
case CMOVNS:
|
|
return "CMOVNS";
|
|
case NOP:
|
|
return "NOP";
|
|
case INT3:
|
|
return "INT3";
|
|
case RETN_REL8:
|
|
return "RETN_REL8";
|
|
case RETN:
|
|
return "RETN";
|
|
case NONE:
|
|
return "NONE";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
constexpr auto Mnemonic(const char* s) -> MNEMONIC
|
|
{
|
|
switch (Util::StrHash(s))
|
|
{
|
|
case Util::StrHash("JMP_REL8"):
|
|
return JMP_REL8;
|
|
case Util::StrHash("JMP_REL32"):
|
|
return JMP_REL32;
|
|
case Util::StrHash("JMP_EAX"):
|
|
return JMP_EAX;
|
|
case Util::StrHash("CALL"):
|
|
return CALL;
|
|
case Util::StrHash("LEA"):
|
|
return LEA;
|
|
case Util::StrHash("CDQ"):
|
|
return CDQ;
|
|
case Util::StrHash("CMOVL"):
|
|
return CMOVL;
|
|
case Util::StrHash("CMOVS"):
|
|
return CMOVS;
|
|
case Util::StrHash("CMOVNS"):
|
|
return CMOVNS;
|
|
case Util::StrHash("NOP"):
|
|
return NOP;
|
|
case Util::StrHash("INT3"):
|
|
return INT3;
|
|
case Util::StrHash("RETN_REL8"):
|
|
return RETN_REL8;
|
|
case Util::StrHash("RETN"):
|
|
return RETN;
|
|
default:
|
|
return NONE;
|
|
}
|
|
}
|
|
|
|
inline auto byteIsA(uint8_t byte, MNEMONIC opcode) -> bool
|
|
{
|
|
return byte == opcode;
|
|
}
|
|
|
|
inline auto byteIsAscii(uint8_t byte) -> bool
|
|
{
|
|
static constexpr bool isAscii[0x100] = {
|
|
false, false, false, false, false, false, false, false, false, true, true, false, false, true, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
|
|
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false
|
|
};
|
|
|
|
return isAscii[byte];
|
|
}
|
|
|
|
inline bool isJump(uint8_t byte)
|
|
{
|
|
return byte >= 0x70 && byte <= 0x7F;
|
|
}
|
|
|
|
static auto pattern2bytes(const char* pattern) -> std::vector<int>
|
|
{
|
|
auto bytes = std::vector<int>{};
|
|
const auto start = const_cast<char*>(pattern);
|
|
const auto end = const_cast<char*>(pattern) + strlen(pattern);
|
|
|
|
for (auto current = start; current < end; ++current)
|
|
{
|
|
if (*current == '?')
|
|
{
|
|
++current;
|
|
if (*current == '?')
|
|
++current;
|
|
bytes.push_back(-1);
|
|
}
|
|
else
|
|
{
|
|
bytes.push_back(strtoul(current, ¤t, 16));
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
}
|
|
|
|
namespace PE
|
|
{
|
|
inline auto SetCurrentModule(const char* moduleName) -> void
|
|
{
|
|
Globals::moduleName = moduleName;
|
|
}
|
|
|
|
inline auto GetModuleBase() -> uintptr_t
|
|
{
|
|
return reinterpret_cast<uintptr_t>(GetModuleHandleA(Globals::moduleName));
|
|
}
|
|
|
|
inline auto GetDOSHeader() -> PIMAGE_DOS_HEADER
|
|
{
|
|
return reinterpret_cast<PIMAGE_DOS_HEADER>(GetModuleBase());
|
|
}
|
|
|
|
inline auto GetNTHeaders() -> PIMAGE_NT_HEADERS
|
|
{
|
|
return reinterpret_cast<PIMAGE_NT_HEADERS>(GetModuleBase() + GetDOSHeader()->e_lfanew);
|
|
}
|
|
|
|
class Address
|
|
{
|
|
uintptr_t _address;
|
|
|
|
public:
|
|
Address()
|
|
{
|
|
_address = 0;
|
|
}
|
|
|
|
Address(uintptr_t address)
|
|
: _address(address)
|
|
{
|
|
}
|
|
|
|
Address(void* address)
|
|
: _address(reinterpret_cast<uintptr_t>(address))
|
|
{
|
|
}
|
|
|
|
auto operator=(uintptr_t address) -> Address
|
|
{
|
|
_address = address;
|
|
return *this;
|
|
}
|
|
|
|
auto operator=(void* address) -> Address
|
|
{
|
|
_address = reinterpret_cast<uintptr_t>(address);
|
|
return *this;
|
|
}
|
|
|
|
auto operator+(uintptr_t offset) -> Address
|
|
{
|
|
return Address(_address + offset);
|
|
}
|
|
|
|
bool operator>(uintptr_t offset)
|
|
{
|
|
return _address > offset;
|
|
}
|
|
|
|
bool operator>(Address address)
|
|
{
|
|
return _address > address._address;
|
|
}
|
|
|
|
bool operator<(uintptr_t offset)
|
|
{
|
|
return _address < offset;
|
|
}
|
|
|
|
bool operator<(Address address)
|
|
{
|
|
return _address < address._address;
|
|
}
|
|
|
|
bool operator>=(uintptr_t offset)
|
|
{
|
|
return _address >= offset;
|
|
}
|
|
|
|
bool operator>=(Address address)
|
|
{
|
|
return _address >= address._address;
|
|
}
|
|
|
|
bool operator<=(uintptr_t offset)
|
|
{
|
|
return _address <= offset;
|
|
}
|
|
|
|
bool operator<=(Address address)
|
|
{
|
|
return _address <= address._address;
|
|
}
|
|
|
|
bool operator==(uintptr_t offset)
|
|
{
|
|
return _address == offset;
|
|
}
|
|
|
|
bool operator==(Address address)
|
|
{
|
|
return _address == address._address;
|
|
}
|
|
|
|
bool operator!=(uintptr_t offset)
|
|
{
|
|
return _address != offset;
|
|
}
|
|
|
|
bool operator!=(Address address)
|
|
{
|
|
return _address != address._address;
|
|
}
|
|
|
|
auto RelativeOffset(uint32_t offset) -> Address
|
|
{
|
|
_address = ((_address + offset + 4) + *(int32_t*)(_address + offset));
|
|
return *this;
|
|
}
|
|
|
|
auto AbsoluteOffset(uint32_t offset) -> Address
|
|
{
|
|
_address = _address + offset;
|
|
return *this;
|
|
}
|
|
|
|
auto Jump() -> Address
|
|
{
|
|
if (ASM::isJump(*reinterpret_cast<UINT8*>(_address)))
|
|
{
|
|
UINT8 toSkip = *reinterpret_cast<UINT8*>(_address + 1);
|
|
_address = _address + 2 + toSkip;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto Get() -> uintptr_t
|
|
{
|
|
return _address;
|
|
}
|
|
|
|
template <typename T>
|
|
auto GetAs() -> T
|
|
{
|
|
return reinterpret_cast<T>(_address);
|
|
}
|
|
|
|
auto IsValid() -> bool
|
|
{
|
|
return _address != 0;
|
|
}
|
|
};
|
|
|
|
class Section
|
|
{
|
|
public:
|
|
std::string sectionName;
|
|
IMAGE_SECTION_HEADER rawSection;
|
|
|
|
static auto GetAllSections() -> std::vector<Section>
|
|
{
|
|
std::vector<Section> sections;
|
|
|
|
auto sectionsSize = GetNTHeaders()->FileHeader.NumberOfSections;
|
|
auto section = IMAGE_FIRST_SECTION(GetNTHeaders());
|
|
|
|
for (WORD i = 0; i < sectionsSize; i++, section++)
|
|
{
|
|
auto secName = std::string((char*)section->Name);
|
|
|
|
sections.push_back({ secName, *section });
|
|
}
|
|
|
|
return sections;
|
|
}
|
|
|
|
static auto GetSection(std::string sectionName) -> Section
|
|
{
|
|
for (auto& section : GetAllSections())
|
|
{
|
|
if (section.sectionName == sectionName)
|
|
{
|
|
return section;
|
|
}
|
|
}
|
|
|
|
MemcuryThrow("Section not found");
|
|
return Section{};
|
|
}
|
|
|
|
auto GetSectionSize() -> uint32_t
|
|
{
|
|
return rawSection.Misc.VirtualSize;
|
|
}
|
|
|
|
auto GetSectionStart() -> Address
|
|
{
|
|
return Address(GetModuleBase() + rawSection.VirtualAddress);
|
|
}
|
|
|
|
auto GetSectionEnd() -> Address
|
|
{
|
|
return Address(GetSectionStart() + GetSectionSize());
|
|
}
|
|
|
|
auto isInSection(Address address) -> bool
|
|
{
|
|
return address >= GetSectionStart() && address < GetSectionEnd();
|
|
}
|
|
};
|
|
}
|
|
|
|
class Scanner
|
|
{
|
|
PE::Address _address;
|
|
|
|
public:
|
|
Scanner(PE::Address address)
|
|
: _address(address)
|
|
{
|
|
}
|
|
|
|
static auto SetTargetModule(const char* moduleName) -> void
|
|
{
|
|
PE::SetCurrentModule(moduleName);
|
|
}
|
|
|
|
static auto FindPatternEx(HANDLE handle, const char* pattern, const char* mask, uint64_t begin, uint64_t end) -> Scanner
|
|
{
|
|
auto scan = [](const char* pattern, const char* mask, char* begin, unsigned int size) -> char*
|
|
{
|
|
size_t patternLen = strlen(mask);
|
|
for (unsigned int i = 0; i < size - patternLen; i++)
|
|
{
|
|
bool found = true;
|
|
for (unsigned int j = 0; j < patternLen; j++)
|
|
{
|
|
if (mask[j] != '?' && pattern[j] != *(begin + i + j))
|
|
{
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
return (begin + i);
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
uint64_t match = NULL;
|
|
SIZE_T bytesRead;
|
|
char* buffer = nullptr;
|
|
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
|
|
|
uint64_t curr = begin;
|
|
|
|
for (uint64_t curr = begin; curr < end; curr += mbi.RegionSize)
|
|
{
|
|
if (!VirtualQueryEx(handle, (void*)curr, &mbi, sizeof(mbi)))
|
|
continue;
|
|
|
|
if (mbi.State != MEM_COMMIT || mbi.Protect == PAGE_NOACCESS)
|
|
continue;
|
|
|
|
buffer = new char[mbi.RegionSize];
|
|
|
|
if (ReadProcessMemory(handle, mbi.BaseAddress, buffer, mbi.RegionSize, &bytesRead))
|
|
{
|
|
char* internalAddr = scan(pattern, mask, buffer, (unsigned int)bytesRead);
|
|
|
|
if (internalAddr != nullptr)
|
|
{
|
|
match = curr + (uint64_t)(internalAddr - buffer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
delete[] buffer;
|
|
|
|
MemcuryAssertM(match != 0, "FindPatternEx return nullptr");
|
|
|
|
return Scanner(match);
|
|
}
|
|
|
|
static auto FindPatternEx(HANDLE handle, const char* sig) -> Scanner
|
|
{
|
|
char pattern[100];
|
|
char mask[100];
|
|
|
|
char lastChar = ' ';
|
|
unsigned int j = 0;
|
|
|
|
for (unsigned int i = 0; i < strlen(sig); i++)
|
|
{
|
|
if ((sig[i] == '?' || sig[i] == '*') && (lastChar != '?' && lastChar != '*'))
|
|
{
|
|
pattern[j] = mask[j] = '?';
|
|
j++;
|
|
}
|
|
|
|
else if (isspace(lastChar))
|
|
{
|
|
pattern[j] = lastChar = (char)strtol(&sig[i], 0, 16);
|
|
mask[j] = 'x';
|
|
j++;
|
|
}
|
|
lastChar = sig[i];
|
|
}
|
|
pattern[j] = mask[j] = '\0';
|
|
|
|
auto module = (uint64_t)GetModuleHandle(nullptr);
|
|
|
|
return FindPatternEx(handle, pattern, mask, module, module + Memcury::PE::GetNTHeaders()->OptionalHeader.SizeOfImage);
|
|
}
|
|
|
|
static auto FindPattern(const char* signature, bool bWarnIfNotFound = true) -> Scanner
|
|
{
|
|
PE::Address add{ nullptr };
|
|
|
|
const auto sizeOfImage = PE::GetNTHeaders()->OptionalHeader.SizeOfImage;
|
|
auto patternBytes = ASM::pattern2bytes(signature);
|
|
const auto scanBytes = reinterpret_cast<std::uint8_t*>(PE::GetModuleBase());
|
|
|
|
const auto s = patternBytes.size();
|
|
const auto d = patternBytes.data();
|
|
|
|
for (auto i = 0ul; i < sizeOfImage - s; ++i)
|
|
{
|
|
bool found = true;
|
|
for (auto j = 0ul; j < s; ++j)
|
|
{
|
|
if (scanBytes[i + j] != d[j] && d[j] != -1)
|
|
{
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
add = reinterpret_cast<uintptr_t>(&scanBytes[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// MemcuryAssertM(add != 0, "FindPattern return nullptr");
|
|
|
|
if (bWarnIfNotFound)
|
|
{
|
|
if (add == 0)
|
|
{
|
|
LOG_WARN(LogMemory, "Failed to find {}", signature);
|
|
// MessageBoxA(0, ("FindPattern " + std::string(signature) + " null").c_str(), "Memcury", MB_ICONERROR);
|
|
}
|
|
}
|
|
|
|
return Scanner(add);
|
|
}
|
|
|
|
static auto FindPointerRef(void* Pointer, int useRefNum = 0, bool bUseFirstResult = false) -> Scanner // credit me and ender
|
|
{
|
|
PE::Address add{ nullptr };
|
|
|
|
auto textSection = PE::Section::GetSection(".text");
|
|
|
|
const auto scanBytes = reinterpret_cast<std::uint8_t*>(textSection.GetSectionStart().Get());
|
|
|
|
int aa = 0;
|
|
|
|
// scan only text section
|
|
for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++)
|
|
{
|
|
if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && (scanBytes[i + 1] == ASM::LEA || scanBytes[i + 1] == 0x8B))
|
|
{
|
|
if (PE::Address(&scanBytes[i]).RelativeOffset(3).GetAs<void*>() == Pointer)
|
|
{
|
|
add = PE::Address(&scanBytes[i]);
|
|
|
|
// LOG_INFO(LogDev, "2add: 0x{:x}", add.Get() - __int64(GetModuleHandleW(0)));
|
|
|
|
if (bUseFirstResult)
|
|
return Scanner(add);
|
|
|
|
/* if (++aa > useRefNum)
|
|
break; */
|
|
}
|
|
}
|
|
|
|
if (scanBytes[i] == ASM::CALL)
|
|
{
|
|
if (PE::Address(&scanBytes[i]).RelativeOffset(1).GetAs<void*>() == Pointer)
|
|
{
|
|
add = PE::Address(&scanBytes[i]);
|
|
|
|
// LOG_INFO(LogDev, "1add: 0x{:x}", add.Get() - __int64(GetModuleHandleW(0)));
|
|
|
|
if (bUseFirstResult)
|
|
return Scanner(add);
|
|
|
|
/* if (++aa > useRefNum)
|
|
break; */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (add == 0)
|
|
{
|
|
MessageBoxA(0, "FindPointerRef return nullptr", "Memcury", MB_OK);
|
|
}
|
|
else
|
|
{
|
|
// MessageBoxA(0, std::format("FindPointerRef return 0x{:x}", add.Get() - __int64(GetModuleHandleW(0))).c_str(), "Memcury", MB_OK);
|
|
}
|
|
|
|
return Scanner(add);
|
|
}
|
|
|
|
// Supports wide and normal strings both std and pointers
|
|
template <typename T = const wchar_t*>
|
|
static auto FindStringRef(T string, bool bWarnIfNotFound = true, int useRefNum = 0, bool bIsInFunc = false) -> Scanner
|
|
{
|
|
PE::Address add{ nullptr };
|
|
|
|
constexpr auto bIsWide = std::is_same<T, const wchar_t*>::value;
|
|
constexpr auto bIsChar = std::is_same<T, const char*>::value;
|
|
|
|
constexpr auto bIsPtr = bIsWide || bIsChar;
|
|
|
|
auto textSection = PE::Section::GetSection(".text");
|
|
auto rdataSection = PE::Section::GetSection(".rdata");
|
|
|
|
const auto scanBytes = reinterpret_cast<std::uint8_t*>(textSection.GetSectionStart().Get());
|
|
|
|
int aa = 0;
|
|
|
|
// scan only text section
|
|
for (DWORD i = 0x0; i < textSection.GetSectionSize(); i++)
|
|
{
|
|
if ((scanBytes[i] == ASM::CMOVL || scanBytes[i] == ASM::CMOVS) && scanBytes[i + 1] == ASM::LEA)
|
|
{
|
|
auto stringAdd = PE::Address(&scanBytes[i]).RelativeOffset(3);
|
|
|
|
// Check if the string is in the .rdata section
|
|
if (rdataSection.isInSection(stringAdd))
|
|
{
|
|
auto strBytes = stringAdd.GetAs<std::uint8_t*>();
|
|
|
|
// Check if the first char is printable
|
|
if (ASM::byteIsAscii(strBytes[0]))
|
|
{
|
|
if constexpr (!bIsPtr)
|
|
{
|
|
// typedef T::value_type char_type;
|
|
using char_type = std::decay_t<std::remove_pointer_t<T>>;
|
|
|
|
auto lea = stringAdd.GetAs<char_type*>();
|
|
|
|
T leaT(lea);
|
|
|
|
if (leaT == string)
|
|
{
|
|
add = PE::Address(&scanBytes[i]);
|
|
|
|
if (++aa > useRefNum)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto lea = stringAdd.GetAs<T>();
|
|
|
|
if constexpr (bIsWide)
|
|
{
|
|
if (wcscmp(string, lea) == 0)
|
|
{
|
|
add = PE::Address(&scanBytes[i]);
|
|
|
|
if (++aa > useRefNum)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strcmp(string, lea) == 0)
|
|
{
|
|
add = PE::Address(&scanBytes[i]);
|
|
|
|
if (++aa > useRefNum)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MemcuryAssertM(add != 0, "FindStringRef return nullptr");
|
|
|
|
if (bWarnIfNotFound)
|
|
{
|
|
if (add == 0)
|
|
{
|
|
if constexpr (bIsWide)
|
|
{
|
|
std::wstring wstr = std::wstring(string);
|
|
LOG_WARN(LogMemory, "Failed to find String {}", std::string(wstr.begin(), wstr.end()));
|
|
|
|
// auto aaa = (L"failed FindStringRef " + std::wstring(string));
|
|
// MessageBoxA(0, std::string(aaa.begin(), aaa.end()).c_str(), "Memcury", MB_ICONERROR);
|
|
}
|
|
else
|
|
{
|
|
LOG_WARN(LogMemory, "Failed to find String {}", string);
|
|
// MessageBoxA(0, ("failed FindStringRef " + std::string(string)).c_str(), "Memcury", MB_ICONERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (add.Get())
|
|
{
|
|
if (bIsInFunc)
|
|
{
|
|
for (int i = 0; i < 300; i++)
|
|
{
|
|
if (*(uint8_t*)(add.Get() - i) == 0x48 && *(uint8_t*)(add.Get() - i + 1) == 0x83)
|
|
{
|
|
// MessageBoxA(0, std::format("0x{:x}", (__int64(add.Get() - i) - __int64(GetModuleHandleW(0)))).c_str(), "Memcury", MB_OK);
|
|
|
|
auto beginFunc = Scanner(add.Get() - i);
|
|
|
|
auto ref = FindPointerRef(beginFunc.GetAs<void*>());
|
|
|
|
return ref;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Scanner(add);
|
|
}
|
|
|
|
auto Jump() -> Scanner
|
|
{
|
|
_address.Jump();
|
|
return *this;
|
|
}
|
|
|
|
inline auto ScanFor(std::vector<uint8_t> opcodesToFind, bool forward = true, int toSkip = 0) -> Scanner
|
|
{
|
|
const auto scanBytes = _address.GetAs<std::uint8_t*>();
|
|
|
|
for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--)
|
|
{
|
|
bool found = true;
|
|
|
|
for (int k = 0; k < opcodesToFind.size() && found; k++)
|
|
{
|
|
auto& currentOpcode = opcodesToFind[k];
|
|
|
|
if (currentOpcode == -1)
|
|
continue;
|
|
|
|
// std::cout << std::format("[{} {}] 0x{:x}\n", i, k, currentOpcode);
|
|
|
|
found = currentOpcode == scanBytes[i + k];
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
_address = &scanBytes[i];
|
|
if (toSkip != 0)
|
|
{
|
|
return ScanFor(opcodesToFind, forward, toSkip - 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto FindFunctionBoundary(bool forward = false) -> Scanner
|
|
{
|
|
const auto scanBytes = _address.GetAs<std::uint8_t*>();
|
|
|
|
for (auto i = (forward ? 1 : -1); forward ? (i < 2048) : (i > -2048); forward ? i++ : i--)
|
|
{
|
|
if ( // ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL8) ||
|
|
// ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_REL32) ||
|
|
// ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::JMP_EAX) ||
|
|
ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN_REL8) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::RETN) || ASM::byteIsA(scanBytes[i], ASM::MNEMONIC::INT3))
|
|
{
|
|
_address = (uintptr_t)&scanBytes[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto RelativeOffset(uint32_t offset) -> Scanner
|
|
{
|
|
if (!_address.Get())
|
|
{
|
|
LOG_WARN(LogMemory, "Trying to relative offset an invalid offset!");
|
|
return *this;
|
|
}
|
|
|
|
_address.RelativeOffset(offset);
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto AbsoluteOffset(uint32_t offset) -> Scanner
|
|
{
|
|
_address.AbsoluteOffset(offset);
|
|
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
auto GetAs() -> T
|
|
{
|
|
return _address.GetAs<T>();
|
|
}
|
|
|
|
auto Get() -> uintptr_t
|
|
{
|
|
return _address.Get();
|
|
}
|
|
|
|
auto IsValid() -> bool
|
|
{
|
|
return _address.IsValid();
|
|
}
|
|
};
|
|
|
|
/* Bad don't use it tbh... */
|
|
class TrampolineHook
|
|
{
|
|
void** originalFunctionPtr;
|
|
PE::Address originalFunction;
|
|
PE::Address hookFunction;
|
|
PE::Address allocatedPage;
|
|
std::vector<uint8_t> restore;
|
|
|
|
void PointToCodeIfNot(PE::Address& ptr)
|
|
{
|
|
auto bytes = ptr.GetAs<std::uint8_t*>();
|
|
|
|
if (ASM::byteIsA(bytes[0], ASM::MNEMONIC::JMP_REL32))
|
|
{
|
|
ptr = bytes + 5 + *(int32_t*)&bytes[1];
|
|
}
|
|
}
|
|
|
|
void* AllocatePageNearAddress(void* targetAddr)
|
|
{
|
|
SYSTEM_INFO sysInfo;
|
|
GetSystemInfo(&sysInfo);
|
|
const uint64_t PAGE_SIZE = sysInfo.dwPageSize;
|
|
|
|
uint64_t startAddr = (uint64_t(targetAddr) & ~(PAGE_SIZE - 1)); // round down to nearest page boundary
|
|
uint64_t minAddr = fmin(startAddr - 0x7FFFFF00, (uint64_t)sysInfo.lpMinimumApplicationAddress);
|
|
uint64_t maxAddr = fmax(startAddr + 0x7FFFFF00, (uint64_t)sysInfo.lpMaximumApplicationAddress);
|
|
|
|
uint64_t startPage = (startAddr - (startAddr % PAGE_SIZE));
|
|
|
|
for (uint64_t pageOffset = 1; pageOffset; pageOffset++)
|
|
{
|
|
uint64_t byteOffset = pageOffset * PAGE_SIZE;
|
|
uint64_t highAddr = startPage + byteOffset;
|
|
uint64_t lowAddr = (startPage > byteOffset) ? startPage - byteOffset : 0;
|
|
|
|
bool needsExit = highAddr > maxAddr && lowAddr < minAddr;
|
|
|
|
if (highAddr < maxAddr)
|
|
{
|
|
void* outAddr = VirtualAlloc((void*)highAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
|
if (outAddr)
|
|
return outAddr;
|
|
}
|
|
|
|
if (lowAddr > minAddr)
|
|
{
|
|
void* outAddr = VirtualAlloc((void*)lowAddr, PAGE_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
|
if (outAddr != nullptr)
|
|
return outAddr;
|
|
}
|
|
|
|
if (needsExit)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void WriteAbsoluteJump(void* jumpLocation, void* destination)
|
|
{
|
|
uint8_t absJumpInstructions[] = {
|
|
ASM::Mnemonic("CMOVNS"), 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr
|
|
0x41, 0xFF, 0xE2 // jmp r10
|
|
};
|
|
|
|
auto destination64 = (uint64_t)destination;
|
|
memcpy(&absJumpInstructions[2], &destination64, sizeof(destination64));
|
|
memcpy(jumpLocation, absJumpInstructions, sizeof(absJumpInstructions));
|
|
}
|
|
|
|
uintptr_t PrepareRestore()
|
|
{
|
|
/*
|
|
This is not a correct way to do it at all, since not all functions sub from the stack
|
|
This needs so much more tests, but it works for now.
|
|
*/
|
|
|
|
Scanner scanner(originalFunction);
|
|
scanner.ScanFor({ 0x48, 0x83, 0xEC }); // sub rsp
|
|
|
|
auto restoreSize = scanner.Get() - originalFunction.Get();
|
|
|
|
MemcuryAssert(restoreSize > 0 && restoreSize < 0x100);
|
|
|
|
restore.reserve(restoreSize);
|
|
for (auto i = 0; i < restoreSize; i++)
|
|
{
|
|
restore.push_back(originalFunction.GetAs<uint8_t*>()[i]);
|
|
}
|
|
|
|
return restoreSize;
|
|
}
|
|
|
|
void WriteRestore()
|
|
{
|
|
auto restorePtr = allocatedPage + ASM::SIZE_OF_JMP_ABSLOUTE_INSTRUCTION + 2;
|
|
|
|
memcpy(restorePtr.GetAs<void*>(), restore.data(), restore.size());
|
|
|
|
*originalFunctionPtr = restorePtr.GetAs<void*>();
|
|
|
|
// Write a jump back to where the execution should resume
|
|
restorePtr.AbsoluteOffset((uint32_t)restore.size());
|
|
|
|
auto contuineExecution = originalFunction + restore.size();
|
|
|
|
WriteAbsoluteJump(restorePtr.GetAs<void*>(), contuineExecution.GetAs<void*>());
|
|
}
|
|
|
|
auto PrepareJMPInstruction(uint64_t dst)
|
|
{
|
|
uint8_t bytes[5] = { ASM::Mnemonic("JMP_REL32"), 0x0, 0x0, 0x0, 0x0 };
|
|
|
|
const uint64_t relAddr = dst - (originalFunction.Get() + ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION);
|
|
memcpy(bytes + 1, &relAddr, 4);
|
|
|
|
return std::move(bytes);
|
|
}
|
|
|
|
bool IsHooked()
|
|
{
|
|
return originalFunction.GetAs<uint8_t*>()[0] == ASM::Mnemonic("JMP_REL32");
|
|
}
|
|
|
|
public:
|
|
TrampolineHook(void** originalFunction, void* hookFunction)
|
|
{
|
|
this->originalFunctionPtr = originalFunction;
|
|
|
|
this->originalFunction = *originalFunction;
|
|
this->hookFunction = hookFunction;
|
|
|
|
PointToCodeIfNot(this->originalFunction);
|
|
PointToCodeIfNot(this->hookFunction);
|
|
};
|
|
|
|
bool Commit()
|
|
{
|
|
auto fnStart = originalFunction.GetAs<void*>();
|
|
|
|
auto restoreSize = PrepareRestore();
|
|
|
|
if (!allocatedPage.IsValid())
|
|
{
|
|
allocatedPage = AllocatePageNearAddress(fnStart);
|
|
}
|
|
|
|
memset(allocatedPage.GetAs<void*>(), ASM::MNEMONIC::INT3, 0x1000);
|
|
|
|
WriteAbsoluteJump(allocatedPage.GetAs<void*>(), hookFunction.GetAs<void*>());
|
|
|
|
DWORD oldProtect;
|
|
VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect);
|
|
|
|
auto jmpInstruction = PrepareJMPInstruction(allocatedPage.Get());
|
|
|
|
WriteRestore();
|
|
|
|
memset(fnStart, ASM::MNEMONIC::INT3, restoreSize);
|
|
memcpy(fnStart, jmpInstruction, ASM::SIZE_OF_JMP_RELATIVE_INSTRUCTION);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Revert()
|
|
{
|
|
auto fnStart = originalFunction.GetAs<void*>();
|
|
|
|
DWORD oldProtect;
|
|
VirtualProtect(fnStart, 1024, PAGE_EXECUTE_READWRITE, &oldProtect);
|
|
|
|
memcpy(fnStart, restore.data(), restore.size());
|
|
|
|
*originalFunctionPtr = originalFunction.GetAs<void*>();
|
|
|
|
// VirtualFree(allocatedPage.GetAs<void*>(), 0x1000, MEM_RELEASE);
|
|
|
|
return true;
|
|
}
|
|
|
|
auto Toggle()
|
|
{
|
|
if (IsHooked())
|
|
Revert();
|
|
else
|
|
Commit();
|
|
|
|
return IsHooked();
|
|
}
|
|
};
|
|
|
|
namespace VEHHook
|
|
{
|
|
struct HOOK_INFO
|
|
{
|
|
void* Original;
|
|
void* Detour;
|
|
|
|
HOOK_INFO(void* Original, void* Detour)
|
|
: Original(Original)
|
|
, Detour(Detour)
|
|
{
|
|
}
|
|
};
|
|
|
|
inline std::vector<HOOK_INFO> Hooks;
|
|
inline std::vector<DWORD> HookProtections;
|
|
inline HANDLE ExceptionHandler;
|
|
|
|
inline long Handler(EXCEPTION_POINTERS* Exception)
|
|
{
|
|
if (Exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
|
|
{
|
|
auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Rip = Exception->ContextRecord->Rip](const HOOK_INFO& Hook)
|
|
{ return Hook.Original == (void*)Rip; });
|
|
if (Itr != Hooks.end())
|
|
{
|
|
Exception->ContextRecord->Rip = (uintptr_t)Itr->Detour;
|
|
}
|
|
|
|
Exception->ContextRecord->EFlags |= 0x100; // SINGLE_STEP_FLAG
|
|
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
else if (Exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
|
|
{
|
|
// TODO: find a way to only vp the function that about to get executed
|
|
for (auto& Hook : Hooks)
|
|
{
|
|
DWORD dwOldProtect;
|
|
VirtualProtect(Hook.Original, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &dwOldProtect);
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
inline bool Init()
|
|
{
|
|
if (ExceptionHandler == nullptr)
|
|
{
|
|
ExceptionHandler = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)Handler);
|
|
}
|
|
return ExceptionHandler != nullptr;
|
|
}
|
|
|
|
inline bool AddHook(void* Target, void* Detour)
|
|
{
|
|
if (ExceptionHandler == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Util::IsSamePage(Target, Detour))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!VirtualProtect(Target, 1, PAGE_EXECUTE_READ | PAGE_GUARD, &HookProtections.emplace_back()))
|
|
{
|
|
HookProtections.pop_back();
|
|
return false;
|
|
}
|
|
|
|
Hooks.emplace_back(Target, Detour);
|
|
return true;
|
|
}
|
|
|
|
inline bool RemoveHook(void* Original)
|
|
{
|
|
auto Itr = std::find_if(Hooks.begin(), Hooks.end(), [Original](const HOOK_INFO& Hook)
|
|
{ return Hook.Original == Original; });
|
|
|
|
if (Itr == Hooks.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto ProtItr = HookProtections.begin() + std::distance(Hooks.begin(), Itr);
|
|
Hooks.erase(Itr);
|
|
|
|
DWORD dwOldProtect;
|
|
bool Ret = VirtualProtect(Original, 1, *ProtItr, &dwOldProtect);
|
|
HookProtections.erase(ProtItr);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline int HexToDec(std::string hexValue)
|
|
{
|
|
int len = hexValue.size();
|
|
// Initializing base value to 1, i.e 16 ^ 0
|
|
int base = 1;
|
|
int dec_value = 0;
|
|
// extracting characters as digits from last
|
|
// character
|
|
for (int i = len - 1; i >= 0; i--) {
|
|
if (hexValue[i] >= '0' && hexValue[i] <= '9') {
|
|
dec_value += (int(hexValue[i]) - 48) * base;
|
|
|
|
|
|
// incrementing base by power
|
|
base = base * 16;
|
|
}
|
|
// if character lies in 'A' - 'F' , converting
|
|
// it to integral 10 - 15 by subtracting 55
|
|
// from ASCII value
|
|
else if (hexValue[i] >= 'A' && hexValue[i] <= 'F') {
|
|
dec_value += (int(hexValue[i]) - 55) * base;
|
|
|
|
|
|
// incrementing base by power
|
|
base = base * 16;
|
|
}
|
|
}
|
|
return dec_value;
|
|
}
|
|
|
|
inline std::string GetBytes(uintptr_t Address, int count = 10) {
|
|
std::string Bytes;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
auto byte = *(uint8_t*)(Address + i) & 0xff;
|
|
auto Byte = (byte == 0) ? "? " : std::format("{:x} ", byte);
|
|
|
|
if (Byte.length() == 2 && byte != 0) // 2 because of the space
|
|
Byte = "0" + Byte;
|
|
|
|
Bytes += Byte;
|
|
}
|
|
|
|
std::transform(Bytes.begin(), Bytes.end(), Bytes.begin(), ::toupper);
|
|
|
|
return Bytes;
|
|
}
|
|
|
|
inline bool IsBadReadPtr(void* p)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
|
if (::VirtualQuery(p, &mbi, sizeof(mbi)))
|
|
{
|
|
DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
|
|
bool b = !(mbi.Protect & mask);
|
|
// check the page is not a guard page
|
|
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true;
|
|
|
|
return b;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void VirtualSwap(void** VTable, int Idx, void* NewFunc)
|
|
{
|
|
DWORD dwProtection;
|
|
VirtualProtect(&VTable[Idx], 8, PAGE_EXECUTE_READWRITE, &dwProtection);
|
|
|
|
VTable[Idx] = NewFunc;
|
|
|
|
DWORD dwTemp;
|
|
VirtualProtect(&VTable[Idx], 8, dwProtection, &dwTemp);
|
|
}
|
|
|
|
// Finds a string ref, then goes searches xref of the function that it's in and returns that address.
|
|
inline uintptr_t FindFunctionCall(const wchar_t* Name, const std::vector<uint8_t>& Bytes = std::vector<uint8_t>{ 0x48, 0x89, 0x5C }, int skip = 0) // credit ender & me
|
|
{
|
|
auto FunctionPtr = Memcury::Scanner::FindStringRef(Name, true, skip).ScanFor({ 0x48, 0x8D, 0x0D }).RelativeOffset(3).GetAs<void*>();
|
|
|
|
auto PtrRef = Memcury::Scanner::FindPointerRef(FunctionPtr);
|
|
|
|
/* if (!PtrRef.Get() || PtrRef.Get() == __int64(FunctionPtr))
|
|
{
|
|
std::wstring NameWStr = std::wstring(Name);
|
|
LOG_WARN(LogMemory, "Failed to find pointer reference for {}", std::string(NameWStr.begin(), NameWStr.end()));
|
|
return 0;
|
|
} */
|
|
|
|
return PtrRef.ScanFor(Bytes, false).Get();
|
|
} |