Всем салам, сегодня будем обходить любые клиентчеки со стороны сервера
Алгоритм обхода у нас такой:
Сохранить к себе чистые модули gta_sa.exe и samp, и при попытке чтения таковых, подменять их.
Все хуки буду ставить через MinHook.
Оффсет обработчика RPC ClientCheck на R3 - 11710; R4 - 0x11A40
В начало кода добавляем это:
Создадим enum для версий сампа:
Объявим переменные констант(как бы это глупо не звучало :D)
Где нибудь ниже объявим все необходимые переменные:
Объявляем прототипы и трамплины для хуков:
Начнем с обхода захода с R2/R3/R4.
(Перейти к SAMP R1 можете тыкнув сюда)
Возможные варианты подмены чтения gta_sa.exe - хук на месте вызова, либо хук ReadMemory и проверка адреса возврата.
Тут я покажу оба.
В случае с хуком вызова будем хукать это место:
Из-за того что в MinHook нет Call хуков, я буду использовать naked hook. Сам код:
А чтобы подменять чтение сампа, будем подменять HMODULE у сампа. Он используется только при краше и чтении памяти, так что можем безболезненно менять его.
А в случае с хуком ReadMemory это будет выглядеть так:
Перейдем к обходу для R1. Переход к точке входа
Здесь у нас принцип такой: подменять версию сампа при отправке RPC и на клиентчеки отсылать наш ответ.
Сначала добавьте samp.dll от любой версии которая вам нравится в ресурсы с типом DLL
также не забудьте заменить FAKEVER на версию, dll от которой вы берете
Объявим функцию хука отправки RPC для подмены версии сампа
Объявим функцию получения чексуммы с куска памяти(Скопировал псевдокод с ida pro):
Функция хука:
Ну и перейдем к точке входа:
Инициализируем MinHook и получаем все нужные адреса:
Определяем версию сампа:
Ну и конце копируем модуль гташки:
На этом наш обход завершен.
Плюсы этого обхода в том, что он скроет любое говно что вы поставите на свою гта: будь то клео, сампфункс, сайлент аим и любые другие читы.
Также если сервер попытается прочитать наш хук, то ничего он не узнает, ведь мы перекидываем его чтение на чистый модуль
Пасиба @HellsCoder за идею обхода, @DarkP1xel за моральную поддержку, а также Lot. за пинки :D
Алгоритм обхода у нас такой:
Сохранить к себе чистые модули gta_sa.exe и samp, и при попытке чтения таковых, подменять их.
Все хуки буду ставить через MinHook.
Оффсет обработчика RPC ClientCheck на R3 - 11710; R4 - 0x11A40
В начало кода добавляем это:
C++:
#include <Windows.h>
#include <psapi.h>
#include <intrin.h>
#include "MinHook.h"
#pragma comment(lib, "libMinHook-x86-v141-mt.lib")
#pragma intrinsic(_ReturnAddress)
#define FAKEVER "0.3.7-R4"
Создадим enum для версий сампа:
C++:
enum SampVersion {
SAMP_UNKNOWN = -1,
SAMP_0_3_7_R1 = 0,
SAMP_0_3_7_R3_1,
SAMP_0_3_7_R4,
};
Объявим переменные констант(как бы это глупо не звучало :D)
C++:
DWORD READMEMFUNC;
DWORD SAMPHMODULE;
DWORD HOOKREADMEM;
DWORD HOOKEXITREADMEM;
Где нибудь ниже объявим все необходимые переменные:
C++:
HMODULE hSAMPModule;
HMODULE hGTAModule;
DWORD dwSampModule;
DWORD dwGTAModule;
unsigned char* ClearSAMPModule = nullptr; // байты чистого сампа
unsigned char* ClearGTAModule = nullptr; // байты чистой гташки
Объявляем прототипы и трамплины для хуков:
C++:
typedef bool(__fastcall* RakPeer_RPC)(void*, void*, int*, BitStream*, int, int, int, int, __int16, int, int, int, int, int);
typedef unsigned char(__cdecl* ReadMemory)(int, unsigned __int16);
typedef void(__cdecl* ClientCheck)(RPCParameters*);
RakPeer_RPC fpRPC = NULL;
ReadMemory fpHkRead = NULL;
ClientCheck fpHkClientCheck = NULL;
Начнем с обхода захода с R2/R3/R4.
(Перейти к SAMP R1 можете тыкнув сюда)
Возможные варианты подмены чтения gta_sa.exe - хук на месте вызова, либо хук ReadMemory и проверка адреса возврата.
Тут я покажу оба.
В случае с хуком вызова будем хукать это место:
Код:
.text:10011A3E 140 50 push eax
.text:10011A3F 144 E8 FC CC FF FF call readMemory
Вариант с naked хуком::
// Объявляем функцию с параметром naked и укажем что она ничего не принимает и не возращает
__declspec(naked) void HK_ReadMemory(void) {
static unsigned int address = 0; // Адрес откуда собирается читать самп
__asm {
pushad // Кидаем все регистры на стек, чтобы не затереть случайно лишнего
mov address, eax // Вытаскиваем адрес с eax регистра
}
address += reinterpret_cast<DWORD>(ClearGTAModule) - dwGTAModule; // Меняем адрес на наш.
static DWORD dwTmp = dwSampModule + READMEMFUNC;
static DWORD retjmp = dwSampModule + HOOKEXITREADMEM;
__asm {
popad // тащим все регистры со стека обратно
mov eax, address // перезаписываем регистр eax
push eax // пушим его перед вызовом ReadMemory
call dwTmp // Вызываем функцию ReadMemory
jmp retjmp // Прыжок дальше
}
}
А в случае с хуком ReadMemory это будет выглядеть так:
C++:
unsigned char __cdecl HOOK_ReadMemory(unsigned int address, unsigned short readSize) {
auto ret = reinterpret_cast<DWORD>(_ReturnAddress()); // Получаем адрес куда вернется функция после своего возврата
if (ret >= 0x400000 && ret <= 0x856000 + 256) { // Если чтение будет производиться в модуле gta_sa.exe
address += reinterpret_cast<DWORD>(ClearGTAModule) - dwGTAModule;
}
else { // Чтение идет в модуле samp.dll
address += reinterpret_cast<DWORD>(ClearSAMPModule) - dwSAMPModule;
}
return fpHkReadMem(address, readSize);
}
Перейдем к обходу для R1. Переход к точке входа
Здесь у нас принцип такой: подменять версию сампа при отправке RPC и на клиентчеки отсылать наш ответ.
Сначала добавьте samp.dll от любой версии которая вам нравится в ресурсы с типом DLL
также не забудьте заменить FAKEVER на версию, dll от которой вы берете
Объявим функцию хука отправки RPC для подмены версии сампа
C++:
bool __fastcall HOOK_RakPeer_RPC(void* dis, void* EDX, int* uniqueID, BitStream* parameters, int a4, int a5, int a6, int a7, __int16 a8, int a9, int a10, int a11, int a12, int a13) {
if (*uniqueID == 25) {
INT32 iVersion; UINT8 byteMod; UINT8 byteNicknameLen;
UINT32 uiClientChallengeResponse;
UINT8 byteAuthKeyLen;
parameters->Read(iVersion);
parameters->Read(byteMod);
parameters->Read(byteNicknameLen);
char* nickname = new char[byteNicknameLen + 1];
nickname[byteNicknameLen] = 0;
parameters->Read(nickname, byteNicknameLen);
parameters->Read(uiClientChallengeResponse);
parameters->Read(byteAuthKeyLen);
char* authKey = new char[byteAuthKeyLen + 1];
authKey[byteAuthKeyLen] = 0;
parameters->Read(authKey, byteAuthKeyLen);
parameters->SetWriteOffset(parameters->GetReadOffset());
parameters->Write(static_cast<UINT8>(strlen(FAKEVER)));
parameters->Write(FAKEVER, strlen(FAKEVER));
delete[] authKey, nickname;
}
return fpRPC(dis, EDX, uniqueID, parameters, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
}
Объявим функцию получения чексуммы с куска памяти(Скопировал псевдокод с ida pro):
C++:
unsigned char readMemory(int address, unsigned __int16 readSize)
{
unsigned char result = 0;
int i = 0;
if (readSize)
{
do
result ^= *(BYTE*)(i++ + address) & 0xCC;
while (i != readSize);
}
return result;
}
Функция хука:
C++:
void HandleRPCPacketFunc(RPCParameters* rpcParams) {
BitStream bs(rpcParams->input, rpcParams->numberOfBitsOfData / 8, false);
#pragma pack(push, 1)
struct CCheck { // Для удобной работы с входящими параметрами
unsigned __int8 requestType;
unsigned __int32 arg;
unsigned __int16 offset, readSize;
};
#pragma pack(pop)
CCheck* data = reinterpret_cast<CCheck*>(rpcParams->input); // Определяем структуру
// Мы отправляем ответ только на те типы, которые R1 не обрабатывает. Остальное отдаем ему.
if (data->requestType != 0x45 && data->requestType != 5) fpHkClientCheck(rpcParams);
if (data->readSize > 256u || data->readSize < 2u || data->offset > 256u) return; // Делаем те же проверки что и самп
unsigned __int8 result = 0; // что будем отправлять серверу
switch (data->requestType) {
case 0x5:
if (data->arg >= 0x400000 && data->arg <= 0x856E00) { // Делаем проверку на рамки как самп.
result = readMemory(data->arg + data->offset - dwGTAModule + reinterpret_cast<DWORD>(ClearGTAModule), data->readSize);
}
break;
case 0x45:
{
if (data->arg <= 0xC3500) { // Проверяем то что может читать.
result = readMemory(data->arg + data->offset + reinterpret_cast<DWORD>(ClearSAMPModule), data->readSize);
}
}
break;
}
// Создадим битстрим для отправки
BitStream sendBS;
// Записываем нужные данные.
sendBS.Write(data->requestType);
sendBS.Write(data->arg);
sendBS.Write(result);
int sendID = RPC_ClientCheck;
#pragma pack(push, 1)
struct CNetGameR1 { // Опять же мне так удобнее.
char junk[0x3C9];
RakClientInterface* m_pRakClient;
};
#pragma pack(pop)
RakClientInterface* pRak = (*reinterpret_cast<CNetGameR1**>(dwSampModule + 0x21A0F8))->m_pRakClient;
// Отправляем результат чтения.
pRak->RPC(&sendID, &sendBS, PacketPriority::HIGH_PRIORITY, PacketReliability::RELIABLE_ORDERED, 0u, false);
return;
}
Ну и перейдем к точке входа:
Инициализируем MinHook и получаем все нужные адреса:
C++:
DWORD oldProt;
MH_Initialize();
MODULEINFO SAMPmoduleInfo;
hSAMPModule = GetModuleHandle(L"samp.dll");
hGTAModule = GetModuleHandle(L"gta_sa.exe");
dwSampModule = reinterpret_cast<DWORD>(hSAMPModule);
dwGTAModule = reinterpret_cast<DWORD>(hGTAModule);
if (hSAMPModule == NULL || hGTAModule == NULL) return FALSE; // Вдруг самп не загружен
Определяем версию сампа:
C++:
GetModuleInformation(GetCurrentProcess(), hSAMPModule, &SAMPmoduleInfo, sizeof(SAMPmoduleInfo)); // Получаем инфу о модуле
switch (reinterpret_cast<DWORD>(SAMPmoduleInfo.EntryPoint) - dwSampModule) {
case 0x31DF13: sampVer = SampVersion::SAMP_0_3_7_R1; break;
case 0xCC4D0: sampVer = SampVersion::SAMP_0_3_7_R3_1; break;
case 0xCBCB0: sampVer = SampVersion::SAMP_0_3_7_R4; break;
default: return FALSE; // Если версия неизвестная.
}
C++:
if (sampVer == SampVersion::SAMP_0_3_7_R1) {
ClearSAMPModule = reinterpret_cast<unsigned char*>(LockResource(LoadResource(hModule, FindResourceW(hModule, MAKEINTRESOURCEW(IDR_DLL_FILE1), L"DLL_FILE")))); // Получаем DLL сампа из ресурсов
MH_CreateAndEnableHook(dwSampModule + 0xEAF0, &HandleRPCPacketFunc, reinterpret_cast<LPVOID*>(&fpHkClientCheck)); // Ставим хук на обработчик RPC ClientCheck
MH_CreateAndEnableHook(dwSampModule + 0x36C30, &HOOK_RakPeer_RPC, reinterpret_cast<LPVOID*>(&fpRPC)); // Ставим хук на отправку рпц
}
else {
switch (sampVer) {
case SampVersion::SAMP_0_3_7_R3_1:
{
HOOKREADMEM = 0x11A3F;
HOOKEXITREADMEM = 0x11A44;
READMEMFUNC = 0xE740;
SAMPHMODULE = 0x26E880;
}
break;
case SampVersion::SAMP_0_3_7_R4:
{
HOOKREADMEM = 0x11D6F;
HOOKEXITREADMEM = 0x11D74;
READMEMFUNC = 0xEA50;
SAMPHMODULE = 0x26E9B0;
}
break;
}
ClearSAMPModule = new unsigned char[SAMPmoduleInfo.SizeOfImage]; // Выделяем массив
// Копируем модуль samp.dll
memcpy(ClearSAMPModule, reinterpret_cast<void*>(dwSampModule), 0xC3500 + 256); // пределы чеков сампа - C3500
VirtualProtect(reinterpret_cast<void*>(dwSampModule + HOOKREADMEM - 1), 6, PAGE_EXECUTE_READWRITE, &oldProt);
*reinterpret_cast<unsigned char*>(dwSampModule + HOOKREADMEM - 1) = 0x90; // Нопим байт чтобы листинг не сбивался
//MH_CreateAndEnableHook(dwSampModule + HOOKREADMEM, &HK_ReadMemory, NULL); // Если у вас naked хук - расскоммментировать!
// Строку ниже закомментировать, если у вас naked хук
MH_CreateAndEnableHook(dwSampModule + 0xEA50, &sub_1000EA50, reinterpret_cast<LPVOID*>(&fpHkRead));
VirtualProtect(reinterpret_cast<void*>(dwSampModule + HOOKREADMEM - 1), 6, oldProt, &oldProt);
// Раскомментировать если у вас naked хук
/*
VirtualProtect(reinterpret_cast<void*>(dwSampModule + SAMPHMODULE), 4, PAGE_EXECUTE_READWRITE, &oldProt);
*reinterpret_cast<HMODULE*>(dwSampModule + SAMPHMODULE) = reinterpret_cast<HMODULE>(ClearSAMPModule);
VirtualProtect(reinterpret_cast<void*>(dwSampModule + SAMPHMODULE), 4, oldProt, &oldProt);
*/
}
Ну и конце копируем модуль гташки:
C++:
MODULEINFO GTAmoduleInfo;
GetModuleInformation(GetCurrentProcess(), hGTAModule, >AmoduleInfo, sizeof(GTAmoduleInfo));
// копируем только то, что может прочитать сервер.
ClearGTAModule = new unsigned char[0x856000 + 256 - 0x400000];
memcpy(ClearGTAModule, reinterpret_cast<void*>(dwGTAModule), 0x856000 + 256 - 0x400000);
На этом наш обход завершен.
Плюсы этого обхода в том, что он скроет любое говно что вы поставите на свою гта: будь то клео, сампфункс, сайлент аим и любые другие читы.
Также если сервер попытается прочитать наш хук, то ничего он не узнает, ведь мы перекидываем его чтение на чистый модуль
Пасиба @HellsCoder за идею обхода, @DarkP1xel за моральную поддержку, а также Lot. за пинки :D
Последнее редактирование: