Требования к читателю:
- Знание основ С++ (Сюда входит: переменные, арифметические операции, операторы условного ветвления, понимание работы блоков видимости кода, указатели, базовое понимание работы и создания потоков, умение подключить инклуды)
- Базовые навыки по работе с 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
Качать со вкладки релиза
Нас интересует конкретно sigmaker.dll - вот ее разместите в папку плагинс для идахи.
pass: provhacks
-
Качаем отладчик x64dbg от Mr.Exodia (внутри есть и x32 версия которая нам и нужна, тут стоят все необходимые настройки и плагины для реверса под провинцию): https://drive.google.com/file/d/1mvEBARTULS44X31aMoe3yIuxRJ_FzCwc/view?usp=sharing
-
Качаем спец.билд Neutrino Injector с обходом FairplayKD.sys драйвера (Позволяет прицепить отладчик): https://drive.google.com/file/d/19B5tP5C-LfdP5oeiOe3ARgVkf6uV5-x1/view?usp=sharing
pass: provhacks
Открываем IDA Pro и открываем в ней client.dll которая расположена по пути: папка исходов мта\bin\mods\deadmatch\client.dll - вам предложит подгрузить найденный PDB. файл, соглашайтесь обязательно! (Этот файл будет как и дллка только в случае если вы выполнили пункт со сборкой исходников мта, это очень важно).
Теперь в поиске ищем функцию GetMoveSpeed
Открываем ту что принадлежит классу CVehicle т.к нас интересует получение скорости только для транспорта. Тыкаем два раза ЛКМ и попадаем внутрь тела функции, если интересно как она выглядит в исходниках мта то вот: https://github.com/multitheftauto/mtasa-blue/blob/ebf54d4fcfe9b0b5600c2b632595029ea97f2b3f/Client/mods/deathmatch/logic/CClientVehicle.cpp#L536
Переходим в идашку и видим следующее
Запускаем провинцию, переходим в настройке и ставим запуск в оконном режиме + низкое разрешение 1024 х (Важно! Иначе в случае брейкпоинта дебагером либо выходе с игры во время запущенной отладки, вы повисните и не сможете выйти пока не снимите процесс отладчика)
Сохранили настры? Терь закрываем игру.
Открываем NeutrinoInjector.exe
В появившемся окне нажимаем ОК и запустится игра, теперь входим на сервер.
Открываем от имени админа x32dbg
Открываем вкладку Файл и выбираем Присоединиться
Выбираем тут процесс gta_sa и жмем присоединиться
Переходим во вкладку Отладочные символы и пишем в строку поиска client.dll
Два раза кликаем ЛКМ по модулю client.dll и попадаем в код на его точку входа.
Сигнализировать о том что вы точно попали в модуль client.dll будет строка в самом верху, написано Модуль: client.dll
Теперь выделите весь этот код на точке входа, от инструкции push ebp тяните выделение до инструкции ret 4
Терь тыкните вкладку Модули → SwissArmyKnife → Signature → Create
Появится вкладка плагина для создания сигнатуры кода по выделенной области кода
Затираем поля Data и Mask, сюда чуть позже будем вставлять свои сигнатуры для проверки во время исполнения, найдет ли нашу сигнатуру в памяти игры и будет ли она уникальной!
Переходим обратно в IDA Pro
Выделяем следующий код (взят рандомно внутри функции)
Теперь переходим во вкладку Edit → Plugins → SigMaker и открываем плагин для изготовки сигнатур
Выбираем Create code pattern from selection
Видим что в окне вывода нам дало сгенерированую сигнатуру на этот код
Что такое сигнатура? Это уникальная последовательность байт-кода в памяти, для поиска нужных нам участков кода без использования оффсетов а.к.а смещений адрессов внутри памяти, так в чем же преимущество сигнатуры перед оффсетами? Оффсет правильно называть в реверс-инжиниринге RVA (Relative Virtual Address) Относительный виртуальный адрес. Проблема относительных адресов в том что если внутри дллки что-то поменяют разработчики то адрес сместится а нам этот геморой с повторным поиском оффсета совсем не к чему, вот тут приходят на помощь сигнатуры, они позволяют найти нужный нам адрес в памяти даже с тем учетом что нашу .dll могут обновить и что-то в нее добавить/поменять.
Сигнатура состоит из шаблона (pattern по-английски) и маски (mask по-английски), что значит шаблон? Шаблон это последовательность байт-кода, а точнее все байты которые есть в ассемблерных инструкциях этого кода, т.е опкоды процессора + байты данных. Маска же помогает понять какие из этих байтов являются статическими т.е никогда не изменяются и какие из них динамические (меняющихся, как правило это байты данных которые меняются при каждом запуске игры т.к указывают на поля каких то классов к примеру которые выделяются в памяти динамически).
Шаблон к примеру выглядит следующим образом: \x80\xB8\x00\x00\x00
Маска же выглядит так: xx???
В маске x - означает статический байт, ? - означает динамический байт, их порядок задан строго по одному байту от начала шаблона, в самом же шаблоне 1 байт это две цифры после \x префикса.
Копируем шаблон и маску сигнатуры в x32dbg
Нажимаем кнопку Scan
Внизу отладчика видим надпись Found 1 references(s) что значит что найдено 1-но совпадение, это свидетельствует что наша сигнатура валидна и она уникальная, очень важно что бы совпадение было только одно! А это к сожалению бывает не всегда и тут выручает только опыт. А что делать если не нашло вообще нихуя? 0 Совпадений к примеру. Тогда подобным образом копируем рандомно другие участки кода внутри интересующей функции пока не найдем 1-но совпадение. Если бы вообще нихера не нашло, то есть другой прием, смотрим в IDA
Здесь показывает кросс-модульные вызовы, откуда вызывает эту функцию, если нажать ЛКМ два раза по оффсету, то мы попадем в место где виден вызов call инструкции с нашей функцией, здесь тоже можно рандомно где то сгенерить сигнатуру и таким образом потом выйти в место откуда видно вызов нашей функи, в совсем безысходном положении это прекрасный выход но сложен он тем что в том месте потребуется еще искать call инструкцию с вызовом нашем функции а в памяти с отладчика уже не будет имен вызываемых функций и нам прийдется сопоставлять код с тем что дает x32dbg с тем как он выглядит в отладчике чтобы понять где именно нужная нам call инструкция, поиск упрощает то что можно посчитать через сколько call инструкций идет наша относительно от какого места выделена ваша сигнатура и где находится call НашаФункция
Окей, с трудностями во время изготовки сигнатур и способами их решений мы более менее в теории разобрались, теперь закроем x32dbg - вместе автоматически закроет и игру.
Т.к сигнатуру мы делали не от начала функции (начало функции в ассемблере зовут ее прологом а конец - эпилогом) то мы не сможем установить сюда хук а.к.а перехват функции, по этому нам нужно будет сделать вторую сигнатуру но уже от начала функции, т.к в IDA на угад выделяя код это сделать сложнее, поступим следующим образом.
Создаем DLL проект подобно предыдущим примерам с туториалов.
Качаем sigscan.h https://drive.google.com/file/d/1qlWtmoalc9yvP2bL4jvFZkiLqV-p-v8W/view?usp=sharing
Подключаем в проект и добавляем инклуд подобно остальным.
Сохраняем проект и пока что закроем, сейчас нам нужно скачать библиотеку для установки хуков - MinHook.
Клонируем репозиторий отсюда и собираем проект: https://github.com/TsudaKageyu/minhook
Открываем проект расположенный по пути source\repos\minhook\build\VC16
Компилируем проект libMinHook на конфигурации Release Win32
После сборки, переходим по пути source\repos\minhook\build\VC16\lib\Release
И копируем .lib файл в папку нашего проекта там где лежит dllmain.cpp
В эту же директорию копируем инклуд минхука, расположен он по пути source\repos\minhook\include
Обратно открываем проект нашей дллки! И подрубаем инклуд с библиотекой в проект.
#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 чтобы перейти по адресу!
В итоге мы видим тело функции в памяти, нажимаем пкм по коду → Анализ → Проанализировать модуль (Чтобы лучше видеть в отладчике где начало функции)
Теперь выделяем код начиная от push ebp инструкции (С нее начинается распаковка аргументов функции в ассемблере а.к.а пролог функции)
И тянем на рандомное кол-во кода
Теперь нажимаем на вкладку Модули → SwissArmyKnife → Options
Ставим галочку на Find shortest possible signatures (Slower) чтобы нам генерировало самые короткие возможные сигнатуры
Жмём окей, терь так же открываем SwissArmyKnife плагин но тыкает Signature → Create и нам выдает окно со сгенерированой сигнатурой
Внимание! Сигнатура показанная на скриншоте сделана под MTA:SA 1.5.9 и будет отличатся от боевых серверов мта провинц т.к делалась на на их тестовом сервере, не пугайтесь, сейчас на рп серверах и стресс-тесте могут быть различия в сигнатурах но далеко не во всех, однако в данном конкретном случае да.
Копируем поле Data (Это шаблон сигнатуры) и поле Mask - это маска сигнатуры с заменой в код нашего поиска сигнатур вот здесь и заменяем то что было.
Переходим в 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 км/ч и нам не дает штрафы за превышение скорости! :)