Files
2025-09-18 17:55:52 -04:00

318 lines
10 KiB
C++

#include <fstream>
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <stdio.h>
#include <string.h>
int readFile(const char* fileName, std::vector<char>& buffer)
{
std::ifstream file(fileName, std::ifstream::binary);
if (file)
{
/*
* Get the size of the file
*/
file.seekg(0,std::ios::end);
std::streampos length = file.tellg();
file.seekg(0,std::ios::beg);
/*
* Use a vector as the buffer.
* It is exception safe and will be tidied up correctly.
* This constructor creates a buffer of the correct length.
*
* Then read the whole file into the buffer.
*/
buffer.resize(length);
file.read(&buffer[0],length);
}
else
{
return -1;
}
return buffer.size();
}
int writeFile(const char* fileName, const std::vector<char>& buffer)
{
std::ofstream file(fileName, std::ifstream::binary);
if (file)
{
file.write(&buffer[0],buffer.size());
}
else
{
return -1;
}
return buffer.size();
}
std::string getFileName(const std::string& sysPath, const std::string& fileName)
{
return sysPath + '\\' + fileName;
}
std::string setCommand(const std::string& sysPath, const std::string& sysExe, const std::string& fileName)
{
return getFileName(sysPath, sysExe) + " -w 195936478 --globalBasicSettingsPath " + fileName;
}
size_t findAndSetOffset(const std::vector<char>& exeBuffer, size_t idx, const char* search, size_t& location)
{
if (strcmp(&exeBuffer[idx], search) == 0)
{
if (location != 0)
{
return -1;
}
location = idx;
}
return 0;
}
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Patcher <Dir of exe> <Name of exe>\n";
return 1;
}
std::vector<char> cmpBufferOne;
std::vector<char> cmpBufferTwo;
std::vector<char> cmpBufferGold;
std::vector<char> exeBuffer;
// Step 1: Run the program with the secret options.
// "195936478" is the magic number = 0x0BADC0DE
std::string sysPath(argv[1]);
std::string sysExe(argv[2]);
std::string sysCommand;
std::string relFile; // filename with path
std::string baseFile; // filename without path
std::string exeFile = getFileName(sysPath, sysExe);
// Run player with special options to dump the .text section to "buffer1.bin"
// The dump will write:
// .text
// 32b int = Golden Hash, calculated
// 32b int = Golden Hash to compare.
baseFile = "buffer1.bin";
relFile = getFileName(sysPath, baseFile);
sysCommand = setCommand(sysPath, sysExe, baseFile);
std::cout << sysCommand << std::endl;
system(sysCommand.c_str());
int fileErrorCode = 0;
fileErrorCode = readFile(relFile.c_str(), cmpBufferOne);
if (fileErrorCode < 0)
{
std::cerr << " couldn't read buffer1.bin.\n";
return 1;
}
const int kPadLen = sizeof(int) + sizeof(int);
int codeLen = cmpBufferOne.size() - kPadLen;
int bufferGhOffset = codeLen; // calculated goldHash offset in buffer (what app calculates)
int bufferRhOffset = codeLen + sizeof(int); // referenced goldHash offset in buffer (what app checks against)
std::cout << "codeLen = " << codeLen << std::endl;
// Step 2: find the location that is different.
// VM protect has a pointer that is different in .text. This will find the
// location. Only run 3 times to find it.
int maxIters = 3;
int diffLocation = -1;
baseFile = "buffer2.bin";
relFile = getFileName(sysPath, baseFile);
sysCommand = setCommand(sysPath, sysExe, baseFile);
while (maxIters)
{
system(sysCommand.c_str());
fileErrorCode = readFile(relFile.c_str(), cmpBufferTwo);
if (fileErrorCode < 0)
{
std::cerr << "couldn't read buffer2.bin.\n";
return 1;
}
// make sure only one place in the file is different.
// if multiple are different, make sure ASLR is disabled
// (note that .rdata also has vtbls from some dll's...)
for (int i = 0; i < codeLen; ++i)
{
if(cmpBufferOne[i] != cmpBufferTwo[i])
{
if ((diffLocation >= 0) && ((i-diffLocation) > 4))
{
std::cerr << "Multiple places have differences.\n";
return -1;
}
else
{
diffLocation = i & 0xFFFFFFFC; // ignore 2 lsb
}
}
}
if (diffLocation >= 0)
{
break;
}
else
{
--maxIters;
}
}
if (maxIters == 0)
{
std::cerr << "Could not find differences between runs.\n";
return -1;
}
// Step 3: Find the locations to modify in the exe
fileErrorCode = readFile(exeFile.c_str(), exeBuffer);
if (fileErrorCode < 0)
{
std::cerr << "couldn't read executable.\n";
return 1;
}
int exeLen = exeBuffer.size();
// There is a common, unique prefix. Check to ensure there is only
// one for maskAddr and one for goldHash
size_t maskAddrOffset = 0;
size_t goldHashOffset = 0;
const char* magicMaskAddrCstr = "1(m$n9.[?y`z+f : a&Rn5<d*nGD9.@93Dr7&";
const char* magicGoldHashCstr = "1N9S6,*%D-&m8sH%/~m _qvZ=&be*db elI^s";
// Added the base/size to the patcher.
const size_t imageBase = 0x00401000;
const char magicImageBaseCstr[24] = {
0x26, 0x53, 0x25, 0x58, 0x64, 0x40, 0x6E, 0x42, 0x34, 0x58, 0x30, 0x20,
0x63, 0x18, 0x0A, 0x34, 0x75, 0x44, 0x26, 0x46, 0x35, 0x29, 0x68, 0x00};
const char magicImageSizeCstr[24] = {
0x35, 0x10, 0x0F, 0x72, 0x38, 0x52, 0x57, 0x43, 0x47, 0x20, 0x39, 0x33,
0x23, 0x42, 0x66, 0x2A, 0x2E, 0x3C, 0x3B, 0x22, 0x5C, 0x73, 0x45, 0x00};
size_t imageBaseOffset = 0;
size_t imageSizeOffset = 0;
bool useImageInfo = true;
// I realize there are some better string matching algorithms out there.
// the exe isn't that large.
for (int i = 0; i < exeLen; ++i)
{
if (findAndSetOffset(exeBuffer, i, magicMaskAddrCstr, maskAddrOffset))
{
std::cerr << "maskAddrOffset is not unique!\n";
return 1;
}
if (findAndSetOffset(exeBuffer, i, magicGoldHashCstr, goldHashOffset))
{
std::cerr << "goldHashOffset is not unique!\n";
return 1;
}
if (findAndSetOffset(exeBuffer, i, magicImageBaseCstr, imageBaseOffset))
{
std::cerr << "imageBaseOffset is not unique!\n";
useImageInfo = false;
}
if (findAndSetOffset(exeBuffer, i, magicImageSizeCstr, imageSizeOffset))
{
std::cerr << "imageSizeOffset is not unique!\n";
useImageInfo = false;
}
}
if (maskAddrOffset == 0)
{
std::cerr << "Unable to find maskAddr.\n";
return 1;
}
if (goldHashOffset == 0)
{
std::cerr << "Unable to find goldHash.\n";
return 1;
}
if (imageBaseOffset == 0)
{
std::cerr << "Unable to find imageBase.\n";
useImageInfo = false;
}
if (imageSizeOffset == 0)
{
std::cerr << "Unable to find imageSize.\n";
useImageInfo = false;
}
size_t alignedMaskAddrOffset = (maskAddrOffset | 7) + 1;
size_t alignedGoldHashOffset = (goldHashOffset | 7) + 1;
imageBaseOffset -= sizeof(size_t); // found byte 4 (on 32b)
imageSizeOffset -= sizeof(size_t);
std::cout << "diffLocation = " << std::hex << diffLocation << std::endl;
std::cout << "maskAddr = " << std::hex << maskAddrOffset << std::endl;
std::cout << "goldHash = " << std::hex << goldHashOffset << std::endl;
std::cout << "imageBase = " << std::hex << imageBaseOffset << std::endl;
std::cout << "imageSize = " << std::hex << imageSizeOffset << std::endl;
// Step 4: Modify the maskAddr
// The aligned versions are used in the application and place the values
// on 8B boundaries. I've noticed that it VC++ doesn't align strings in
// all cases.
//
// Also update the imageBase and imageSize here.
int* maskAddrPtr = (int*) &exeBuffer[alignedMaskAddrOffset];
*maskAddrPtr = diffLocation;
if (useImageInfo)
{
*((size_t*) &exeBuffer[imageBaseOffset]) = imageBase;
*((size_t*) &exeBuffer[imageSizeOffset]) = codeLen;
}
fileErrorCode = writeFile(exeFile.c_str(), exeBuffer);
if (fileErrorCode < 0)
{
std::cerr << "Failed to write first.\n";
return -1;
}
// Step 5: Run the program to get the golden hash
baseFile = "buffer2.bin";
relFile = getFileName(sysPath, baseFile);
sysCommand = setCommand(sysPath, sysExe, baseFile);
system(sysCommand.c_str());
fileErrorCode = readFile(relFile.c_str(), cmpBufferTwo);
if (fileErrorCode < 0)
{
std::cerr << "couldn't read buffer2.bin.\n";
return 1;
}
int goldHashValue = *((int*) (&cmpBufferTwo[bufferGhOffset]));
// Step 6: Modify the goldHash and confirm the hash doesn't change
int* goldHashPtr = (int*) &exeBuffer[alignedGoldHashOffset];
*goldHashPtr = goldHashValue;
fileErrorCode = writeFile(exeFile.c_str(), exeBuffer);
if (fileErrorCode < 0)
{
std::cerr << "Failed to write first.\n";
return -1;
}
baseFile = "buffer3.bin";
relFile = getFileName(sysPath, baseFile);
sysCommand = setCommand(sysPath, sysExe, baseFile);
system(sysCommand.c_str());
fileErrorCode = readFile(relFile.c_str(), cmpBufferGold);
if (fileErrorCode < 0)
{
std::cerr << "couldn't read buffer3.bin.\n";
return 1;
}
int goldHashCheck = *((int*) (&cmpBufferGold[bufferGhOffset])); // the value reported by the 2nd run of the hash check.
int goldHashRCheck = *((int*) (&cmpBufferGold[bufferRhOffset])); // the value reported as the golden hash in the code.
std::cout << std::hex << goldHashCheck << " == " << std::hex << goldHashValue << " : " << std::hex << goldHashRCheck << std::endl;
// This is the check to see if the previously recorded goldHash matches
// both the calculated hash from the new run AND the goldHash that our app will check against.
if ((goldHashValue != goldHashCheck) || (goldHashValue != goldHashRCheck))
{
std::cerr << "Error, golden hash didn't work!\n";
return 1;
}
return 0;
}