Skip to content

Instantly share code, notes, and snippets.

@GermanAizek
Last active January 28, 2024 01:16
Show Gist options
  • Save GermanAizek/9293329042c9f68e9c66fea73dfae051 to your computer and use it in GitHub Desktop.
Save GermanAizek/9293329042c9f68e9c66fea73dfae051 to your computer and use it in GitHub Desktop.

Реверс-инжиниринг MTA Province: Пишем анти-штрафы за превышение скорости

Требования к читателю:

  • Знание основ С++ (Сюда входит: переменные, арифметические операции, операторы условного ветвления, понимание работы блоков видимости кода, указатели, базовое понимание работы и создания потоков, умение подключить инклуды)
  • Базовые навыки по работе с Visual Studio 2017-2022 версии (Создание проекта, его стандартная настройка вроде выбора конфигурации, отключения предкомпилируемых заголовком, сборка проекта)
  • Понимание английского хотя бы на базовом уровне
  • Прочитаны все наши предыдущие туториалы по разработке (Важно)
  • Загуглить и прочитать туториалы по основам ассемблера, в идеале MASM (Microsoft Assembler т.к он используется в дебагерах/дизассемблерах но подойдет и турбо-ассемблер TASM, главное познакомится с набором базовых часто встречающихся инструкций вроде jmp/je/jnz/call/push/pop/ret и этого хватит и да, ассемблер делится на архитектуры 32-битные и 64-битные а в нашем случае GTA:SA процесс имеет 32-битную архитектуру, потому х64 нас не интересует.) Мама прости я стану реверсером!))0

Привет мой юный читер! :) Если ты прочел наши предыдущие туторы и понял что это твое и ты кончаешь только от одного вида Visual Studio, то на этот раз мы тебя познакомим с болезненной темой которая крайне необходима для разработки читов, это не совсем программирование но близко по тематике.

Запасаемся попкорном и литрами пива потому что в данном туториале будет много жопной боли.

Реверс-инжиниринг а.к.а обратное исследование игр/какого либо приложения не владея его исходниками, однако не пугайтесь, в случае с мта, исходники как раз таки есть, нету только исходников самой мта провинс)) Но из-за опен сорсности мта это будет достаточно просто.

В данном примере вы также будете познакомлены с хуками, а.к.а перехватами функций, они вполне заслуживают отдельного туториала но цель текущего урока совсем не они, а попытка познакомить вас со способом поиска необходимых игровых оффсетов/изготовкой сигнатур кода.

Клонируем репозиторий исходников мта и собираем их, для этого есть официальный туториал на русском от разработчиков мта: https://wiki.multitheftauto.com/wiki/RU/Compiling_MTASA

  • Качаем IDA Pro 7.0

https://drive.google.com/file/d/1rneDAGNtrx4QI8Wf3AOsPrhKKTf-GFCJ/view?usp=sharing

  • При установке, укажите ключ указанный в файле SN.txt

  • После установки, перейдите в каталог куда ее установили и зайдите в папку plugins (В моем случае это C:\Program Files\IDA 7.0\plugins)

Скачайте плагин для готовки сигнатур SigMaker: https://github.com/ajkhoury/SigMaker-x64

Качать со вкладки релиза

image

image

Нас интересует конкретно sigmaker.dll - вот ее разместите в папку плагинс для идахи.

pass: provhacks

pass: provhacks

Открываем IDA Pro и открываем в ней client.dll которая расположена по пути: папка исходов мта\bin\mods\deadmatch\client.dll - вам предложит подгрузить найденный PDB. файл, соглашайтесь обязательно! (Этот файл будет как и дллка только в случае если вы выполнили пункт со сборкой исходников мта, это очень важно).

image

Теперь в поиске ищем функцию GetMoveSpeed

image

Открываем ту что принадлежит классу CVehicle т.к нас интересует получение скорости только для транспорта. Тыкаем два раза ЛКМ и попадаем внутрь тела функции, если интересно как она выглядит в исходниках мта то вот: https://github.com/multitheftauto/mtasa-blue/blob/ebf54d4fcfe9b0b5600c2b632595029ea97f2b3f/Client/mods/deathmatch/logic/CClientVehicle.cpp#L536

image

Переходим в идашку и видим следующее

image

Запускаем провинцию, переходим в настройке и ставим запуск в оконном режиме + низкое разрешение 1024 х (Важно! Иначе в случае брейкпоинта дебагером либо выходе с игры во время запущенной отладки, вы повисните и не сможете выйти пока не снимите процесс отладчика)

image

Сохранили настры? Терь закрываем игру.

Открываем NeutrinoInjector.exe

image

В появившемся окне нажимаем ОК и запустится игра, теперь входим на сервер.

Открываем от имени админа x32dbg

image

Открываем вкладку Файл и выбираем Присоединиться

image

Выбираем тут процесс gta_sa и жмем присоединиться

image

Переходим во вкладку Отладочные символы и пишем в строку поиска client.dll

image

Два раза кликаем ЛКМ по модулю client.dll и попадаем в код на его точку входа.

image

Сигнализировать о том что вы точно попали в модуль client.dll будет строка в самом верху, написано Модуль: client.dll

Теперь выделите весь этот код на точке входа, от инструкции push ebp тяните выделение до инструкции ret 4

image

Терь тыкните вкладку Модули → SwissArmyKnife → Signature → Create

image

Появится вкладка плагина для создания сигнатуры кода по выделенной области кода

image

Затираем поля Data и Mask, сюда чуть позже будем вставлять свои сигнатуры для проверки во время исполнения, найдет ли нашу сигнатуру в памяти игры и будет ли она уникальной!

Переходим обратно в IDA Pro

Выделяем следующий код (взят рандомно внутри функции)

image

Теперь переходим во вкладку Edit → Plugins → SigMaker и открываем плагин для изготовки сигнатур

image

Выбираем Create code pattern from selection

image

Видим что в окне вывода нам дало сгенерированую сигнатуру на этот код

image

Что такое сигнатура? Это уникальная последовательность байт-кода в памяти, для поиска нужных нам участков кода без использования оффсетов а.к.а смещений адрессов внутри памяти, так в чем же преимущество сигнатуры перед оффсетами? Оффсет правильно называть в реверс-инжиниринге RVA (Relative Virtual Address) Относительный виртуальный адрес. Проблема относительных адресов в том что если внутри дллки что-то поменяют разработчики то адрес сместится а нам этот геморой с повторным поиском оффсета совсем не к чему, вот тут приходят на помощь сигнатуры, они позволяют найти нужный нам адрес в памяти даже с тем учетом что нашу .dll могут обновить и что-то в нее добавить/поменять.

Сигнатура состоит из шаблона (pattern по-английски) и маски (mask по-английски), что значит шаблон? Шаблон это последовательность байт-кода, а точнее все байты которые есть в ассемблерных инструкциях этого кода, т.е опкоды процессора + байты данных. Маска же помогает понять какие из этих байтов являются статическими т.е никогда не изменяются и какие из них динамические (меняющихся, как правило это байты данных которые меняются при каждом запуске игры т.к указывают на поля каких то классов к примеру которые выделяются в памяти динамически).

Шаблон к примеру выглядит следующим образом: \x80\xB8\x00\x00\x00

Маска же выглядит так: xx???

В маске x - означает статический байт, ? - означает динамический байт, их порядок задан строго по одному байту от начала шаблона, в самом же шаблоне 1 байт это две цифры после \x префикса.

Копируем шаблон и маску сигнатуры в x32dbg

image

Нажимаем кнопку Scan

image

Внизу отладчика видим надпись Found 1 references(s) что значит что найдено 1-но совпадение, это свидетельствует что наша сигнатура валидна и она уникальная, очень важно что бы совпадение было только одно! А это к сожалению бывает не всегда и тут выручает только опыт. А что делать если не нашло вообще нихуя? 0 Совпадений к примеру. Тогда подобным образом копируем рандомно другие участки кода внутри интересующей функции пока не найдем 1-но совпадение. Если бы вообще нихера не нашло, то есть другой прием, смотрим в IDA

image

Здесь показывает кросс-модульные вызовы, откуда вызывает эту функцию, если нажать ЛКМ два раза по оффсету, то мы попадем в место где виден вызов call инструкции с нашей функцией, здесь тоже можно рандомно где то сгенерить сигнатуру и таким образом потом выйти в место откуда видно вызов нашей функи, в совсем безысходном положении это прекрасный выход но сложен он тем что в том месте потребуется еще искать call инструкцию с вызовом нашем функции а в памяти с отладчика уже не будет имен вызываемых функций и нам прийдется сопоставлять код с тем что дает x32dbg с тем как он выглядит в отладчике чтобы понять где именно нужная нам call инструкция, поиск упрощает то что можно посчитать через сколько call инструкций идет наша относительно от какого места выделена ваша сигнатура и где находится call НашаФункция

Окей, с трудностями во время изготовки сигнатур и способами их решений мы более менее в теории разобрались, теперь закроем x32dbg - вместе автоматически закроет и игру.

Т.к сигнатуру мы делали не от начала функции (начало функции в ассемблере зовут ее прологом а конец - эпилогом) то мы не сможем установить сюда хук а.к.а перехват функции, по этому нам нужно будет сделать вторую сигнатуру но уже от начала функции, т.к в IDA на угад выделяя код это сделать сложнее, поступим следующим образом.

Создаем DLL проект подобно предыдущим примерам с туториалов.

Качаем sigscan.h https://drive.google.com/file/d/1qlWtmoalc9yvP2bL4jvFZkiLqV-p-v8W/view?usp=sharing

Подключаем в проект и добавляем инклуд подобно остальным.

image

Сохраняем проект и пока что закроем, сейчас нам нужно скачать библиотеку для установки хуков - MinHook.

Клонируем репозиторий отсюда и собираем проект: https://github.com/TsudaKageyu/minhook

image

image

Открываем проект расположенный по пути source\repos\minhook\build\VC16

image

Компилируем проект libMinHook на конфигурации Release Win32

image

После сборки, переходим по пути source\repos\minhook\build\VC16\lib\Release

image

И копируем .lib файл в папку нашего проекта там где лежит dllmain.cpp

image

В эту же директорию копируем инклуд минхука, расположен он по пути source\repos\minhook\include

image

image

Обратно открываем проект нашей дллки! И подрубаем инклуд с библиотекой в проект.

image

#include "MinHook.h" // подрубаем инклуд минхука

#pragma comment(lib, "libMinHook.x86.lib") // подрубаем библиотеку минхука

Теперь пробуем собрать проект, если все собралось в порядке то можно идти дальше.

Далее рассмотрим готовый код с правильным примером как найти адрес по нашей сигнатуре уже в памяти игры с помощью нашей дллки которую как и в предыдущих примерах мы будем инжектить с помощью Neutrino Injector`a!

// Выключаем ошибку C4996 на безопасные функции С++ STL

#ifndef _CRT_SECURE_NO_WARNINGS

#define _CRT_SECURE_NO_WARNINGS

#endif

// Все эти инклуды иметь обязательно!

#include <TlHelp32.h>
#include <Windows.h>
#include <direct.h>
#include <stdio.h>
#include <winternl.h>

#include <algorithm>
#include <cwctype>
#include <random>
#include <string>
#include <thread>

#include "CVector.h"
#include "MinHook.h"  // подрубаем инклуд минхука
#include "sigscan.h"

bool AntiRadar = false;  // переменная-селектор включения нашего анти-радара

#pragma comment(lib, "libMinHook.x86.lib")  // подрубаем библиотеку минхука

bool w_findStringIC(const std::wstring& strHaystack,
                    const std::wstring& strNeedle)

// функция поиска юникодных строк которой похуй на регистр текста (ищет как
// Большие так и Маленькие буквы)

{
  auto it = std::search(strHaystack.begin(),
                        strHaystack.end(),  // лямбда-выражение С++11

                        strNeedle.begin(), strNeedle.end(),

                        [](wchar_t ch1, wchar_t ch2) {
                          return std::towupper(ch1) == std::towupper(ch2);
                        });

  return (it != strHaystack.end());
}

void FuckArthemidaIntegrity(
    std::string dllName)  // Выключаем обнаружение изменений в памяти игры

{
  MODULEINFO modinfo = {0};
  typedef NTSTATUS(__stdcall * pfnNtQueryInformationThread)(

      HANDLE ThreadHandle, THREAD_INFORMATION_CLASS ThreadInformationClass,

      PVOID ThreadInformation, ULONG ThreadInformationLength,
      PULONG ReturnLength);

  pfnNtQueryInformationThread fnNtQueryInformationThread =
      (pfnNtQueryInformationThread)

          GetProcAddress(GetModuleHandleA("ntdll.dll"),
                         "NtQueryInformationThread");

  K32GetModuleInformation(GetCurrentProcess(),
                          GetModuleHandleA(dllName.c_str()), &modinfo,
                          sizeof(MODULEINFO));

  HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);

  if (h != INVALID_HANDLE_VALUE)

  {
    THREADENTRY32 te{0};
    te.dwSize = sizeof(te);

    if (Thread32First(h, &te))

    {
      do

      {
        if (te.th32OwnerProcessID == GetCurrentProcessId())

        {
          HANDLE hThread =
              OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);

          if (hThread)

          {
            DWORD tempBase = NULL;

            fnNtQueryInformationThread(hThread, (THREAD_INFORMATION_CLASS)9,
                                       &tempBase, sizeof(DWORD), 0);

            if (tempBase >= (DWORD)modinfo.lpBaseOfDll &&

                tempBase <= ((DWORD)modinfo.lpBaseOfDll + modinfo.SizeOfImage))

              SuspendThread(hThread);  // ставим античит на паузу

            CloseHandle(hThread);
          }
        }

        te.dwSize = sizeof(te);

      } while (Thread32Next(h, &te));
    }

    CloseHandle(h);
  }
}

float cVecLength(CVector vec) {
  return sqrt((vec.fX * vec.fX) + (vec.fY * vec.fY) + (vec.fZ * vec.fZ));
}

typedef void(__thiscall* ptrVehGetMoveSpeed)(void* ECX, CVector* vecMoveSpeed);

ptrVehGetMoveSpeed callVehGetMoveSpeed = nullptr;

void __fastcall VehGetMoveSpeed(void* ECX, void* EDX, CVector* vecMoveSpeed)

{
  if (!AntiRadar)
    callVehGetMoveSpeed(ECX, vecMoveSpeed);

  else

  {
    CVector vecSpeed;
    callVehGetMoveSpeed(ECX, &vecSpeed);

    std::random_device rd;
    std::mt19937 gen(rd());

    std::uniform_real_distribution<float> dis(66.0f, 69.0f);

    float speed_limit = dis(gen), fMult = 111.84681456f;

    float acSpeed = cVecLength(vecSpeed * fMult);

    if (acSpeed && acSpeed != 0 && acSpeed >= 68.0f)

    {
      float diff = speed_limit / acSpeed;

      vecMoveSpeed->fX = vecSpeed.fX * diff;

      vecMoveSpeed->fY = vecSpeed.fY * diff;

      vecMoveSpeed->fZ = vecSpeed.fZ * diff;

    }

    else
      callVehGetMoveSpeed(ECX, vecMoveSpeed);
  }
}

std::string SetClipboardText(
    std::string output)  // функция копирования текста буфера обмена

{
  HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, output.length());

  memcpy(GlobalLock(hMem), output.c_str(), output.length());

  GlobalUnlock(hMem);

  OpenClipboard(0);

  EmptyClipboard();

  SetClipboardData(CF_TEXT, hMem);

  CloseClipboard();
}

// тело хука для перехвата загрузки .dll в наш процесс

typedef NTSTATUS(__stdcall* ptrLdrLoadDll)(PWCHAR PathToFile, ULONG Flags,
                                           PUNICODE_STRING ModuleFileName,
                                           PHANDLE ModuleHandle);

ptrLdrLoadDll callLdrLoadDll =
    nullptr;  // указатель на оригинальную функцию загрузки дллок

NTSTATUS __stdcall hkLdrLoadDll(PWCHAR PathToFile, ULONG Flags,
                                PUNICODE_STRING ModuleFileName,
                                PHANDLE ModuleHandle)  // хук на загрузку .dll

{
  // Ловим дллку client.dll в которой находится реализация нашей игровой
  // функции, грузит клиент.длл при подключении к серверу

  NTSTATUS sts =
      callLdrLoadDll(PathToFile, Flags, ModuleFileName, ModuleHandle);

  std::wstring wstrModuleFileName =
      std::wstring(ModuleFileName->Buffer, ModuleFileName->Length);

  if (w_findStringIC(wstrModuleFileName,
                     L"client.dll"))  // если наша .dll имеет имя .client.dll

  {
    FuckArthemidaIntegrity(
        "core.dll");  // ебем в античите проверку на модификацию памяти игры

    SigScan scan;
    MessageBeep(MB_ICONASTERISK);

    callVehGetMoveSpeed =
        (ptrVehGetMoveSpeed)
            scan.FindPattern(
                "client.dll",

                "\x80\xB8\x00\x00\x00\x00\x00\x74\x1B\x8B\x45\x08\xC7\x00\x00"
                "\x00\x00\x00\xC7\x40\x00\x00\x00\x00\x00\xC7\x40\x00\x00\x00"
                "\x00\x00",  // сюда вставляем шаблон сигнатуры

                "xx?????xxxxxxx????xx?????xx?????");  // сюда вставляем маску
                                                      // сигнатуры

    if (callVehGetMoveSpeed != nullptr)

    {
      // MH_RemoveHook(callVehGetMoveSpeed); // на всякий случай удаляем хук
      // вдруг мы будем перезаходить на сервер

      // MH_CreateHook(callVehGetMoveSpeed, &VehGetMoveSpeed,
      // reinterpret_cast<LPVOID*>(&callVehGetMoveSpeed)); // создаем хук

      // MH_EnableHook(MH_ALL_HOOKS); // включаем хук

      char strq[256];
      memset(strq, 0, sizeof(strq));

      sprintf(strq, "0x%X", (DWORD)callVehGetMoveSpeed);

      SetClipboardText(
          strq);  // копируем найденный адрес с сигнатуры в буфер обмена
    }
  }
}

void HackThread()  // наша функция с обработкой клавиш чита

{
  MH_Initialize();  // Обязательно инициализируем мин-хук но только 1 раз при
                    // загрузке длл!

  // Ставим хук на загрузку дллок в процесс игры

  callLdrLoadDll = (ptrLdrLoadDll)GetProcAddress(GetModuleHandleA("ntdll.dll"),
                                                 "LdrLoadDll");

  if (callLdrLoadDll != nullptr)

  {
    MH_CreateHook(callLdrLoadDll, &hkLdrLoadDll,
                  reinterpret_cast<LPVOID*>(&callLdrLoadDll));  // создаем хук

    MH_EnableHook(MH_ALL_HOOKS);  // включаем хук
  }

  while (true)

  {
    if (GetAsyncKeyState(VK_DELETE))  // если нажали кнопку DELETE

    {
      AntiRadar ^=
          true;  // переключаем селектор чита на состояние включен/выключен в
                 // зависимости какой он был до этого

      MessageBeep(MB_ICONASTERISK);  // проигрываем звук бипа

      Sleep(1000);
    }

    Sleep(125);
  }
}

std::thread RunOurThread(HackThread);  // создаем объект потока на нашу функцию

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
                      LPVOID lpReserved)

{
  switch (ul_reason_for_call)

  {
    case DLL_PROCESS_ATTACH:

      RunOurThread.detach();  // запускаем поток асинхронно

      break;
  }

  return TRUE;
}

Собираем проект и ложим дллку в папку нейтрино инжектора!

Заинжектили дллку, зашли на сервер, терь сверните игру и так как и раньше откройте x32dbg и присоедините его к процессу игры.

Нажимаем CTRL + G, тыкаем лкм в поле ввода и нажимаем CTRL + V - нам скопирует с буфера обмена адрес который нашла сигнатура с нашей дллки, теперь нажмите ENTER чтобы перейти по адресу!

В итоге мы видим тело функции в памяти, нажимаем пкм по коду → Анализ → Проанализировать модуль (Чтобы лучше видеть в отладчике где начало функции)

image

Теперь выделяем код начиная от push ebp инструкции (С нее начинается распаковка аргументов функции в ассемблере а.к.а пролог функции)

И тянем на рандомное кол-во кода

image

Теперь нажимаем на вкладку Модули → SwissArmyKnife → Options

image

Ставим галочку на Find shortest possible signatures (Slower) чтобы нам генерировало самые короткие возможные сигнатуры

image

Жмём окей, терь так же открываем SwissArmyKnife плагин но тыкает Signature → Create и нам выдает окно со сгенерированой сигнатурой

image

Внимание! Сигнатура показанная на скриншоте сделана под MTA:SA 1.5.9 и будет отличатся от боевых серверов мта провинц т.к делалась на на их тестовом сервере, не пугайтесь, сейчас на рп серверах и стресс-тесте могут быть различия в сигнатурах но далеко не во всех, однако в данном конкретном случае да.

Копируем поле Data (Это шаблон сигнатуры) и поле Mask - это маска сигнатуры с заменой в код нашего поиска сигнатур вот здесь и заменяем то что было.

image

Переходим в x32dbg и нажимаем ОК в окне сигнатур, внизу нам должно написать Found 1 references что свидетельствует сигнатура валид и она одна а значит уникальна.

Все, закрываем x32dbg, он нам больше не нужен!

Переходим в код длл, теперь разкоментируем зеленые строки отвечающие за включение хука и уберем лишнее копирование адреса с сигнатуры в буфер обмена. Готовый код вот.

// Выключаем ошибку C4996 на безопасные функции С++ STL

#ifndef _CRT_SECURE_NO_WARNINGS

#define _CRT_SECURE_NO_WARNINGS

#endif

// Все эти инклуды иметь обязательно!

#include <TlHelp32.h>
#include <Windows.h>
#include <direct.h>
#include <stdio.h>
#include <winternl.h>

#include <algorithm>
#include <cwctype>
#include <random>
#include <string>
#include <thread>

#include "CVector.h"
#include "MinHook.h"  // подрубаем инклуд минхука
#include "sigscan.h"

bool AntiRadar = false;  // переменная-селектор включения нашего анти-радара

#pragma comment(lib, "libMinHook.x86.lib")  // подрубаем библиотеку минхука

bool w_findStringIC(const std::wstring& strHaystack,
                    const std::wstring& strNeedle)

// функция поиска юникодных строк которой похуй на регистр текста (ищет как
// Большие так и Маленькие буквы)

{
  auto it = std::search(strHaystack.begin(),
                        strHaystack.end(),  // лямбда-выражение С++11

                        strNeedle.begin(), strNeedle.end(),

                        [](wchar_t ch1, wchar_t ch2) {
                          return std::towupper(ch1) == std::towupper(ch2);
                        });

  return (it != strHaystack.end());
}

void FuckArthemidaIntegrity(
    std::string dllName)  // Выключаем обнаружение изменений в памяти игры

{
  MODULEINFO modinfo = {0};
  typedef NTSTATUS(__stdcall * pfnNtQueryInformationThread)(

      HANDLE ThreadHandle, THREAD_INFORMATION_CLASS ThreadInformationClass,

      PVOID ThreadInformation, ULONG ThreadInformationLength,
      PULONG ReturnLength);

  pfnNtQueryInformationThread fnNtQueryInformationThread =
      (pfnNtQueryInformationThread)

          GetProcAddress(GetModuleHandleA("ntdll.dll"),
                         "NtQueryInformationThread");

  K32GetModuleInformation(GetCurrentProcess(),
                          GetModuleHandleA(dllName.c_str()), &modinfo,
                          sizeof(MODULEINFO));

  HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);

  if (h != INVALID_HANDLE_VALUE)

  {
    THREADENTRY32 te{0};
    te.dwSize = sizeof(te);

    if (Thread32First(h, &te))

    {
      do

      {
        if (te.th32OwnerProcessID == GetCurrentProcessId())

        {
          HANDLE hThread =
              OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);

          if (hThread)

          {
            DWORD tempBase = NULL;

            fnNtQueryInformationThread(hThread, (THREAD_INFORMATION_CLASS)9,
                                       &tempBase, sizeof(DWORD), 0);

            if (tempBase >= (DWORD)modinfo.lpBaseOfDll &&

                tempBase <= ((DWORD)modinfo.lpBaseOfDll + modinfo.SizeOfImage))

              SuspendThread(hThread);  // ставим античит на паузу

            CloseHandle(hThread);
          }
        }

        te.dwSize = sizeof(te);

      } while (Thread32Next(h, &te));
    }

    CloseHandle(h);
  }
}

float cVecLength(CVector vec) {
  return sqrt((vec.fX * vec.fX) + (vec.fY * vec.fY) + (vec.fZ * vec.fZ));
}

typedef void(__thiscall* ptrVehGetMoveSpeed)(
    void* ECX, CVector* vecMoveSpeed);  // шаблон указателя на функцию

ptrVehGetMoveSpeed callVehGetMoveSpeed = nullptr;  // сам указатель на функцию

// Обратите внимание что метод CVehicle::GetMoveSpeed является членом класса и
// не имеет атрибута static а значит согласно соглашениям о вызовах в ассемблере
// под архитектуру 32-бита, эта функция будет иметь соглашение о вызове
// __thiscall и для хука используются два дополнительных аргумента которых нет в
// шапке функции среди исходников мта (это void* ECX, void *EDX) в ECX регистре,
// будет передаваться адрес класса которому принадлежит метод, EDX регистр не
// используется в __thiscall но нужен для соглашения __fastcall т.к это
// соглашение используется как обходной путь для хука на член класса ибо
// визуалка не позволит поставить __thiscall хук напрямую, компилятор не знает
// адрес класса которому пренадлежит метод т.к класс создается в памяти по
// динамическому адресу! Пока что не забивайте этим голову, это тема отдельного
// урока и он очень сложен.

void __fastcall VehGetMoveSpeed(
    void* ECX, void* EDX,
    CVector* vecMoveSpeed)  // тело хука для перехвата синхронизации скорости на
                            // сервер

{
  if (!AntiRadar)
    callVehGetMoveSpeed(ECX,
                        vecMoveSpeed);  // если анти-радар офнут то пропускаем
                                        // код ниже в обычном режиме функции

  else

  {
    // код рассчёта валидной скорости для GTA SA

    CVector vecSpeed;
    callVehGetMoveSpeed(ECX, &vecSpeed);

    std::random_device rd;
    std::mt19937 gen(rd());

    std::uniform_real_distribution<float> dis(
        66.0f, 69.0f);  // рандом скорость от 66 до 69 кмч

    float speed_limit = dis(gen), fMult = 111.84681456f;

    float acSpeed =
        cVecLength(vecSpeed * fMult);  // Внимание! Погрешность 10-11 км/ч,
                                       // потому ставим на 10-11 км/ч меньше!

    if (acSpeed && acSpeed != 0 &&
        acSpeed >=
            68.0f)  // ограничиваем для сервера, значение спидометра в 79 км/ч

    {
      float diff = speed_limit / acSpeed;

      // Задаем вектор ускорения в пакет синхронизации на новое значение

      vecMoveSpeed->fX = vecSpeed.fX * diff;

      vecMoveSpeed->fY = vecSpeed.fY * diff;

      vecMoveSpeed->fZ = vecSpeed.fZ * diff;

    }

    else
      callVehGetMoveSpeed(ECX, vecMoveSpeed);
  }
}

// тело хука для перехвата загрузки .dll в наш процесс

typedef NTSTATUS(__stdcall* ptrLdrLoadDll)(PWCHAR PathToFile, ULONG Flags,
                                           PUNICODE_STRING ModuleFileName,
                                           PHANDLE ModuleHandle);

ptrLdrLoadDll callLdrLoadDll =
    nullptr;  // указатель на оригинальную функцию загрузки дллок

NTSTATUS __stdcall hkLdrLoadDll(PWCHAR PathToFile, ULONG Flags,
                                PUNICODE_STRING ModuleFileName,
                                PHANDLE ModuleHandle)  // хук на загрузку .dll

{
  // Ловим дллку client.dll в которой находится реализация нашей игровой
  // функции, грузит клиент.длл при подключении к серверу

  NTSTATUS sts =
      callLdrLoadDll(PathToFile, Flags, ModuleFileName, ModuleHandle);

  std::wstring wstrModuleFileName =
      std::wstring(ModuleFileName->Buffer, ModuleFileName->Length);

  if (w_findStringIC(wstrModuleFileName,
                     L"client.dll"))  // если наша .dll имеет имя .client.dll

  {
    FuckArthemidaIntegrity(
        "core.dll");  // ебем в античите проверку на модификацию памяти игры

    SigScan scan;
    MessageBeep(MB_ICONASTERISK);

    callVehGetMoveSpeed = (ptrVehGetMoveSpeed)scan.FindPattern(
        "client.dll",

        // mta:sa 1.5.8 сигнатура для рп серверов провинции(На стресс-тест
        // сервере может отличатся т.к там мта 1.5.9 но это не точно :D !)

        "\x55\x8B\xEC\x8B\xC1\x80",  // сюда вставляем шаблон сигнатуры

        "xxxxxx");  // сюда вставляем маску сигнатуры

    if (callVehGetMoveSpeed != nullptr)

    {
      MH_RemoveHook(callVehGetMoveSpeed);  // на всякий случай удаляем хук вдруг
                                           // мы будем перезаходить на сервер

      MH_CreateHook(
          callVehGetMoveSpeed, &VehGetMoveSpeed,
          reinterpret_cast<LPVOID*>(&callVehGetMoveSpeed));  // создаем хук

      MH_EnableHook(MH_ALL_HOOKS);  // включаем хук
    }
  }
}

void HackThread()  // наша функция с обработкой клавиш чита

{
  MH_Initialize();  // Обязательно инициализируем мин-хук но только 1 раз при
                    // загрузке длл!

  // Ставим хук на загрузку дллок в процесс игры

  callLdrLoadDll = (ptrLdrLoadDll)GetProcAddress(GetModuleHandleA("ntdll.dll"),
                                                 "LdrLoadDll");

  if (callLdrLoadDll !=
      nullptr)  // всегда проверяем указатель в С++ на нулевой чтобы не крашнуло

  {
    MH_CreateHook(callLdrLoadDll, &hkLdrLoadDll,
                  reinterpret_cast<LPVOID*>(&callLdrLoadDll));  // создаем хук

    MH_EnableHook(MH_ALL_HOOKS);  // включаем хук
  }

  while (true)

  {
    if (GetAsyncKeyState(VK_DELETE))  // если нажали кнопку DELETE

    {
      AntiRadar ^=
          true;  // переключаем селектор чита на состояние включен/выключен в
                 // зависимости какой он был до этого

      MessageBeep(MB_ICONASTERISK);  // проигрываем звук бипа

      Sleep(1000);
    }

    Sleep(125);
  }
}

std::thread RunOurThread(HackThread);  // создаем объект потока на нашу функцию

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
                      LPVOID lpReserved)

{
  switch (ul_reason_for_call)

  {
    case DLL_PROCESS_ATTACH:

      RunOurThread.detach();  // запускаем поток асинхронно

      break;
  }

  return TRUE;
}

Собираем дллку, запускаем нейтрино, входим на сервер, садимся в любую тачку за руль, едем по городу и разгоняемся на макс скорость, в итоге видим что спидометр упирается в 77-78 км/ч и нам не дает штрафы за превышение скорости! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment