Files
watrbx-game-engine/App/util/ProgramMemoryChecker.h
2025-10-28 14:05:46 -04:00

367 lines
12 KiB
C++

#pragma once
#include <boost/scoped_ptr.hpp>
#include "rbx/rbxTime.h"
#include <vector>
#include "Security/RandomConstant.h"
#include "v8datamodel/HackDefines.h"
#include "util/HeapValue.h"
#include "Security/ApiSecurity.h"
namespace RBX {
namespace Hasher
{
// The PMC constructor assumes a specific ordering of these items.
enum HashSection
{
kGoldHashStart = 0,
kGoldHashEnd = 1,
kGoldHashRot = 2,
kRdataHash = 3,
kVmpPlainHash = 4,
kVmpMutantHash = 5,
kIatHash = 6,
kMiscHash = 7,
kMsvcHash = 8,
kVmp0MiscHash = 9,
kVmp1MiscHash = 10,
kNonGoldHashRot = 11,
kNumberOfSectionHashes = 12,
kGoldHashStruct = 12,
kAllHashStruct = 13,
kNumberOfHashes = 14
};
// These do not have a 1:1 relation with the indicies above.
enum HashFailures
{
// 0x?000
kVmp1MiscHashFail = 1<<15,
kVmp0MiscHashFail = 1<<14,
kVmpMutantHashFail = 1<<13,
kIatHashFail = 1<<12,
// 0x0?00
kGoldHashFail = 1<<11,
kNonceFail = 1<<10,
kAllHashStructFail = 1<<9,
kGoldHashStructFail = 1<<8,
// 0x00?0
kNonGoldHashRotFail = 1<<7,
kVmpPlainHashFail = 1<<6,
kMsvcHashFail = 1<<5,
kRdataHashFail = 1<<4,
// 0x000?
kMiscHashFail = 1<<3,
kGoldHashRotFail = 1<<2,
kGoldHashEndFail = 1<<1,
kGoldHashStartFail = 1<<0
};
static const unsigned int kGoldHashMask = kGoldHashFail;
static const unsigned int kDiffHashMask = kGoldHashStartFail | kGoldHashEndFail
| kMiscHashFail | kRdataHashFail | kMsvcHashFail | kGoldHashStructFail
| kAllHashStructFail | kVmpPlainHashFail | kVmpMutantHashFail | kVmp0MiscHashFail
| kVmp1MiscHashFail | kIatHashFail;
static const unsigned int kMovingHashMask = kNonceFail | kGoldHashRotFail
| kNonGoldHashRotFail;
static const int zeroPad[4] = {0,0,0,0};
static const unsigned int kPmcNonceGoodInc = 3692164867;
static const unsigned int kPmcNonceBadInc = 3692164869;
static const unsigned int kPmcNonceGoodIncInv = 2880154539; //0xABABABAB supplied by irc user. unimportant fact.
}
struct ScanRegion
{
char* startingAddress;
unsigned int size;
// ".text" and ".rdata" appear in several places in RAM, so it is safe to have them as literals.
// likewise, exploiters would quickly realize that .text and .rdata are scanned.
// if they change the value to something else, it would crash.
static ScanRegion getScanRegion(const char* moduleName, const char* RegionName);
ScanRegion() : startingAddress(NULL), size(0){}
ScanRegion(const ScanRegion &initValue) : startingAddress(initValue.startingAddress), size(initValue.size){}
ScanRegion(char* startingAddress, unsigned int size) : startingAddress(startingAddress), size(size) {}
};
struct ScanRegionTest : ScanRegion
{
void* hashState;
unsigned int lastHashValue;
bool closeHash;
bool useHashValueInStructHash;
bool useHashAddrSizeInStructHash;
ScanRegionTest() : ScanRegion(),
hashState(NULL),
lastHashValue(0),
closeHash(false),
useHashValueInStructHash(true),
useHashAddrSizeInStructHash(true) {}
ScanRegionTest(ScanRegion initValue) : ScanRegion(initValue),
hashState(NULL),
lastHashValue(0),
closeHash(false),
useHashValueInStructHash(true),
useHashAddrSizeInStructHash(true) {}
};
struct PmcHashContainer
{
typedef std::vector<unsigned int> HashVector;
unsigned int nonce;
HashVector hash;
PmcHashContainer(const PmcHashContainer& init);
PmcHashContainer() : nonce(0) {}
};
extern PmcHashContainer pmcHash;
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
class NtApiCaller
{
private:
static const uintptr_t kKey = 111777;
static const unsigned kNtQvmEndToken = 0x0018C204; // sub esp, 4; ret 0x18;
static const unsigned kNtGtxEndToken = 0x0008C204; // sub esp, 4; ret 0x08;
// on windows xp, this is 0x00?8C212FF // call dword ptr [edx]; ret 0x?8
static const unsigned kEndMask = 0xFFFFFF00;
typedef DWORD (NTAPI *NtQvmPfn)(HANDLE, PVOID, DWORD, PVOID, ULONG, PULONG);
typedef DWORD (NTAPI *NtGtxPfn)(HANDLE, PCONTEXT);
HANDLE thisProcess;
HeapValue<size_t> hashEndSize;
HeapValue<uintptr_t> ntQvmAsUint;
HeapValue<size_t> ntQvmCallHash;
HeapValue<uintptr_t> ntGtxAsUint;
HeapValue<size_t> ntGtxCallHash;
HeapValue<uintptr_t> ntdllTextBase;
HeapValue<size_t> ntdllSize;
static unsigned int hashFeed(unsigned int state, unsigned int value)
{
return state + _rotl((state+kKey)*(value-kKey), 7);
}
__forceinline void initApiFunction(uintptr_t pfn, HeapValue<uintptr_t>& pfnOut, uintptr_t callTemplate, HeapValue<uintptr_t>& callHashOut, HeapValue<uintptr_t>& hashEndSizeOut, unsigned int endToken)
{
if (!pfn || (pfn - ntdllTextBase > ntdllSize))
{
// couldn't find NtQueryVirtualMemory or it wasn't in the dll.
Tokens::apiToken.addFlagSafe(kNtApiNoApi);
}
pfnOut = pfn;
// ZwFilterToken isn't important, but it is called in a near identical way as NtQVM
// generate a hash of how ntdll will be called.
if (callTemplate && (callTemplate - ntdllTextBase < ntdllSize))
{
for (int i = 5; i < 32; ++i) // assume call takes < 32B on x86/WoW64
{
unsigned int value = *reinterpret_cast<unsigned int*>(callTemplate + i);
callHashOut = hashFeed(callHashOut, value);
if((kEndMask & value) == (kEndMask & endToken))
{
hashEndSizeOut = i;
break;
}
}
if (hashEndSizeOut == 0)
{
// didn't find the end token for some reason.
Tokens::apiToken.addFlagSafe(kNtApiNoSyscall);
}
}
else
{
// In this case, ZwFilterToken didn't exist for some reason, or wasn't in ntdll
Tokens::apiToken.addFlagSafe(kNtApiNoTemplate);
}
}
__forceinline bool checkCaller(uintptr_t pfn, const HeapValue<uintptr_t>& callHash)
{
const unsigned char* const& funcMem = reinterpret_cast<const unsigned char*>(pfn);
// probably should check to make sure this is within ntdll.
if (pfn && (pfn - ntdllTextBase < ntdllSize))
{
bool canCall = true;
// Check Early Hooking:
// mov eax, dword 0x0000???? <- B8 ?? ?? 00 00
if (funcMem[0] != 0xB8 || funcMem[3] != 0x00 || funcMem[4] != 0x00)
{
canCall = false;
Tokens::apiToken.addFlagSafe(kNtApiEarly);
}
// Check hash of function
unsigned int checkHash = 0;
int endIdx = hashEndSize;
for (int i = 5; i < 32; ++i) // assume call takes < 32B on x86 and WoW64
{
unsigned int value = *reinterpret_cast<volatile const unsigned int*>(funcMem + i);
checkHash = hashFeed(checkHash, value);
if(i == endIdx)
{
break;
}
}
if (checkHash != callHash)
{
canCall = false;
Tokens::apiToken.addFlagSafe(kNtApiHash);
}
// This decodes and calls the function pointer
return canCall;
}
else
{
// not defined or not within ntdll.
Tokens::apiToken.addFlagSafe(kNtApiNoCall);
}
return false;
}
public:
__forceinline DWORD virtualQuery(void* addr, MEMORY_BASIC_INFORMATION* info, size_t cb)
{
volatile DWORD result = 0;
uintptr_t pfn = ntQvmAsUint;
if (checkCaller(pfn, ntQvmCallHash))
{
result = reinterpret_cast<NtQvmPfn>(pfn)(thisProcess, addr, 0, info, cb, NULL);
}
pfn = 0;
return result;
}
__forceinline DWORD getThreadContext(HANDLE thread, CONTEXT* ctx)
{
volatile DWORD result = 0;
uintptr_t pfn = ntGtxAsUint;
if (checkCaller(pfn, ntGtxCallHash))
{
result = reinterpret_cast<NtGtxPfn>(pfn)(thread, ctx);
}
pfn = 0;
return result;
}
__forceinline bool isNtdllAddress(uintptr_t addr)
{
return (addr - ntdllTextBase) < ntdllSize;
}
NtApiCaller();
};
#endif
class ProgramMemoryChecker
{
protected:
unsigned int hsceHashOrReduced;
unsigned int hsceHashAndReduced;
public:
static const int kHASH_SEED_INIT = 42;
static const int kAllDone = 0xCCCCCCCC; // A number that is non-zero.
static const int kLuaLockOk = 0x1842783;
static const int kLuaLockBad = 0;
static const int kSteps = 30;
static const unsigned int kBlock = 16;
ProgramMemoryChecker();
unsigned int bytesPerStep;
unsigned int currentRegion;
const char* currentMemory;
std::vector<ScanRegionTest> scanningRegions;
unsigned int lastCompletedHash;
unsigned int lastGoldenHash;
Time lastCompletedTime;
unsigned int step();
unsigned int getLastCompletedHash() const;
unsigned int getLastGoldenHash() const;
Time getLastCompletedTime() const;
void getLastHashes(PmcHashContainer::HashVector& outHashes) const;
// This is a hash of the hashes, as well as a hash of the region information.
unsigned int hashScanningRegions(size_t regions = Hasher::kNumberOfHashes-2) const;
// return hash of HumanoidState::computeEvent, update hsceHashOrReduce.
unsigned int updateHsceHash();
unsigned int getHsceOrHash() const;
unsigned int getHsceAndHash() const;
// Should look at return code.
int isLuaLockOk() const;
// Check for stealthedit. Stealthedit sets some pages to non-executable
// and then catches the resulting exception, using the opportunity
// to redirect to a modified page without disturbing the hash mechanism.
// http://www.szemelyesintegracio.hu/cheats/41-game-hacking-articles/419-stealthedit
static bool areMemoryPagePermissionsSetupForHacking();
};
#ifdef _WIN32
_declspec(align(8)) extern const char* const maskAddr;
_declspec(align(8)) extern const char* const goldHash;
unsigned int protectVmpSections();
#else
__attribute__((__aligned__(8))) extern const char* const maskAddr;
__attribute__((__aligned__(8))) extern const char* const goldHash;
#endif
namespace Security{
// The storage for hash checker related security constants.
extern volatile const size_t rbxGoldHash;
// The lower part of .text
extern volatile const uintptr_t rbxLowerBase;
extern volatile const size_t rbxLowerSize;
// The upper part of .text
extern volatile const uintptr_t rbxUpperBase;
extern volatile const size_t rbxUpperSize;
// the .rdata section
extern volatile const uintptr_t rbxRdataBase;
extern volatile const size_t rbxRdataSize;
// the vmp sections
extern volatile const uintptr_t rbxVmpBase;
extern volatile const size_t rbxVmpSize;
// the Import Address (thunk) Table
extern volatile const uintptr_t rbxIatBase;
extern volatile const size_t rbxIatSize;
// the vmp sections (plain .text section)
extern volatile const uintptr_t rbxVmpPlainBase;
extern volatile const size_t rbxVmpPlainSize;
// the vmp sections (mutation .text section)
extern volatile const uintptr_t rbxVmpMutantBase;
extern volatile const size_t rbxVmpMutantSize;
// the vmp sections (don't know)
extern volatile const uintptr_t rbxVmp0MiscBase;
extern volatile const size_t rbxVmp0MiscSize;
// the vmp sections (don't know)
extern volatile const uintptr_t rbxVmp1MiscBase;
extern volatile const size_t rbxVmp1MiscSize;
// the .rdata section without IAT
extern volatile const uintptr_t rbxRdataNoIatBase;
extern volatile const size_t rbxRdataNoIatSize;
}
}