Статья [0x8]Легкий Геймхакинг: Шаг за шагом.

Unseen

(L3) cache
Пользователь
Регистрация
17.01.2022
Сообщения
262
Реакции
246
Автор: Unseen
Специально для xss.pro

Предыдущая часть: https://xss.pro/threads/108173/

Вступление…
Доброго времени суток дорогие форумчане! Прошу прощения, что не было новых частей давно =) Сегодня в этой статье начнем практиковаться полученными знаниями из предыдущих частей на примере популярной всеми любимой игры – CS;GO, CS2. В этой части предлагаю рассмотреть дамперы, как они сильно упрощают жизнь при разработки читов. Постараюсь объяснить все на более простом языке. Попробуем реализовать простенький чит с минимальными функциями в виде External для примера. И да, мы не станем полностью реверсить игру, так как до нас уже это сделали добрые люди и думаю изобретать велосипед будет лишней тратой времени, я лишь в теории попытаюсь быстренько объяснить это все. В будущих уроках, мы конечно углубимся в эти структуры, чтобы было глубокое понимание о них. А вам пожелаю приятного чтива и надеюсь вы получите для себя новые знания по взлому игр.

Подготовка…
Итак, особо большой разницы по поиску тех же структур и смещений для этой игры по сравнению с той же Assault Cube нету, почти во всех играх техники поиска и реверса будут примерно одинаковы. А чтобы не повторяться насчет ручного поиска смещений, давайте научимся работать с дампером смещений. Он реально, сильно упрощает нашу с вами жизнь! Действия, которые рекомендую сделать перед началом работы, дабы обезопасить свой основной аккаунт:

1)Создать новый аккаунт и делать все данные действия с него

2)Сделать игру в оконном режиме, чтобы было удобно работать

3)Далее запустить игру без античита VAC.

Как запустить CS2 без античита VAC:

  • Авторизуйтесь или зайдите в Steam;
  • Найдите Counter-Strike 2 в библиотеке;
  • Нажмите ПКМ на поле с названием игры;
  • Затем перейдите во вкладку «Свойства»;
  • Выберите «Основные», а потом — «Параметры запуска»;
  • Пропишите консольную команду -insecure

1.png

После всех манипуляций запускаем игру и создаем игру с бесконечной разминкой. В итоге имеем такой вид:
2.png

Начало…
Как мы уже понимаем, что написание External читов - это почти всегда управление/работа с памятью игры. Примерный процесс можем выделить следующий:
  • Отреверсить игру в целях лучшего понимания ее внутренней реализации(структур, логики,классов и т.д.)
  • Найти смещения(оффсеты) для структур и их полей
  • Сделать некие действия(чтение/запись) с найденными адресами или на основе полученных данных в этих адресах манипулировать курсором/кнопками мыши, клавиатурой, рисовать поверх окна игры и т.д., что в итоге дает некую реализацию функции в нашем чите.
И чтобы каждый раз не вычислять смещения, разработчики читов создают дамперы. Это софт, который на основе различных техник поиска(например сигнатурный поиск), вычисляет смещения для различных структур и т.д. Начнем с того, что игры, такие как CS:GO, CS2 – крупные проекты и они состоят из различных файлов, имеется один основной исполняемый файл, и другие, такие как: различные модули dll, библиотеки, текстуры, модели и прочее. Чтобы все это вызывать, игра должна хранить где-то всю эту информацию. Обычно дамперы находят все эти данные в памяти и просто записывают всю эту информацию в какой-нибудь файл – пример текстовый файл. В той же CS;GO/CS2 этот файл, который хранит информацию об структурах, анимациях и т.д. называется SchemaSystem. А SchemaSystem это место, где хранятся все смещения и игровые классы/структуры. SchemaSystem.dll . И да заметим, что многие моменты тоже зависят от игрового движка, по сути, если отреверсить сам движок, то можно легко писать читы под разные игры, которые были сделаны на этом движке. Потому, что база почти у всех одна и та же обычно.

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

Давайте взглянем на этот дампер смещений для CS2. Ссылка на гитхаб проекта https://github.com/a2x/cs2-dumper Скачать собранный релизный вариант можно здесь https://github.com/a2x/cs2-dumper/releases , советую брать последний релиз.
3.png

Распаковываем в любое место и собственно можем запустить, тем самым у нас начнется генерация смещений! В результаты мы получаем папку с названием «output», и в ней будут файлы с названием модулей dll, в которых соответственно структуры и смещения относительно этих структур и самих модулей. Хочу отметить, что мы получаем файлы в разных форматах и это очень удобно. Например, формат json, он очень удобен при обновлениях смещений для нашего чита, ваши клиенты могут делать запрос на ваш сервер с дампером и получать всегда актуальные данные(смещения). С этим форматом автоматизировать данный процесс очень легко!
Содержимое папки OUTPUT:

4.png

Давайте взглянем на примере файла структур для модуля client.dll в котором можем найти нашу структуру базовой сущности(C_BaseEntity) и смещения для его полей. Например, смещение для здоровья будет m_iHealth = 0x334. От этой структуры наследуются другие структуры наподобие локального игрока. Как видим, это знакомая картина, мы размечали похожую структуру в Reclass в игре Assault Cube. В ней есть куча полезной информации, которая будет нам на руку при написании функций в чите!
5.png

И да, скажу сразу, что в ходе реверса CS;GO/CS2 за игрока отвечают аж две структуры:
Первая структура - это наш контроллер(CBasePlayerController) и в ней хранится больше сетевая информация типа никнейм(m_iszPlayerName), стим айди(m_steamID), и куча других.
6.png

Вторая структура это что-то типа пешки/модели(C_BasePlayerPawn), в ней тоже куча полезной информации типа углов градуса и т.д.
7.png

При написании кода мы будем плотно взаимодействовать с каждым из них. Так как они оба очень связаны.

Еще можем заметить, что рядом с описаниями полей нам дампер четко дал комментария для типов полей структуры(пример Vector, bool, float). Это все сильно упрощает нам время на разработку. Теперь же, чтобы не зацикливаться на этом файле, предлагаю написать простенькую базу для нашего чита. Как договорились выше, реализуем все в виде External чита. Если забыли External софт работает отдельно от процесса игры и всего лишь делает запросы вида Чтение/Запись по адресу процесса игры. Конечно в этой части мы не будем делать какие-либо серьезные функции в нашем софте, так как идея этой части продемонстрировать вам пользу дампера и хоть как-то коснуться игр по серьезнее.

Практика…
Давайте по традиции создадим проект в Visual studio. Языком будет C/C++. И думаю, создадим два заголовочных файла. Это Memory.h – там будем хранить все функции для работы с памятью процесса, то есть игры. И само собой offsets.h – как догадались там будут лежать нужные нам смещения. Так у нас будет порядок в кодовой базе нашего проекта, дабы в будущем легко было его поддерживать!

В итоге у нас получается следующая картина:
8.png

Весь исходный код Memory.h Думаю можно не объяснять, это все те же функции, которые мы писали на прошлых частях, только чуть доработанные под нашу игру. Разобраться будет не сложно!

А теперь давайте попробуем поработать со структурой нашего локального игрока и его полями . Как мы могли увидеть в файле с оффсетами сгенерированным дампером для модуля client.dll.hpp хранятся различные структуры и их смещения. А главные смещения для нужных нам структур наподобие локальный игрок, список сущностей, углы, и других систем можно найти в файле offsets.hpp. Сделано очень удобно я бы сказал.

Содержимое offsets.hpp:

9.png

Теперь нам следует скопировать нужные нам смещения в заголовочный файл offsets.h в нашем проекте. Я взял туда для нашего эксперимента:
  • dwLocalPlayerPawn(Пешка игрока – моделька),
  • m_iHealth(поле здоровье),
  • m_nPrevArmorVal(Бронежилет),
  • m_iTeamNum(номер команды игрока),
  • m_flFlashMaxAlpha(эффект флешки на игрока)
Т.е. все нужное для нашего локального игрока.

Как выглядит итоговый вариант offsets.h:
10.png

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

Важно! В главном файле проекта не забудьте подключить нужные нам библиотеки, заголовочные файлы.

Полный исходный код проекта:
C++:
#include "Memory.h"
#include <iostream>
#include "offsets.h"
#include <string>

int main() {

    setlocale(LC_ALL, "Russian");

    std::cout << "CS2 Pid: " << MEMORY::processId << std::endl;
    std::cout << "Address of client.dll: " << std::hex << MEMORY::baseAddress << std::endl;
   
    //Получаем структуру локального игрока
    uintptr_t localPlayerPawn = MEMORY::memRead<uintptr_t>(MEMORY::baseAddress + offsets::dwLocalPlayerPawn);

    //Получаем адрес поля-переменной для эффекта дейтсвия флешки на игрока
    uintptr_t flash = localPlayerPawn + offsets::m_flFlashMaxAlpha;
   
    //AntiFlash
    //Записываем по адресу новое значение 50.5, и сохраняем результат операции в res.
    bool res = MEMORY::memWrite<float>(flash, 50.5);
    if (res) {
        std::cout << "Успешно записано!"<<std::endl;
    }
    else {
        std::cout << "Не удалось!"<<std::endl;
    }

    std::cout << "Для продолжения нажмите любую клавишу!" << std::endl;
    getchar();


    while (1)
    {
        int armor = MEMORY::memRead<int>(localPlayerPawn + offsets::m_nPrevArmorVal);
        int health = MEMORY::memRead<int>(localPlayerPawn + offsets::m_iHealth);
        int team_num = MEMORY::memRead<int>(localPlayerPawn + offsets::m_iTeamNum);
        std::cout << "HP:" << std::dec << health << " |  Team:" << team_num << " | Armor: "<< armor << std::endl;
        Sleep(1000);
        system("cls");
    }
   


   

    getchar();
}

Полный исходный код файла Memory.h:
C++:
#pragma once
#include <windows.h>
#include <TlHelp32.h>
#define WIN32_LEAN_AND_MEAN




namespace MEMORY {
    DWORD GetProcess(const wchar_t* Target) {
        HANDLE snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

        if (snapshotHandle == INVALID_HANDLE_VALUE) {
            return NULL;
        }

        PROCESSENTRY32W processEntry = { };
        processEntry.dwSize = sizeof(PROCESSENTRY32W);

        if (Process32FirstW(snapshotHandle, &processEntry)) {

            do {

                if (_wcsicmp(processEntry.szExeFile, Target) == 0) {
                    CloseHandle(snapshotHandle);
                    return processEntry.th32ProcessID;
                }



            } while (Process32NextW(snapshotHandle, &processEntry));



        }

        CloseHandle(snapshotHandle);
        return NULL;

    }



    uintptr_t GetModuleBaseAddress(DWORD processId, const wchar_t* ModuleTarget) {

        HANDLE snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId);

        if (snapshotHandle == INVALID_HANDLE_VALUE) {
            return NULL;
        }

        MODULEENTRY32W moduleEntry = { };
        moduleEntry.dwSize = sizeof(MODULEENTRY32W);

        if (Module32FirstW(snapshotHandle, &moduleEntry)) {

            do {

                if (_wcsicmp(moduleEntry.szModule, ModuleTarget) == 0) {
                    CloseHandle(snapshotHandle);
                    return reinterpret_cast<uintptr_t>(moduleEntry.modBaseAddr);
                }



            } while (Module32NextW(snapshotHandle, &moduleEntry));



        }

        CloseHandle(snapshotHandle);
        return NULL;

    }





    DWORD processId = GetProcess(L"cs2.exe");

    uintptr_t baseAddress = GetModuleBaseAddress(processId, L"client.dll");


    HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);



    template <typename type>
    type memRead(uintptr_t pointerStatic) {
        type value = { };
        ReadProcessMemory(MEMORY::processHandle, (LPVOID)pointerStatic, &value, sizeof(type), NULL);
        return value;
    }

    template <typename type>
    bool memWrite(uintptr_t pointerStatic, type value) {

        return WriteProcessMemory(MEMORY::processHandle, (LPVOID)pointerStatic, &value, sizeof(type), NULL);

    }

}

Полный исходный код файла offsets.h:
C++:
#pragma once

namespace offsets {

    int dwLocalPlayerPawn = 0x173B568;
    int m_nPrevArmorVal = 0x14C0;
    int m_iHealth = 0x334;
    int m_iTeamNum = 0x3CB;
    int m_flFlashMaxAlpha = 0x1360;

}

А чтобы найти структуру нашего игрока нам необходимо получить базовый адрес модуля client.dll, в котором и находится эта структура. Для этого давайте выведем для информативности pid процесса игры и базовый адрес этого модуля – client.dll.
C++:
std::cout << "CS2 Pid: " << MEMORY::processId << std::endl;
std::cout << "Address of client.dll: " << std::hex << MEMORY::baseAddress << std::endl;

К фунцкиям и переменным из файла Memory.h можем обращаться через MEMORY::

Далее создадим переменную – localPlayerPawn в которой будем хранить адрес указателя на структуру локального игрока. Локальный игрок у нас получается базовый адрес модуля(client.dll) + смещение dwLocalPlayerPawn. То есть мы получили адрес указателя при помощи пешки игрока. Как говорили ранее путей может быть больше.
C++:
uintptr_t localPlayerPawn = MEMORY::memRead<uintptr_t>(MEMORY::baseAddress + offsets::dwLocalPlayerPawn);

Ну и само собой, нам теперь необходимо в самой структуре игрока обратиться к его полям-переменным жизней, брони, номер команды. И вывести результаты.

Создаем переменные health, armor, team_num в которых будем хранить значения здоровья, брони, номер команды. И при помощи MEMORY::memRead обращаемся к значением хранимых в адресах. Будем это все выводить каждую секунду в цикле.
C++:
while (1)
{
    int armor = MEMORY::memRead<int>(localPlayerPawn + offsets::m_nPrevArmorVal);
    int health = MEMORY::memRead<int>(localPlayerPawn + offsets::m_iHealth);
    int team_num = MEMORY::memRead<int>(localPlayerPawn + offsets::m_iTeamNum);
    std::cout << "HP:" << std::dec << health << " |  Team:" << team_num << " | Armor: "<< armor << std::endl;
    Sleep(1000);
    system("cls");
}

Результаты:
11.png

Как можем наблюдать, все сработало отлично и мы вывели все необходимое. Team 2 – получается игрок играет за террористов. Попробуйте поменять команду и посмотреть другие значения =)

Давайте теперь попробуем сделать, что-нибудь похожее на AntiFlash(Чтобы игрока не слепило светогранатой).

Для этого так же объявим переменную flash в которой сохраним адрес поля, отвечающий за эффект от флешки.
C++:
uintptr_t flash = localPlayerPawn + offsets::m_flFlashMaxAlpha;

Далее нам необходимо произвести запись нового значения по этому адресу. Значение должно быть типа float. Как нам определил дампер поле m_flFlashMaxAlpha является типом float. Значит и записывать следует туда значения такого типа. Я для примера взял значение 50.5, получилось что-то легитного антифлэша))
C++:
bool res = MEMORY::memWrite<float>(flash, 50.5);
if (res) {
    std::cout << "Успешно записано!"<<std::endl;
}
else {
    std::cout << "Не удалось!"<<std::endl;
}
Как заметили, я еще объявил переменную res, чтобы проверить успешность выполнения функции записи memWrite. Она возвращает логический тип bool. Лучше всегда проверять корректность выполнения таких операций.

Результат AntiFlash:
12.png

Как видим все смещения работают отлично и дампер сильно упростил нам задачу. Конечно можно было вывести еще кучу другой полезной информации из игрового процесса при помощи смещений. Но оставим это на будущее или же очень любопытным)

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

Еще отдельно хотелось бы добавить пару слов насчет античита VAC. Если на прайм аккаунте произвести такие эксперименты с такими полями, как m_flFlashMaxAlpha, у вас сразу же станет красный траст. Даже если бы мы проделали запись в памяти на уровне ядра. Потому что античит VAC не только смотрит метод записи в память, но и смотрит на значение по дефолту и если оно поменяется, то это уже вызывает недоворие и собственно красный траст. И наша с вами задача правильно это все обходить)) Детальнее рассмотрим в будущих частях.

На этом думаю можно заканчивать эту часть статьи, если у вас остались вопросы – смело задавайте под постом. Надеюсь, статья была полезной и понятной. Постараюсь выпускать следующие части почаще! До следующих частей!

Ваш Unseen!
 
Последнее редактирование:
Классный цикл статей, который дает понимание того, как концептуально функционируют все читы. Для меня этот цикл стал точкой входа в эту предметную область, спасибо!

Но как я понимаю, самое сложное в этой сфере - не само написание читов, а обход античита. Планируются ли статьи по механикам обхода актуальных античитов?
 
Классный цикл статей, который дает понимание того, как концептуально функционируют все читы. Для меня этот цикл стал точкой входа в эту предметную область, спасибо!

Но как я понимаю, самое сложное в этой сфере - не само написание читов, а обход античита. Планируются ли статьи по механикам обхода актуальных античитов?
Да все верно. Я бы сказал, что написание Чита да и любой другой малвари можно разделить на две задачи, а именно: реализация функционала софта и обход Систем защиты. Я стараюсь писать статьи по своему плану. Чтобы читателю было легко осваивать информацию. Пока что мы находимся на этапе реализации функций. Разбираемся с базовыми понятиями. Если хватит терпения и сил, то думаю мы пройдемся по всем моментам, так же и про обход систем защиты, и других техник. Подробно все обсудим. В планах еще есть идея запустить цикл статей, но уже с написанием малвари. Так же показать кухню изнутри. Так что следите =)
 
разбираешься в override методиках графики? типо когда условные боксы ресуются поверх экрана(то есть пленочкой). Я будучи при написании глоушки под кс смог реализовать только внутреннее отображение моделек(записывалось на прямую в игру, что изменяло обводку моделек). А как сделать override пока не знаю.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх