mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
929 lines
28 KiB
C++
929 lines
28 KiB
C++
/*
|
|
This is John's counter-insurgency code, upgraded by Will.
|
|
|
|
When modifying this file, please be mindful of what suspicious
|
|
string literals and API calls will look like in a disassembler.
|
|
*/
|
|
#include "stdafx.h"
|
|
|
|
#include "boost/filesystem/operations.hpp"
|
|
|
|
#include <vector>
|
|
#include <list>
|
|
|
|
#include "util/CheatEngine.h"
|
|
#include "V8DataModel/HackDefines.h"
|
|
#include "util/RobloxGoogleAnalytics.h"
|
|
#include <psapi.h>
|
|
#include <TlHelp32.h>
|
|
|
|
#include "VMProtectSDK.h"
|
|
#include "FastLog.h"
|
|
|
|
#include <ctime>
|
|
|
|
|
|
FASTFLAGVARIABLE(US21969, false);
|
|
FASTINTVARIABLE(NumSmoothingPasses, 0)
|
|
FASTINTVARIABLE(RegLambda, -1000000000)
|
|
|
|
namespace RBX
|
|
{
|
|
bool ceDetected = false;
|
|
bool ceHwndChecks = false;
|
|
HeapValue<uintptr_t> vehHookLocationHv(0);
|
|
HeapValue<uintptr_t> vehStubLocationHv(0);
|
|
void* vehHookContinue;
|
|
__declspec(align(4096)) int writecopyTrap[4096];
|
|
}
|
|
|
|
namespace
|
|
{
|
|
using boost::shared_ptr;
|
|
|
|
typedef std::list<RGBQUAD> RGBImage;
|
|
typedef std::vector<shared_ptr<RGBImage> > RGBImages;
|
|
|
|
static void addImageFromBytes(RGBImages &images, const BYTE kRGBs[], const size_t kRGBLen)
|
|
{
|
|
RGBImages::value_type image(new RGBImage);
|
|
images.push_back(image);
|
|
|
|
RGBQUAD p;
|
|
p.rgbReserved = 0;
|
|
|
|
for (size_t i = 0; i <= kRGBLen - 3; i += 3)
|
|
{
|
|
p.rgbRed = kRGBs[i];
|
|
p.rgbGreen = kRGBs[i+1];
|
|
p.rgbBlue = kRGBs[i+2];
|
|
image->push_back(p);
|
|
}
|
|
}
|
|
|
|
// Add the large icon in the CE app itself.
|
|
static void addImage1(RGBImages &images)
|
|
{
|
|
const BYTE kRGBs[] = {
|
|
0,29,38,
|
|
0,30,39,
|
|
48,130,141,
|
|
0,56,67,
|
|
41,135,147,
|
|
0,78,92,
|
|
33,133,148,
|
|
0,92,106
|
|
};
|
|
|
|
addImageFromBytes(images, kRGBs, sizeof(kRGBs) / sizeof(BYTE));
|
|
}
|
|
|
|
// Add the small icon in the upper-left of the CE window.
|
|
static void addImage2(RGBImages &images)
|
|
{
|
|
const BYTE kRGBs[] = {
|
|
1,86,130,
|
|
1,56,86,
|
|
3,29,43,
|
|
3,19,28,
|
|
0,13,21,
|
|
0,23,36,
|
|
0,43,67,
|
|
1,76,115
|
|
};
|
|
|
|
addImageFromBytes(images, kRGBs, sizeof(kRGBs) / sizeof(BYTE));
|
|
}
|
|
|
|
// Add the icon that will appear in the task bar.
|
|
static void addImage3(RGBImages &images)
|
|
{
|
|
const BYTE kRGBs[] = {
|
|
1,40,61,
|
|
1,45,69,
|
|
1,66,101,
|
|
1,91,137,
|
|
1,100,149,
|
|
1,89,134,
|
|
1,63,96,
|
|
1,44,68
|
|
};
|
|
|
|
addImageFromBytes(images, kRGBs, sizeof(kRGBs) / sizeof(BYTE));
|
|
}
|
|
|
|
// Add the small icon that will appear in the task bar (using small icons)
|
|
static void addImage4(RGBImages &images)
|
|
{
|
|
const BYTE kRGBs[] = {
|
|
3,30,46,
|
|
6,33,49,
|
|
1,72,108,
|
|
1,96,144,
|
|
2,49,75,
|
|
8,35,51,
|
|
8,35,51,
|
|
7,40,58
|
|
};
|
|
|
|
addImageFromBytes(images, kRGBs, sizeof(kRGBs) / sizeof(BYTE));
|
|
}
|
|
|
|
static void setupImageArray(RGBImages &images)
|
|
{
|
|
addImage1(images);
|
|
addImage2(images);
|
|
|
|
if (FFlag::US21969)
|
|
{
|
|
addImage3(images);
|
|
addImage4(images);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
// ScanForCheatEngine. Returns true if we find it.
|
|
bool RBX::vmProtectedDetectCheatEngineIcon()
|
|
{
|
|
VMProtectBeginMutation("6");
|
|
|
|
RGBImages cheatImageTemplates;
|
|
setupImageArray(cheatImageTemplates);
|
|
|
|
HWND hDesktopWnd=GetDesktopWindow();
|
|
HDC hDesktopDC=GetDC(hDesktopWnd);
|
|
int nWidth=GetSystemMetrics(SM_CXSCREEN);
|
|
int nHeight=GetSystemMetrics(SM_CYSCREEN);
|
|
HDC hBmpFileDC=CreateCompatibleDC(hDesktopDC);
|
|
HBITMAP hBmpFileBitmap=CreateCompatibleBitmap(hDesktopDC,nWidth,nHeight);
|
|
HBITMAP hOldBitmap = (HBITMAP) SelectObject(hBmpFileDC,hBmpFileBitmap);
|
|
BitBlt(hBmpFileDC,0,0,nWidth,nHeight,hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
|
|
SelectObject(hBmpFileDC,hOldBitmap);
|
|
|
|
bool result = false;
|
|
LPVOID pBuf=NULL;
|
|
BITMAPINFO bmpInfo;
|
|
//BITMAPFILEHEADER bmpFileHeader;
|
|
HDC hdc=GetDC(NULL);
|
|
|
|
do
|
|
{
|
|
ZeroMemory(&bmpInfo,sizeof(BITMAPINFO));
|
|
bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
|
|
GetDIBits(hdc,hBmpFileBitmap,0,0,NULL,&bmpInfo,DIB_RGB_COLORS);
|
|
|
|
if(bmpInfo.bmiHeader.biSizeImage<=0)
|
|
bmpInfo.bmiHeader.biSizeImage=bmpInfo.bmiHeader.biWidth*abs(bmpInfo.bmiHeader.biHeight)*(bmpInfo.bmiHeader.biBitCount+7)/8;
|
|
|
|
if((pBuf=malloc(bmpInfo.bmiHeader.biSizeImage))==NULL)
|
|
{
|
|
//MessageBox(NULL,_T("Unable to Allocate Bitmap Memory"),_T("Error"),MB_OK|MB_ICONERROR);
|
|
break;
|
|
}
|
|
|
|
bmpInfo.bmiHeader.biCompression=BI_RGB;
|
|
int scanlines = GetDIBits(hdc,hBmpFileBitmap,0,bmpInfo.bmiHeader.biHeight,pBuf,&bmpInfo,DIB_RGB_COLORS);
|
|
|
|
if (bmpInfo.bmiHeader.biBitCount != 32) break;
|
|
|
|
// assume 32 bit color depth
|
|
const int *p = (const int *)pBuf;
|
|
for (RGBImages::const_iterator imagesIt = cheatImageTemplates.begin(); cheatImageTemplates.end() != imagesIt; ++imagesIt)
|
|
{
|
|
if (result)
|
|
{
|
|
break;
|
|
}
|
|
|
|
shared_ptr<RGBImage> image = *imagesIt;
|
|
for (unsigned int i = 0 ; i < (bmpInfo.bmiHeader.biSizeImage / 4) - (image->size() * 4); i++) // step - number of pixels (in template) * 4 bytes per pixel
|
|
{
|
|
if (result)
|
|
{
|
|
break;
|
|
}
|
|
|
|
result = true;
|
|
size_t offs = 0;
|
|
for (RGBImage::const_iterator it = image->begin(); image->end() != it; ++it, ++offs)
|
|
{
|
|
const RGBQUAD &quad = *it;
|
|
const int px = *reinterpret_cast<const int *>(&quad);
|
|
if ((p[i + offs] & 0xFFFFFF /*ignore reserved bits, assume little-endian*/) != px)
|
|
{
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (false);
|
|
|
|
if(hdc)
|
|
ReleaseDC(NULL,hdc);
|
|
|
|
if(pBuf)
|
|
free(pBuf);
|
|
|
|
if (hBmpFileDC)
|
|
DeleteDC(hBmpFileDC);
|
|
|
|
if (hBmpFileBitmap)
|
|
DeleteObject(hBmpFileBitmap);
|
|
|
|
VMProtectEnd();
|
|
return result;
|
|
|
|
}
|
|
|
|
namespace RBX {
|
|
|
|
namespace CryptStrings{
|
|
// Code generation. This is equivalent to strCmp(x, "Window") == 0
|
|
bool cmpWindow(const char* inString)
|
|
{
|
|
const unsigned char cmpString[7] = {9, 214, 144, 249, 109, 4, 186};
|
|
if (!inString) return false;
|
|
for (int i = 0; i < 7; ++i)
|
|
{
|
|
if ((unsigned char)((inString[i]+i)*159) != cmpString[i]) return false;
|
|
if (!inString[i]) return i == 6;
|
|
};
|
|
return false;
|
|
}
|
|
|
|
bool startsWith(const char* str, const char* prefix) {
|
|
if (!str || !prefix) return false;
|
|
while (*prefix) {
|
|
if (*str != *prefix) return false;
|
|
++str; ++prefix;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Code generation. This is equivalent to strCmp(x, "ComboLBox") == 0
|
|
bool cmpComboLBox(const char* inString)
|
|
{
|
|
const unsigned char cmpString[10] = {229, 16, 89, 51, 53, 231, 120, 90, 128, 111};
|
|
if (!inString) return false;
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
if ((unsigned char)((inString[i]+i)*183) != cmpString[i]) return false;
|
|
if (!inString[i]) return i == 9;
|
|
};
|
|
return false;
|
|
}
|
|
|
|
// Compares input to "Cheat Engine" but is not null terminated.
|
|
// This has been modified to work with the obfuscated strings.
|
|
bool cmpPrefixCheatEngine(const char* inString)
|
|
{
|
|
const unsigned char cmpString[12] = {71, 245, 155, 148, 24, 1, 175, 17, 3, 10, 24, 176};
|
|
if (!inString) return false;
|
|
for (int i = 0; i < 12; ++i)
|
|
{
|
|
if ((unsigned char)(((inString[i]^kCeCharKey)+i)*173) != cmpString[i]) return false;
|
|
if (!(inString[i]^kCeCharKey)) return false;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
// CE 5.6.1 loads a LOT of windows at startup. The class names are revealing.
|
|
bool cmpTfrmdissectWindow(const char* inString)
|
|
{
|
|
const unsigned char cmpString[18] = {244, 95, 20, 240, 168, 94, 129, 202, 21, 204, 238, 242, 93, 19, 130, 238, 127, 217};
|
|
if (!inString) return false;
|
|
for (int i = 0; i < 18; ++i)
|
|
{
|
|
if ((unsigned char)((inString[i]+i)*73) != cmpString[i]) return false;
|
|
if (!inString[i]) return (i == 17);
|
|
};
|
|
return false;
|
|
}
|
|
|
|
};
|
|
|
|
size_t HwndScanner::fullWindowInfo::compareByPid ( fullWindowInfo lhs, fullWindowInfo rhs)
|
|
{
|
|
return (lhs.pid < rhs.pid);
|
|
}
|
|
|
|
BOOL CALLBACK HwndScanner::makeHwndVector(HWND hwnd, LPARAM lParam)
|
|
{
|
|
VMProtectBeginMutation("7");
|
|
std::vector<fullWindowInfo>* listing = reinterpret_cast<std::vector<fullWindowInfo>* >(lParam);
|
|
|
|
char className[32];
|
|
GetClassName(hwnd,className, sizeof(className));
|
|
|
|
fullWindowInfo thisWindow;
|
|
if ( CryptStrings::cmpWindow(className) || CryptStrings::cmpComboLBox(className))
|
|
{
|
|
// Some people get very reactive when a game scans for "personal data" in other
|
|
// processes. This attempts to limit the search to a handful of apps that use
|
|
// the same names for UI elements.
|
|
char title[32] = "NA";
|
|
bool* returnValue = reinterpret_cast<bool*>(lParam);
|
|
GetWindowText(hwnd,title,sizeof(title));
|
|
thisWindow.title = title;
|
|
|
|
// These will linger in RAM, so they should be obfuscated.
|
|
for (int i = 0; i < sizeof(title); ++i)
|
|
{
|
|
if (!thisWindow.title[i])
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
thisWindow.title[i] ^= kCeCharKey;
|
|
}
|
|
}
|
|
|
|
// Window position and size doesn't seem like personal information.
|
|
// and again, I don't want to make it easy to determine what these are.
|
|
DWORD pid = 0;
|
|
GetWindowThreadProcessId(hwnd, &pid);
|
|
thisWindow.pid = pid;
|
|
|
|
WINDOWINFO windowInfo;
|
|
windowInfo.cbSize = sizeof(windowInfo);
|
|
GetWindowInfo(hwnd, &windowInfo);
|
|
thisWindow.winWidth = kCeStructKey * (windowInfo.rcClient.right - windowInfo.rcClient.left);
|
|
thisWindow.winHeight = kCeStructKey * (windowInfo.rcClient.bottom - windowInfo.rcClient.top);
|
|
thisWindow.active = windowInfo.dwWindowStatus;
|
|
thisWindow.kickEarly = false;
|
|
listing->push_back(thisWindow);
|
|
}
|
|
else if (CryptStrings::cmpTfrmdissectWindow(className))
|
|
{
|
|
thisWindow.active = 0;
|
|
thisWindow.winHeight = 0;
|
|
thisWindow.winWidth = 0;
|
|
thisWindow.title = "";
|
|
thisWindow.pid = 0;
|
|
thisWindow.kickEarly = true;
|
|
listing->push_back(thisWindow);
|
|
}
|
|
|
|
VMProtectEnd();
|
|
return TRUE;
|
|
}
|
|
|
|
HwndScanner::HwndScanner()
|
|
{
|
|
}
|
|
|
|
int HwndScanner::scan()
|
|
{
|
|
hwndScanResults.clear();
|
|
EnumWindows(makeHwndVector, (LPARAM) &hwndScanResults);
|
|
std::sort (hwndScanResults.begin(), hwndScanResults.end(), &fullWindowInfo::compareByPid);
|
|
return hwndScanResults.size();
|
|
}
|
|
|
|
bool HwndScanner::detectTitle() const
|
|
{
|
|
for(std::vector<fullWindowInfo>::const_iterator it = hwndScanResults.begin(); it != hwndScanResults.end(); ++it )
|
|
{
|
|
if (CryptStrings::cmpPrefixCheatEngine(it->title.c_str()) || CryptStrings::startsWith(it->title.c_str(), "Citeful") || CryptStrings::startsWith(it->title.c_str(), "Extreme Injector") || CryptStrings::startsWith(it->title.c_str(), "Process Hacker") || CryptStrings::startsWith(it->title.c_str(), "DLL Injector"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HwndScanner::detectEarlyKick() const
|
|
{
|
|
for(std::vector<fullWindowInfo>::const_iterator it = hwndScanResults.begin(); it != hwndScanResults.end(); ++it )
|
|
{
|
|
if (it->kickEarly)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HwndScanner::detectFakeAttach() const
|
|
{
|
|
if (!hwndScanResults.size())
|
|
{
|
|
return false;
|
|
}
|
|
DWORD currentPid = hwndScanResults[0].pid;
|
|
DWORD hasParams = 0;
|
|
for(std::vector<fullWindowInfo>::const_iterator it = hwndScanResults.begin(); it != hwndScanResults.end(); ++it )
|
|
{
|
|
if (it->pid != currentPid)
|
|
{
|
|
hasParams = 0;
|
|
currentPid = it->pid;
|
|
}
|
|
// Drop down "compare type"
|
|
if ((it->winWidth == kCeStructKey*168) && (it->winHeight == kCeStructKey*143))
|
|
{
|
|
hasParams |= 1;
|
|
}
|
|
// Drop down "data type"
|
|
if ((it->winWidth == kCeStructKey*168) && (it->winHeight == kCeStructKey*65))
|
|
{
|
|
hasParams |= 2;
|
|
}
|
|
// This just exists.
|
|
if ((it->winWidth == 0) && (it->winHeight == 0))
|
|
{
|
|
hasParams |= 4;
|
|
}
|
|
// This is the process list.
|
|
if ((it->winWidth == kCeStructKey*245) && (it->winHeight == kCeStructKey*353) && it->active == 0)
|
|
{
|
|
hasParams |= 8;
|
|
}
|
|
// This is "Modify Register at" (destroyed when closed) (for win32)
|
|
else if ((it->winWidth == kCeStructKey*274) && (it->winHeight == kCeStructKey*265))
|
|
{
|
|
hasParams |= 0x8;
|
|
}
|
|
// This is DBVM's "DBK32 Loaded" flashy graphic. AWESOME!
|
|
else if ((it->winWidth == kCeStructKey*420) && (it->winHeight == kCeStructKey*60))
|
|
{
|
|
hasParams |= 0x8;
|
|
}
|
|
if (hasParams == 0xF)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FileScanner::FileScanner() : baseTime(std::time(NULL))
|
|
{
|
|
char folderName[MAX_PATH+1];
|
|
DWORD errCode = GetTempPath(sizeof(folderName), folderName);
|
|
|
|
// Windows API returns 0 for error
|
|
if (errCode)
|
|
{
|
|
tempFolder = folderName;
|
|
}
|
|
|
|
}
|
|
|
|
bool FileScanner::detectLogUpdate() const
|
|
{
|
|
// This here assumes the user has blindly changed "Cheat Engine" in the string table for CE.
|
|
// It is also assumed the user hasn't changed the defaults. For speed, this will attempt to
|
|
// find {CE}/{GUID}/MEMORY.FIRST
|
|
// This will only get CE users who run scans.
|
|
boost::system::error_code fsErrorCode;
|
|
boost::filesystem::exists(tempFolder, fsErrorCode);
|
|
if (fsErrorCode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
boost::filesystem::directory_iterator ceIter(tempFolder, fsErrorCode);
|
|
boost::filesystem::directory_iterator endIter;
|
|
if (fsErrorCode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// CE modifies the %TMP%/{CE} directory when run, but never after that.
|
|
// A scan will update the mtime of %TMP%/{CE}/{GUID}
|
|
// Thus, scan for a recently modified subdirectory with MEMORY.FIRST, in any directory.
|
|
for (; ceIter != endIter; ++ceIter)
|
|
{
|
|
if (boost::filesystem::is_directory(ceIter->status()))
|
|
{
|
|
boost::filesystem::directory_iterator guidIter(*ceIter, fsErrorCode);
|
|
for (; guidIter != endIter; ++guidIter)
|
|
{
|
|
if((boost::filesystem::is_directory(guidIter->status()))
|
|
&& (boost::filesystem::last_write_time(*guidIter) >= baseTime ))
|
|
{
|
|
boost::filesystem::path finalPath = guidIter->path();
|
|
finalPath /= "MEMORY.FIRST";
|
|
bool doesExist = boost::filesystem::exists(finalPath, fsErrorCode);
|
|
if (doesExist && !fsErrorCode)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static DWORD WINAPI watchCeLogDir(void* ceWatchHandle)
|
|
{
|
|
VMProtectBeginMutation("8");
|
|
DWORD waitStatus = WAIT_ABANDONED;
|
|
waitStatus = WaitForSingleObject(ceWatchHandle, INFINITE);
|
|
|
|
if (waitStatus == WAIT_OBJECT_0)
|
|
{
|
|
ceDetected = true;
|
|
}
|
|
VMProtectEnd();
|
|
return 0;
|
|
}
|
|
|
|
namespace CryptStrings
|
|
{
|
|
|
|
void decodeInto(unsigned char* to, const unsigned char* from, unsigned char key, int copySize)
|
|
{
|
|
for (int i = 0; i < copySize; ++i)
|
|
{
|
|
to[i] = (from[i]*key) - i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns a handle to a thread that waits for CE to change log files.
|
|
// Apparently this won't work because you can't access the registry becuase invalid path...
|
|
// I'm guessing it returns invalid path incorrectly because the path is valid.
|
|
HANDLE setupCeLogWatcher()
|
|
{
|
|
VMProtectBeginMutation("9");
|
|
// Cheat Engine
|
|
static const unsigned char kCeName[13] = {221, 247, 57, 28, 136, 187, 213, 107, 49, 78, 136, 144, 116}; // 95 :
|
|
static const unsigned char kCeNameKey = 159;
|
|
// Software\\Cheat Engine
|
|
static const unsigned char kCeReg[22] = {5, 16, 216, 1, 29, 74, 72, 244, 188, 20, 158, 16, 59, 199, 194, 76, 242, 72, 29, 199, 143, 211}; // 71 :
|
|
static const unsigned char kCeRegKey = 119;
|
|
// Scanfolder
|
|
static const unsigned char kCeScanfolder[11] = {167, 84, 119, 141, 130, 36, 106, 95, 25, 47, 162}; // 221 :
|
|
static const unsigned char kCeScanfolderKey = 117;
|
|
|
|
char folderName[MAX_PATH+2];
|
|
DWORD passCode;
|
|
HANDLE ceLogWatcher = INVALID_HANDLE_VALUE;
|
|
boost::filesystem::path ceLogPath("");
|
|
HKEY hKey;
|
|
unsigned char tmpString[32];
|
|
bool tryDefault = true;
|
|
bool spawnThread = true;
|
|
|
|
// First, try to find the folder based on the CE registry entry
|
|
CryptStrings::decodeInto(tmpString, kCeReg, kCeRegKey, sizeof(kCeReg));
|
|
passCode = RegOpenKeyEx(HKEY_CURRENT_USER,(char*)(tmpString), NULL, KEY_READ, &hKey);
|
|
memset(tmpString,0,sizeof(tmpString));
|
|
if (passCode == ERROR_SUCCESS)
|
|
{
|
|
DWORD cbSize = sizeof(folderName);
|
|
CryptStrings::decodeInto(tmpString, kCeScanfolder, kCeScanfolderKey, sizeof(kCeScanfolder));
|
|
passCode = RegQueryValueEx(hKey, (char*)(tmpString), NULL, NULL, (unsigned char*)(folderName), &cbSize);
|
|
memset(tmpString,0,sizeof(tmpString));
|
|
|
|
if (passCode == ERROR_SUCCESS && cbSize && folderName[0] != '\\')
|
|
{
|
|
ceLogPath = folderName;
|
|
tryDefault = false;
|
|
}
|
|
}
|
|
|
|
// Otherwise, assume it is in the default tmp dir.
|
|
if (tryDefault)
|
|
{
|
|
passCode = GetTempPath(sizeof(folderName), folderName);
|
|
if (passCode != 0)
|
|
{
|
|
ceLogPath = folderName;
|
|
spawnThread = true;
|
|
}
|
|
else
|
|
{
|
|
spawnThread = false;
|
|
}
|
|
}
|
|
|
|
// Windows API returns 0 for error
|
|
if (spawnThread)
|
|
{
|
|
CryptStrings::decodeInto(tmpString, kCeName, kCeNameKey, sizeof(kCeName));
|
|
ceLogPath /= (char*)(tmpString);
|
|
memset(tmpString,0,sizeof(tmpString));
|
|
HANDLE ceLogWatchHandle = FindFirstChangeNotification(ceLogPath.string().c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE);
|
|
if (ceLogWatchHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
ceLogWatcher = CreateThread(NULL, 64*1024, &watchCeLogDir, ceLogWatchHandle, 0, NULL);
|
|
}
|
|
}
|
|
VMProtectEnd();
|
|
return ceLogWatcher;
|
|
}
|
|
|
|
}
|
|
|
|
namespace RBX
|
|
{
|
|
// Because of RTTI, this also needs to be obfuscated. "VerifyConnectionJob" seems safe enough.
|
|
VerifyConnectionJob::VerifyConnectionJob() :
|
|
TaskScheduler::Job("SomeNameHere", shared_ptr<TaskScheduler::Arbiter>())
|
|
{
|
|
}
|
|
|
|
Time::Interval VerifyConnectionJob::sleepTime(const Stats& stats)
|
|
{
|
|
return Time::Interval::zero();
|
|
}
|
|
TaskScheduler::Job::Error VerifyConnectionJob::error(const Stats& stats)
|
|
{
|
|
TaskScheduler::Job::Error result = computeStandardErrorCyclicExecutiveSleeping(stats, 1);
|
|
return result;
|
|
}
|
|
double VerifyConnectionJob::getPriorityFactor()
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
TaskScheduler::StepResult VerifyConnectionJob::step(const Stats& stats)
|
|
{
|
|
// Time the CE detection methods. Optionally enable on high end HW.
|
|
HwndScanner hwndScanner;
|
|
Time ceStartTime = Time::now<Time::Precise>();
|
|
for (int i = 0; i < FInt::NumSmoothingPasses; ++i)
|
|
{
|
|
hwndScanner.scan();
|
|
hwndScanner.detectTitle();
|
|
hwndScanner.detectFakeAttach();
|
|
}
|
|
Time::Interval cePerfTime = Time::now<Time::Precise>() - ceStartTime;
|
|
if (static_cast<int>(cePerfTime.seconds()*1E6) < FInt::RegLambda)
|
|
{
|
|
ceHwndChecks = true;
|
|
}
|
|
|
|
// need another fake string here. this also appears in the client.
|
|
RobloxGoogleAnalytics::trackUserTiming(GA_CATEGORY_GAME, GA_CLIENT_START, cePerfTime.msec(), "Process hotfixes");
|
|
|
|
return RBX::TaskScheduler::Done;
|
|
}
|
|
|
|
SpeedhackDetect::SpeedhackDetect()
|
|
{
|
|
MODULEINFO info;
|
|
HMODULE module = GetModuleHandle("kernel32");
|
|
GetModuleInformation(GetCurrentProcess(), module, &info, sizeof(info));
|
|
k32base = reinterpret_cast<DWORD>(module);
|
|
k32size = info.SizeOfImage;
|
|
}
|
|
|
|
bool SpeedhackDetect::isSpeedhack()
|
|
{
|
|
// Check for long jump _clearly_ out of kernel32.dll
|
|
unsigned char* apiByte = reinterpret_cast<unsigned char*>(&GetTickCount);
|
|
DWORD loc = reinterpret_cast<DWORD>(apiByte) + *reinterpret_cast<DWORD*>(apiByte+1) + 5;
|
|
if ((apiByte[0] == 0xE9) && (loc - k32base > k32size))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Basic Detection for Sandboxie. This one is here because it was used to hide CE.
|
|
bool isSandboxie()
|
|
{
|
|
VMProtectBeginMutation("10");
|
|
unsigned char argString[8];
|
|
const unsigned char encodedString[8] = {97, 17, 233, 248, 152, 203, 198, 221}; // SbieDll
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
argString[i] = ((encodedString[i]*51)-i) % 256;
|
|
};
|
|
HMODULE detected = GetModuleHandle(reinterpret_cast<const char*>(argString));
|
|
memset(argString, 0, sizeof(argString));
|
|
VMProtectEnd();
|
|
return (detected != NULL);
|
|
}
|
|
|
|
namespace CryptStrings
|
|
{
|
|
const unsigned char kCeSpeedhack5Dll[10] = {73, 163, 101, 184, 184, 87, 101, 94, 73, 235}; // "speedhack\0"
|
|
const unsigned char kCeSpeedhack6Dll[15] = {73, 163, 101, 184, 184, 87, 101, 94, 73, 130, 73, 26, 12, 185, 138}; // "speedhack-i386\0"
|
|
const unsigned char kCeD3dhookDll[9] = {108, 220, 18, 177, 73, 156, 163, 69}; // "d3dhook\0"
|
|
|
|
bool decryptCeDllStrings(const unsigned char* inString, char* outString, int maxSize)
|
|
{
|
|
memset(outString, 0, maxSize);
|
|
if (!inString) return false;
|
|
for (int i = 0; i < maxSize; ++i)
|
|
{
|
|
outString[i] = (inString[i]*219-i);
|
|
if (!outString[i])
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool isCeBadDll()
|
|
{
|
|
char modName[16];
|
|
bool badDll = false;
|
|
CryptStrings::decryptCeDllStrings(CryptStrings::kCeSpeedhack5Dll, modName, sizeof(CryptStrings::kCeSpeedhack5Dll));
|
|
badDll = badDll || (0 != GetModuleHandleA(modName));
|
|
CryptStrings::decryptCeDllStrings(CryptStrings::kCeSpeedhack6Dll, modName, sizeof(CryptStrings::kCeSpeedhack6Dll));
|
|
badDll = badDll || (0 != GetModuleHandleA(modName));
|
|
CryptStrings::decryptCeDllStrings(CryptStrings::kCeD3dhookDll, modName, sizeof(CryptStrings::kCeD3dhookDll));
|
|
badDll = badDll || (0 != GetModuleHandleA(modName));
|
|
memset(modName, 0, 16);
|
|
return badDll;
|
|
}
|
|
|
|
void DbvmCanary::canary(HANDLE* mutex)
|
|
{
|
|
WaitForSingleObject(*mutex, INFINITE);
|
|
}
|
|
|
|
DbvmCanary::DbvmCanary()
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
canaryCage = CreateMutex(NULL, TRUE, NULL);
|
|
canaryHandle = CreateThread(NULL,NULL,reinterpret_cast<LPTHREAD_START_ROUTINE>(canary),&canaryCage,CREATE_SUSPENDED,NULL);
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
ctx.Dr7 = 0x11110100;
|
|
ctx.Dr6 = 0xF;
|
|
ctx.Dr0 = 0;
|
|
ctx.Dr1 = 1;
|
|
ctx.Dr2 = 2;
|
|
ctx.Dr3 = 3;
|
|
SetThreadContext(canaryHandle, &ctx);
|
|
hashValue = hashDbgRegs(ctx);
|
|
ResumeThread(canaryHandle);
|
|
VMProtectEnd();
|
|
}
|
|
|
|
void DbvmCanary::checkAndLocalUpdate()
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
bool isBadHash = false;
|
|
CONTEXT nextCtx;
|
|
nextCtx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
GetThreadContext(canaryHandle, &nextCtx);
|
|
size_t dbgHash = hashDbgRegs(nextCtx);
|
|
isBadHash = (hashDbgRegs(nextCtx) != hashValue);
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
ctx.Dr7 = 0x11110100;
|
|
ctx.Dr6 = 0xF;
|
|
ctx.Dr0 = (hashValue+0) % 4096;
|
|
ctx.Dr1 = (hashValue+1) % 4096;
|
|
ctx.Dr2 = (hashValue+2) % 4096;
|
|
ctx.Dr3 = (hashValue+3) % 4096;
|
|
hashValue = hashDbgRegs(ctx);
|
|
VMProtectEnd();
|
|
VMProtectBeginVirtualization(NULL);
|
|
if (isBadHash)
|
|
{
|
|
Tokens::simpleToken |= HATE_CHEATENGINE_OLD;
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag7, HATE_CHEATENGINE_OLD);
|
|
}
|
|
VMProtectEnd();
|
|
}
|
|
|
|
void DbvmCanary::kernelUpdate()
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
SetThreadContext(canaryHandle, &ctx);
|
|
VMProtectEnd();
|
|
}
|
|
|
|
#ifdef RBX_RCC_SECURITY
|
|
static boost::mutex hwbpMutex;
|
|
|
|
static inline bool setHwbpContex(uintptr_t addr, CONTEXT* ctx)
|
|
{
|
|
bool changed = false;
|
|
if (addr == 0)
|
|
{
|
|
return changed;
|
|
}
|
|
if ((ctx->Dr0 == addr) || (ctx->Dr1 == addr) || (ctx->Dr2 == addr) || (ctx->Dr3 == addr))
|
|
{
|
|
return changed;
|
|
}
|
|
if (ctx->Dr0 == 0)
|
|
{
|
|
ctx->Dr0 = addr;
|
|
ctx->Dr7 |= 0x000D0001;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr1 == 0)
|
|
{
|
|
ctx->Dr1 = addr;
|
|
ctx->Dr7 |= 0x00D00004;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr2 == 0)
|
|
{
|
|
ctx->Dr2 = addr;
|
|
ctx->Dr7 |= 0x0D000010;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr3 == 0)
|
|
{
|
|
ctx->Dr3 = addr;
|
|
ctx->Dr7 |= 0xD0000040;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
static inline bool clearHwbpContext(uintptr_t addr, CONTEXT* ctx)
|
|
{
|
|
bool changed = false;
|
|
if (ctx->Dr0 == addr)
|
|
{
|
|
ctx->Dr0 = 0;
|
|
ctx->Dr7 &= ~0x000D0001;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr1 == addr)
|
|
{
|
|
ctx->Dr1 = 0;
|
|
ctx->Dr7 &= ~0x00D00004;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr2 == addr)
|
|
{
|
|
ctx->Dr2 = 0;
|
|
ctx->Dr7 &= ~0x0D000010;
|
|
changed = true;
|
|
}
|
|
else if (ctx->Dr3 == addr)
|
|
{
|
|
ctx->Dr3 = 0;
|
|
ctx->Dr7 &= ~0xD0000040;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
void addWriteBreakpoint(uintptr_t addr)
|
|
{
|
|
boost::mutex::scoped_lock lock(hwbpMutex);
|
|
CONTEXT ctx = {};
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
GetThreadContext(GetCurrentThread(), &ctx);
|
|
if (setHwbpContex(addr, &ctx))
|
|
{
|
|
THREADENTRY32 info;
|
|
info.dwSize = sizeof(info);
|
|
DWORD thisPid = GetCurrentProcessId();
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
|
|
Thread32First(snap, &info);
|
|
do
|
|
{
|
|
if (info.th32OwnerProcessID == thisPid)
|
|
{
|
|
HANDLE thread = OpenThread(THREAD_SET_CONTEXT, false, info.th32ThreadID);
|
|
SetThreadContext(thread, &ctx);
|
|
CloseHandle(thread);
|
|
}
|
|
} while (Thread32Next(snap, &info));
|
|
CloseHandle(snap);
|
|
}
|
|
}
|
|
|
|
void removeWriteBreakpoint(uintptr_t addr)
|
|
{
|
|
boost::mutex::scoped_lock lock(hwbpMutex);
|
|
CONTEXT ctx = {};
|
|
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
GetThreadContext(GetCurrentThread(), &ctx);
|
|
if (clearHwbpContext(addr, &ctx))
|
|
{
|
|
THREADENTRY32 info;
|
|
info.dwSize = sizeof(info);
|
|
DWORD thisPid = GetCurrentProcessId();
|
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
|
|
Thread32First(snap, &info);
|
|
do
|
|
{
|
|
if (info.th32OwnerProcessID == thisPid)
|
|
{
|
|
HANDLE thread = OpenThread(THREAD_SET_CONTEXT, false, info.th32ThreadID);
|
|
SetThreadContext(thread, &ctx);
|
|
CloseHandle(thread);
|
|
}
|
|
} while (Thread32Next(snap, &info));
|
|
CloseHandle(snap);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|