Гайд Обходим любые ClientCheck с сервера и заходим с R1

kin4stat

mq-team · kin4@naebalovo.team
Автор темы
Всефорумный модератор
2,746
4,831
Всем салам, сегодня будем обходить любые клиентчеки со стороны сервера

Алгоритм обхода у нас такой:
Сохранить к себе чистые модули 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
Из-за того что в MinHook нет Call хуков, я буду использовать naked hook. Сам код:
Вариант с 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 // Прыжок дальше
    }
}
А чтобы подменять чтение сампа, будем подменять HMODULE у сампа. Он используется только при краше и чтении памяти, так что можем безболезненно менять его.


А в случае с хуком 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, &GTAmoduleInfo, sizeof(GTAmoduleInfo));
// копируем только то, что может прочитать сервер.
ClearGTAModule = new unsigned char[0x856000 + 256 - 0x400000];
memcpy(ClearGTAModule, reinterpret_cast<void*>(dwGTAModule), 0x856000 + 256 - 0x400000);

На этом наш обход завершен.
Плюсы этого обхода в том, что он скроет любое говно что вы поставите на свою гта: будь то клео, сампфункс, сайлент аим и любые другие читы.
Также если сервер попытается прочитать наш хук, то ничего он не узнает, ведь мы перекидываем его чтение на чистый модуль

Пасиба @HellsCoder за идею обхода, @DarkP1xel за моральную поддержку, а также Lot. за пинки :D
 
Последнее редактирование:

Issaychik

Известный
193
35
Обход аддона шоле получился?)
Вернусь на абсолют что-ль 😄
 

SR_team

like pancake
BH Team
4,809
6,488
В собе вроде есть обход без сохранения чистых gta_sa и samp. И R1 не позволяет чекать память, только модельки
 
  • Нравится
Реакции: loganhackerdff

CleanLegend

Известный
Всефорумный модератор
478
935
В собе вроде есть обход без сохранения чистых gta_sa и samp. И R1 не позволяет чекать память, только модельки
в собе только защита от проверки флагов, а тут именно проверка по адресам памяти gta_sa и samp.dll
r1 не позволяет, но когда подрубаешь фейк версию r2-r4,то придется в таком случае эмулировать проверку памяти, так как сервер думает, что ты зашел r2-r4 и начинает проверять память. если нет ответа - не пускает

обходить любые клиентчеки со стороны сервера
правильно наверно будет только клинтчеки: 0x5 и 0x45
 
  • Нравится
Реакции: kin4stat и loganhackerdff

Gorskin

{FFDEAD}
Проверенный
1,343
1,186
Соберите это в асишник, мне лень с визуалкой возиться
 

kin4stat

mq-team · kin4@naebalovo.team
Автор темы
Всефорумный модератор
2,746
4,831
Соберите это в асишник, мне лень с визуалкой возиться
 
  • Нравится
Реакции: bet.io

Tester1337

Новичок
11
4
Тут точно должно быть EDX ?
C++:
fpRPC(dis, EDX, uniqueID, parameters, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
 

uvie

Известный
269
54
can you somehow bypass the R5 version using R1? If the server user is version 5 and they always check when you log in, are you using version R5?