• XSS.stack #1 – первый литературный журнал от юзеров форума

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

Unseen

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


Хо-хо-хо Форумчане! До Нового года остается совсем немного и планировал как можно скорее написать вторую нашу часть про мир геймхакинга! Если статья вышла до НГ, значит я выполнил свою поставленную задачу! А вам пожелаю хорошего чтива 😊

Немного предыстории…

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

Из технической части вспомним, что мы базово прошлись по основам, а именно что из себя представляет оперативная память(ячейки памяти с уникальными адресами для доступа к ним) и что происходит после загрузки туда исполняемой программы операционной системой. Немного разобрались, как и в каком виде хранится информация в современных компьютерных системах(8бит = 1байт).

Коснулись верхушки типов данных, а именно разобрали 1 и 4 байтовые данные. Далее рассмотрим подробно.
Поговорили немного про сканеры памяти и как ими пользоваться. И поэкспериментировали с нашей написанной самими игрой. Произвели поиск и замену значения по найденному адресу в памяти процесса нашей игры при помощи инструмента Cheat Engine.

Начало…

Давайте углубимся в немного в программирование и разберемся с типами данных.

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

Типы данных – это способ хранить данные в памяти. Тип определяет диапазон его возможных значение, список допустимых операций. Как было продемонстрировано в нашей игре, мы хранили значение жизни в 4байтах . Обычно мы можем группировать числа в 1, 2, 4, 8 байтов. То есть, чем больше байтов = тем больше число можем хранить в ячейке(ах). Зная тип данных искомого значения, мы можем легко и правильно сканировать память. Типы данных в программировании определяются с помощью языка программирования. Думаю для примера возьмем язык Си.

В языке программирования C стандартно используются следующие типы данных:

char (символьный тип):
  • Числовой диапазон: -128 до 127 или 0 до 255 (если без знака).
  • Размер: 1 байт.
int (целочисленный тип):
  • Числовой диапазон: зависит от компилятора, обычно -32,768 до 32,767 или 0 до 65,535 (если без знака).
  • Размер: обычно 4 байта, но может варьироваться в зависимости от платформы.
  • Пример: Может хранить количество монет/жизней/патронов
float (вещественный тип с плавающей точкой одинарной точности):
  • Числовой диапазон: примерно от -3.4E38 до 3.4E38.
  • Размер: 4 байта.
  • Пример: Может хранить координаты игрока/врага/предметов и т.д.
double (вещественный тип с плавающей точкой двойной точности):
  • Числовой диапазон: примерно от -1.7E308 до 1.7E308.
  • Размер: 8 байт.
  • Пример: В большинстве случаев нужен для более точных расчетов.
Еще подмечу, если значение в любом из этих типов равно 0, то оно будет считаться не истинным(ложным).

Пример: Игрок находится на земле? Если значение >(больше) 0 то истинна, иначе ложь. Это используется при проверки на различные варианты обработки событий. Допустим есть переменная onGround, которая служит как флаг истинности, которая подразумевает, находится ли игрок на земле или в воздухе? В игре вы делаете прыжок и не можете прыгать до тех пор, пока ваш персонаж не приземлился на землю. Как раз в этот момент игра обрабатывает это все с помощью переменной onGround.

Подробнее можете почитать про типы данных и их модификаторах Здесь

Системы счисления...

При работе с памятью больше всего нам нужно иметь дело с большими числами и для решения этой проблемы была придумана шестнадцатеричная система счисления(HEX).

Шестнадцатеричная система счисления широко используется в программировании и отображении байт кода по нескольким причинам:

Краткость и удобство:
  • Позволяет представлять большие значения байтовой информации (например, адреса памяти) более компактно, чем в двоичной или десятичной системе.
  • Уменьшает количество цифр для представления тех же данных.
Легкость чтения и записи:
  • Шестнадцатеричные числа более легко читать и записывать в сравнении с длинными последовательностями двоичных цифр.
  • Переход между двоичной и шестнадцатеричной системами более интуитивен.
Удобство при работе с байтами:

Байт, в основном, представляет собой 8 бит. Шестнадцатеричная система прекрасно сочетается с байтами, так как каждая цифра шестнадцатеричной системы представляет 4 бита (16 возможных значений).

Пример:

В двоичной системе:

Байт 10101010 может быть представлен в шестнадцатеричной системе как AA.

Таким образом, шестнадцатеричная система обеспечивает компромисс между компактностью представления данных и удобочитаемостью, что делает ее удобной для использования в программировании и анализе байтового кода.
Более подробно можете прочитать Здесь

Шестнадцатеричная начинается с 0x, чтобы сообщить компилятору что это hex формат.
Например: 0x123456 0xABCDEF 0x123ABC

Разновидность...

Теперь когда разобрались с базовыми моментами из программирования, думаю стоит поговорить про виды софта.

Их в основном два вида, а именно External и Internal. Различия у этих читов:

External – в переводе на русский, означает “Внешний”. Extrenal чит являясь исполняемой программой, может из вне читать/изменять память игры при помощи запросов(WinApi) к ОС. Данный тип читов труднее обнаружить из-за своего нахождения вне процесса игры. Тут очень важно, записываете ли вы что то в память игры? Если да, то плохо, если нет, то такой чит гораздо труднее обнаружить. Из-за того, что чит находится вне процесса игры , он будет ограничен по своему функционалу. Ведь мы не можем всегда напрямую перезаписывать данные в памяти игры или вызывать какие либо функции в самой игре, что сказывается на скорости чита.

Internal — в переводится, как “Внутренний”. Это код, чаще всего в виде динамической библиотеки – dll. С помощью инжектора внедряется/загружается в адресное пространство процесса игры. И может напрямую читать и редактировать данные в памяти процесса, обращаться к переменным и функциям. Реализовать чуть сложнее, чем External. В связи со своей активностью внутри памяти процесса игры, относительно легко обнаруживать. Из плюсов чит работает намного быстрее и в плане функций имеет широкий выбор.

Практическая часть…

В этой статье мы сегодня базово разберем первый вид читов - External.
Поработаем с запросами(WinApi) к ОС для доступа к процессу и далее чтение данных/запись в память процесса.
Языком программирования нашего софта будет С++.

Подготовка среды…

Для начала вам нужно скачать среду разработки(IDE) Visual Studio Скачать
Выбираем Visual Studio Community и нажимаем скачать.

Подробная видео инструкция установки С++ в IDE Ютуб

Если вы все сделали правильно, запускаем Visual Studio.

Нас встречает такое окошк:
Выбираем "Создание проекта" и создать.

1.png

Здесь выбираем «Консольное приложение» Далее
2.png

Даем название проекту, можете любой, главное чтобы имя несло смысловую нагрузку =)
3.png

После создания проекта, удаляем весь код из исходного файла.
4.png

Далее нажимаете на сочетание клавиш Alt + F7 или же в меню IDE - > Проект -> Свойство: имя вашего проекта -> Дополнительно -> Набор символов -> Использовать многобайтовую кодировку.

6.png

Кодинг…

Сразу скажу, что мы не будем сильно разбирать синтаксис языка, пройдемся по базовым моментам. Изучить синтаксис думаю не составит труда. В гугле все есть в открытом доступе. Все же статья не про основы программирования =)

Вообще в идеале наш план будет таков:
  • Найти нужный нам процесс(Game.exe) при помощи названия окна.(Надеюсь не забыли, что из себя представляет процесс? Если забыли, то бегом перечитывать 0x1 часть)
  • При помощи запроса к ОС предоставить нашей программе(Cheat.exe) полный доступ к памяти целевого процесса(Game.exe)
  • На этом этапе мы уже можем читать и записывать новые данные по адресам целевого процесса функциями из winapi.
5.png

Как видите вся работа происходит через WinApi. То есть мы будем буквально просить ОС сделать для нас что-то. Далее увидите сами.

У нас имеется четкий план, теперь осталось реализовать.

Весь исходный код нашего софта:
C++:
/*
    Author Unseen
    Source xss.pro
*/
#include <iostream>

/*
специфичный заголовочный файл, в котором объявляются функции,
предоставляющие интерфейс доступа к Windows API
*/
#include <Windows.h>

/*
Заголовочный файл в Windows API, который предоставляет набор функций
для работы с информацией о процессах и потоках.
Он используется для выполнения операций, связанных с манипуляцией процессов и их состояний,
в том числе получения списка запущенных процессов и
информации о модулях в адресном пространстве процесса.
*/
#include <TlHelp32.h>

using namespace std;

/*Глобальные переменные, чтобы работать с ними, где угодно в коде*/
HANDLE targetProc = NULL;
HWND targetWindow = NULL;
DWORD pID = 0;

void GetAccessProcess(char* targetName)
{
    targetWindow = FindWindowA(0, targetName);
    if (targetWindow == NULL)
    {
        cout << "Не удалось найти окно игры!" << endl;
    }
    else
    {
        GetWindowThreadProcessId(targetWindow, &pID);
        if (pID == 0)
        {
            cout << "Не удалось получить идентификатор процесса!" << endl;
        }
        else
        {
            targetProc = OpenProcess(PROCESS_ALL_ACCESS, false, pID);

            if (!targetProc)
            {
                cout << "Не удалось открыть процесс!" << endl;
            }
           

        }
    }

}

template<class dataType>
void memWrite(DWORD address, dataType value)
{
    WriteProcessMemory(targetProc, (PVOID)address, &value, sizeof(dataType), 0);
}

template <class dataType>
dataType memRead(DWORD address)
{
    dataType buffer;
    ReadProcessMemory(targetProc, (PVOID)address, &buffer, sizeof(dataType), 0);
    return buffer;
}

int main() {
   
    setlocale(LC_ALL, "Russian");
   
    GetAccessProcess((char*)"Название_Окна_Игры");

    DWORD adresses_ammo = 0xВставьте найденный адрес;

    cout << "Патронов было: " << memRead<int>(adresses_ammo) << endl;

    memWrite<int>(adresses_ammo, 100);

    cout << "Патронов стало: " << memRead<int>(adresses_ammo) << endl;
    CloseHandle(targetProc);

    cin.get();
    return 0;
}

Давайте теперь разберемся с кодом:
Для чего нужны библиотеки и заголовочные файлы:
  • iostream - Стандартная библиотека для работы с потоками ввода и вывода С++
  • Windows.h - специфичный заголовочный файл, в котором объявляются функции, предоставляющие интерфейс доступа к Windows API.
  • TlHelp32.h - заголовочный файл в Windows API, который предоставляет набор функций для работы с информацией о процессах и потоках. Он используется для выполнения операций, связанных с манипуляцией процессов и их состояний, в том числе получения списка запущенных процессов и информации о модулях в адресном пространстве процесса.
Теперь определим три глобальные переменные:
C++:
HANDLE targetProc = NULL;
HWND targetWindow = NULL;
DWORD pID = 0;
  • targetProc – будет хранить дескриптор целевого процесса. С помощью него мы будем обращаться к процессу.
  • targetWindow – будет хранить дескриптор окна процесса.
  • pID – будем хранить идентификатор целевого процесса. Он используется для управления процессами, отслеживания их состояния и взаимодействия с ними.
Сразу запишем в них пустые значения, это понадобится в дальнейших проверках.
Мы определили их глобальными, чтобы получать доступ к ним из любой точки нашего кода.

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

Давайте эти действия вынесем в отдельную функцию и назовем ее GetAccessProcess. Она будет принимать название окна процесса. P.S. Названия для переменных и функций давайте логичные, чтобы вам было понятно для чего они служит.

Хорошо, теперь при помощи функции из WinApi - FindWindowA найдем дескриптор окна по его названию и сохраним в нашу переменную targetWindow.
Документация по функции FindWindowA из MSDN.
C++:
targetWindow = FindWindowA(0, targetName);

Теперь сделаем проверку об успешном получении дескриптора окна:

C++:
if (targetWindow == NULL)
{
    cout << "Не удалось найти окно игры!" << endl;
}

Если успешно смогли получить дескриптор окна приступаем к получению идентификатора(PID) процесса, для этого есть функция GetWindowThreadProcessId.
Документация по GetWindowThreadProcessId из MSDN.
C++:
GetWindowThreadProcessId(targetWindow, &pID);

Полученное значение записывается в переменную pID.
Далее так же нужно сделать проверку:
C++:
if (pID == 0)
{
    cout << "Не удалось получить идентификатор процесса!" << endl;
}

При успешном получении идентификатора нам остается последний шаг - это получить права доступа для работы с целевым процессом. Делается это при помощи функции OpenProcess и значение записывается в targetProc.
Документация по OpenProcess из MSDN.
C++:
targetProc = OpenProcess(PROCESS_ALL_ACCESS, false, pID);

Делаем так же проверку, что права доступа получены:
C++:
if (!targetProc)
{
    cout << "Не удалось получить права доступа!" << endl;
}

При успешном выполнении, мы получим полный доступ для работы с процессом. Имея доступ мы можем наконец взаимодействовать с памятью процесса, для чего и были сделаны все описанные выше действия.

Еще отмечу, чтобы было удобно нам работать с функциями чтения и записи в память, предлагаю создать шаблоны функций для этого. Шаблоны функций (function template) позволяют определять функции, которые не зависят от конкретных типов. Ведь в памяти могут храниться разные типы данных. Это нам сильно облегчит жизнь =) Создадим два шаблона функций memWrite(Запись в память) и memRead(Чтение из памяти).

Шаблон функции memWrite для записи в память:
C++:
template<class dataType>
void memWrite(DWORD address, dataType value)
{
    WriteProcessMemory(hTargetProc, (PVOID)address, &value, sizeof(dataType), 0);
}

Функция memWrite, принимает адрес и значение, которое нужно записать по данному адресу в память. Сама запись происходит при помощи функции из WinApi - WriteProcessMemory.

Шаблон функции memRead для чтения из памяти:
C++:
template <class dataType>
dataType memRead(DWORD address)
{
    dataType buffer;
    ReadProcessMemory(hTargetProc, (PVOID)address, &buffer, sizeof(dataType), 0);
    return buffer;
}
Функция memRead, будет принимать адрес из которого нужно прочитать данные. Прочитанные данные записываются в переменную buffer и возвращаются функцией туда, откуда вызвали memRead. Чтение происходит при помощи функции из WinApi – ReadProcessMemory.

Важную часть мы уже проделали и осталось совсем мало штрихов =)

Далее идет главная функция main нашей программы.
В ней имеется следующее:
Это нужно, если вы работаете с кириллицей в консоли программы. Попробуйте убрать эту строчку кода и сразу поймете, к чему я =)
C++:
setlocale(LC_ALL, "Russian");

C++:
GetAccessProcess((char*)"Название_Окна_Игры");
В ней мы вызываем созданную выше функцию GetAccessProcess и передаем название окна игры. После получения прав доступа к процессу нам нужно проверить, как все работает.

Для этого возьмите любую игру(желательно оффлайн, ибо в онлайн играх есть шанс получения бана, а про обходы АнтиЧитов пока мы не проходили)
Для примера я взял AssaultCube. Поэкспериментируйте с несколькими играми. Практика, практика и только практика!

Запускаем игру и делаем оконный режим, чтобы было удобно.
7.png
Значит вызов GetAccessProcess и передача в нее имени окна будет следующим:
C++:
GetAccessProcess((char*)"AssaultCube");

Теперь в целях эксперимента, попробуем найти адрес патронов.
Открываем наш любимый сканер Cheat Engine и полученными знаниями из предыдущей части части [0x1] находим нужный нам адрес.

Скорее всего, у вас найдется не меньше двух адресов, связанно это из-за того, что в одном адресе хранится само значение патронов, а в другой значение, которое отображается в интерфейсе игры.
8.png

Попробуйте поэкспериментировать и найти адрес самих патронов, а не адрес интерфейса.
У меня этим адресом оказался 008F2268;

Дальше мы создаем переменную address_ammo и сохраняем в ней найденный адрес 008F2268:
C++:
DWORD address_ammo = 0x008F2268;
DWORD address_ammo = 0x008F2268

Не забываем про 0x перед 16-ым числом.

А теперь настал момент Х =)

Вызываем функцию memRead<int>(address_ammo) и передаем в качестве аргумента переменную.

Заметим, <int> означает, что мы читаем из памяти значение с типом 4 байта. Ведь наше значение патронов целочисленные. Если бы мы хотели вывести (пример*) координаты игрока, то использовали бы float или double.
Попробуем произвести чтение по этому адресу и вывести значение в консоль.
C++:
cout << "Патронов было: " << memRead<int>(address_ammo) << endl;

Хорошо, теперь запишем какое-нибудь новое значение в память при помощи функции memWrite.
C++:
memWrite<int>(address_ammo, 100);
Передаем в функцию адрес и значение, которое нужно записать, например 100.

И снова при помощи memRead прочитаем, чтобы проверить изменилось ли значение.
9.png

Да это сработало!
Мы сначала прочитали значение до записи и после, наша программа смогла произвести чтение и запись. Задача выполнена!
Поработав с процессом мы должны закрыть ее хэндл : CloseHandle(targetProc);
Но у нас все еще осталась одна проблема с адресами. После перезапуска они все еще меняются. В этой части мы не смогли обсудить виртуальную память и смещения в памяти.
Статья итак вышла объемной и тема виртуальной памяти/смещений было бы трудно обсудить сразу. Думаю сделаем это уже в след. части.

Сегодня мы смогли написать основу(скелет) нашего будущего софта. В будущих статьях будем реализовывать новые функции и фичи.

Еще добавлю для совсем зеленых в программировании, как и в предыдущей части:
- Если еще не начали изучать какой-либо язык, то рекомендую сделать это как можно скорее.
- Всегда экспериментируйте! Если что-то не получается, то вы всегда можете спросить на этом чудесном форуме!

Просьба к профешионал кодерам и знатокам, попросил бы Вас дополнять или исправлять моменты, которые я мог пропустить. Таким образом вы сделаете вклад в развитие =)

Всем добра и до следующей части!
 
Статья хорошая, запишите статью по дота 2, потому что там все намного веселее.
Не переживайте! Все разберем😉
 
Статья вышла объемной ) Все прям разжевано для самых маленьких, я как человек который терпеть не может теорию, прям очень душно почувствовалось, думаю я не один такой.
Можно было бы много ненужного текста вывалить, для мотивации сразу в начале показать или написать то что должно выйти в итоге под конец статьи, так было бы сразу понятно что будет статья описывать и тд. Ну это чисто мои такие тараканы )

Могу добавить от себя на счет классификации читов(external, internal) для тех кто заинтересовался этой темой, надо помнить о том что используя external чит, *можно* довести его до скорости internal читов, но быстрее сделать не получится. Да еще миф о том что экстерналы прям сразу лучше интерналов в плане детектов, это бред, все зависит от того какой античит, есть те которые любой экстернал сразу детектят, а вот почти любой экстернал пропускают, так и наоборот, все зависит от того как именно ты написал интернал, какой метод инжекта в процесс использовал и так далее. Вообще если так сказать чтобы понятнее было, любой обычный экстернал с винапи callами засечь просто в миг, а вот обычный интернал труднее. Также я бы добавил что я недавно писал в каком то ответе, что классификация читов в последнее время как по мне умирает, DMA, KVM с виртуальной машиной, Ring0 читы, их куда мы относим? все в экстернал? или новый подраздел для каждого? два месяца назад я писал чит для обхода одной лиги, там использовалось 2 разных kernel ring0 драйвера параллельно, также общий юзермод и обычный экзешник который читал несколько данных из памяти. получается это тот же экстернал? обидно даже такое было осознавать. Давным давно в нулевые кто-то придумал такое понятие, когда в принципе не было вообще читов кроме традиционных экстернал и интернал.

Жду в след статье/статьях нахождение статичного адреса к значению через добавление оффсета к адресам модулей в памяти процесса, как это и делают во всех современных читах :)
 
автор молодец. отправ бтц кош в пм а лучше закрепи его в подписи.
Благодарю за вашу поддержку :) Написал вам в ПМ.
 
Автор: UNSEEN
Источник: https://xss.pro


Хо-хо-хо Форумчане! До Нового года остается совсем немного и планировал как можно скорее написать вторую нашу часть про мир геймхакинга! Если статья вышла до НГ, значит я выполнил свою поставленную задачу! А вам пожелаю хорошего чтива 😊

Немного предыстории…

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

Из технической части вспомним, что мы базово прошлись по основам, а именно что из себя представляет оперативная память(ячейки памяти с уникальными адресами для доступа к ним) и что происходит после загрузки туда исполняемой программы операционной системой. Немного разобрались, как и в каком виде хранится информация в современных компьютерных системах(8бит = 1байт).

Коснулись верхушки типов данных, а именно разобрали 1 и 4 байтовые данные. Далее рассмотрим подробно.
Поговорили немного про сканеры памяти и как ими пользоваться. И поэкспериментировали с нашей написанной самими игрой. Произвели поиск и замену значения по найденному адресу в памяти процесса нашей игры при помощи инструмента Cheat Engine.

Начало…

Давайте углубимся в немного в программирование и разберемся с типами данных.

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

Типы данных – это способ хранить данные в памяти. Тип определяет диапазон его возможных значение, список допустимых операций. Как было продемонстрировано в нашей игре, мы хранили значение жизни в 4байтах . Обычно мы можем группировать числа в 1, 2, 4, 8 байтов. То есть, чем больше байтов = тем больше число можем хранить в ячейке(ах). Зная тип данных искомого значения, мы можем легко и правильно сканировать память. Типы данных в программировании определяются с помощью языка программирования. Думаю для примера возьмем язык Си.

В языке программирования C стандартно используются следующие типы данных:

char (символьный тип):
  • Числовой диапазон: -128 до 127 или 0 до 255 (если без знака).
  • Размер: 1 байт.
int (целочисленный тип):
  • Числовой диапазон: зависит от компилятора, обычно -32,768 до 32,767 или 0 до 65,535 (если без знака).
  • Размер: обычно 4 байта, но может варьироваться в зависимости от платформы.
  • Пример: Может хранить количество монет/жизней/патронов
float (вещественный тип с плавающей точкой одинарной точности):
  • Числовой диапазон: примерно от -3.4E38 до 3.4E38.
  • Размер: 4 байта.
  • Пример: Может хранить координаты игрока/врага/предметов и т.д.
double (вещественный тип с плавающей точкой двойной точности):
  • Числовой диапазон: примерно от -1.7E308 до 1.7E308.
  • Размер: 8 байт.
  • Пример: В большинстве случаев нужен для более точных расчетов.
Еще подмечу, если значение в любом из этих типов равно 0, то оно будет считаться не истинным(ложным).

Пример: Игрок находится на земле? Если значение >(больше) 0 то истинна, иначе ложь. Это используется при проверки на различные варианты обработки событий. Допустим есть переменная onGround, которая служит как флаг истинности, которая подразумевает, находится ли игрок на земле или в воздухе? В игре вы делаете прыжок и не можете прыгать до тех пор, пока ваш персонаж не приземлился на землю. Как раз в этот момент игра обрабатывает это все с помощью переменной onGround.

Подробнее можете почитать про типы данных и их модификаторах Здесь

Системы счисления...

При работе с памятью больше всего нам нужно иметь дело с большими числами и для решения этой проблемы была придумана шестнадцатеричная система счисления(HEX).

Шестнадцатеричная система счисления широко используется в программировании и отображении байт кода по нескольким причинам:

Краткость и удобство:
  • Позволяет представлять большие значения байтовой информации (например, адреса памяти) более компактно, чем в двоичной или десятичной системе.
  • Уменьшает количество цифр для представления тех же данных.
Легкость чтения и записи:
  • Шестнадцатеричные числа более легко читать и записывать в сравнении с длинными последовательностями двоичных цифр.
  • Переход между двоичной и шестнадцатеричной системами более интуитивен.
Удобство при работе с байтами:

Байт, в основном, представляет собой 8 бит. Шестнадцатеричная система прекрасно сочетается с байтами, так как каждая цифра шестнадцатеричной системы представляет 4 бита (16 возможных значений).

Пример:

В двоичной системе:

Байт 10101010 может быть представлен в шестнадцатеричной системе как AA.

Таким образом, шестнадцатеричная система обеспечивает компромисс между компактностью представления данных и удобочитаемостью, что делает ее удобной для использования в программировании и анализе байтового кода.
Более подробно можете прочитать Здесь

Шестнадцатеричная начинается с 0x, чтобы сообщить компилятору что это hex формат.
Например: 0x123456 0xABCDEF 0x123ABC

Разновидность...

Теперь когда разобрались с базовыми моментами из программирования, думаю стоит поговорить про виды софта.

Их в основном два вида, а именно External и Internal. Различия у этих читов:

External – в переводе на русский, означает “Внешний”. Extrenal чит являясь исполняемой программой, может из вне читать/изменять память игры при помощи запросов(WinApi) к ОС. Данный тип читов труднее обнаружить из-за своего нахождения вне процесса игры. Тут очень важно, записываете ли вы что то в память игры? Если да, то плохо, если нет, то такой чит гораздо труднее обнаружить. Из-за того, что чит находится вне процесса игры , он будет ограничен по своему функционалу. Ведь мы не можем всегда напрямую перезаписывать данные в памяти игры или вызывать какие либо функции в самой игре, что сказывается на скорости чита.

Internal — в переводится, как “Внутренний”. Это код, чаще всего в виде динамической библиотеки – dll. С помощью инжектора внедряется/загружается в адресное пространство процесса игры. И может напрямую читать и редактировать данные в памяти процесса, обращаться к переменным и функциям. Реализовать чуть сложнее, чем External. В связи со своей активностью внутри памяти процесса игры, относительно легко обнаруживать. Из плюсов чит работает намного быстрее и в плане функций имеет широкий выбор.

Практическая часть…

В этой статье мы сегодня базово разберем первый вид читов - External.
Поработаем с запросами(WinApi) к ОС для доступа к процессу и далее чтение данных/запись в память процесса.
Языком программирования нашего софта будет С++.

Подготовка среды…

Для начала вам нужно скачать среду разработки(IDE) Visual Studio Скачать
Выбираем Visual Studio Community и нажимаем скачать.

Подробная видео инструкция установки С++ в IDE Ютуб

Если вы все сделали правильно, запускаем Visual Studio.

Нас встречает такое окошк:
Выбираем "Создание проекта" и создать.


Здесь выбираем «Консольное приложение» Далее

Даем название проекту, можете любой, главное чтобы имя несло смысловую нагрузку =)

После создания проекта, удаляем весь код из исходного файла.

Далее нажимаете на сочетание клавиш Alt + F7 или же в меню IDE - > Проект -> Свойство: имя вашего проекта -> Дополнительно -> Набор символов -> Использовать многобайтовую кодировку.


Кодинг…

Сразу скажу, что мы не будем сильно разбирать синтаксис языка, пройдемся по базовым моментам. Изучить синтаксис думаю не составит труда. В гугле все есть в открытом доступе. Все же статья не про основы программирования =)

Вообще в идеале наш план будет таков:
  • Найти нужный нам процесс(Game.exe) при помощи названия окна.(Надеюсь не забыли, что из себя представляет процесс? Если забыли, то бегом перечитывать 0x1 часть)
  • При помощи запроса к ОС предоставить нашей программе(Cheat.exe) полный доступ к памяти целевого процесса(Game.exe)
  • На этом этапе мы уже можем читать и записывать новые данные по адресам целевого процесса функциями из winapi.

Как видите вся работа происходит через WinApi. То есть мы будем буквально просить ОС сделать для нас что-то. Далее увидите сами.

У нас имеется четкий план, теперь осталось реализовать.

Весь исходный код нашего софта:
C++:
/*
    Author Unseen
    Source xss.pro
*/
#include <iostream>

/*
специфичный заголовочный файл, в котором объявляются функции,
предоставляющие интерфейс доступа к Windows API
*/
#include <Windows.h>

/*
Заголовочный файл в Windows API, который предоставляет набор функций
для работы с информацией о процессах и потоках.
Он используется для выполнения операций, связанных с манипуляцией процессов и их состояний,
в том числе получения списка запущенных процессов и
информации о модулях в адресном пространстве процесса.
*/
#include <TlHelp32.h>

using namespace std;

/*Глобальные переменные, чтобы работать с ними, где угодно в коде*/
HANDLE targetProc = NULL;
HWND targetWindow = NULL;
DWORD pID = 0;

void GetAccessProcess(char* targetName)
{
    targetWindow = FindWindowA(0, targetName);
    if (targetWindow == NULL)
    {
        cout << "Не удалось найти окно игры!" << endl;
    }
    else
    {
        GetWindowThreadProcessId(targetWindow, &pID);
        if (pID == 0)
        {
            cout << "Не удалось получить идентификатор процесса!" << endl;
        }
        else
        {
            targetProc = OpenProcess(PROCESS_ALL_ACCESS, false, pID);

            if (!targetProc)
            {
                cout << "Не удалось открыть процесс!" << endl;
            }
          

        }
    }

}

template<class dataType>
void memWrite(DWORD address, dataType value)
{
    WriteProcessMemory(targetProc, (PVOID)address, &value, sizeof(dataType), 0);
}

template <class dataType>
dataType memRead(DWORD address)
{
    dataType buffer;
    ReadProcessMemory(targetProc, (PVOID)address, &buffer, sizeof(dataType), 0);
    return buffer;
}

int main() {
  
    setlocale(LC_ALL, "Russian");
  
    GetAccessProcess((char*)"Название_Окна_Игры");

    DWORD adresses_ammo = 0xВставьте найденный адрес;

    cout << "Патронов было: " << memRead<int>(adresses_ammo) << endl;

    memWrite<int>(adresses_ammo, 100);

    cout << "Патронов стало: " << memRead<int>(adresses_ammo) << endl;
    CloseHandle(targetProc);

    cin.get();
    return 0;
}

Давайте теперь разберемся с кодом:
Для чего нужны библиотеки и заголовочные файлы:
  • iostream - Стандартная библиотека для работы с потоками ввода и вывода С++
  • Windows.h - специфичный заголовочный файл, в котором объявляются функции, предоставляющие интерфейс доступа к Windows API.
  • TlHelp32.h - заголовочный файл в Windows API, который предоставляет набор функций для работы с информацией о процессах и потоках. Он используется для выполнения операций, связанных с манипуляцией процессов и их состояний, в том числе получения списка запущенных процессов и информации о модулях в адресном пространстве процесса.
Теперь определим три глобальные переменные:
C++:
HANDLE targetProc = NULL;
HWND targetWindow = NULL;
DWORD pID = 0;
  • targetProc – будет хранить дескриптор целевого процесса. С помощью него мы будем обращаться к процессу.
  • targetWindow – будет хранить дескриптор окна процесса.
  • pID – будем хранить идентификатор целевого процесса. Он используется для управления процессами, отслеживания их состояния и взаимодействия с ними.
Сразу запишем в них пустые значения, это понадобится в дальнейших проверках.
Мы определили их глобальными, чтобы получать доступ к ним из любой точки нашего кода.

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

Давайте эти действия вынесем в отдельную функцию и назовем ее GetAccessProcess. Она будет принимать название окна процесса. P.S. Названия для переменных и функций давайте логичные, чтобы вам было понятно для чего они служит.

Хорошо, теперь при помощи функции из WinApi - FindWindowA найдем дескриптор окна по его названию и сохраним в нашу переменную targetWindow.
Документация по функции FindWindowA из MSDN.
C++:
targetWindow = FindWindowA(0, targetName);

Теперь сделаем проверку об успешном получении дескриптора окна:

C++:
if (targetWindow == NULL)
{
    cout << "Не удалось найти окно игры!" << endl;
}

Если успешно смогли получить дескриптор окна приступаем к получению идентификатора(PID) процесса, для этого есть функция GetWindowThreadProcessId.
Документация по GetWindowThreadProcessId из MSDN.
C++:
GetWindowThreadProcessId(targetWindow, &pID);

Полученное значение записывается в переменную pID.
Далее так же нужно сделать проверку:
C++:
if (pID == 0)
{
    cout << "Не удалось получить идентификатор процесса!" << endl;
}

При успешном получении идентификатора нам остается последний шаг - это получить права доступа для работы с целевым процессом. Делается это при помощи функции OpenProcess и значение записывается в targetProc.
Документация по OpenProcess из MSDN.
C++:
targetProc = OpenProcess(PROCESS_ALL_ACCESS, false, pID);

Делаем так же проверку, что права доступа получены:
C++:
if (!targetProc)
{
    cout << "Не удалось получить права доступа!" << endl;
}

При успешном выполнении, мы получим полный доступ для работы с процессом. Имея доступ мы можем наконец взаимодействовать с памятью процесса, для чего и были сделаны все описанные выше действия.

Еще отмечу, чтобы было удобно нам работать с функциями чтения и записи в память, предлагаю создать шаблоны функций для этого. Шаблоны функций (function template) позволяют определять функции, которые не зависят от конкретных типов. Ведь в памяти могут храниться разные типы данных. Это нам сильно облегчит жизнь =) Создадим два шаблона функций memWrite(Запись в память) и memRead(Чтение из памяти).

Шаблон функции memWrite для записи в память:
C++:
template<class dataType>
void memWrite(DWORD address, dataType value)
{
    WriteProcessMemory(hTargetProc, (PVOID)address, &value, sizeof(dataType), 0);
}

Функция memWrite, принимает адрес и значение, которое нужно записать по данному адресу в память. Сама запись происходит при помощи функции из WinApi - WriteProcessMemory.

Шаблон функции memRead для чтения из памяти:
C++:
template <class dataType>
dataType memRead(DWORD address)
{
    dataType buffer;
    ReadProcessMemory(hTargetProc, (PVOID)address, &buffer, sizeof(dataType), 0);
    return buffer;
}
Функция memRead, будет принимать адрес из которого нужно прочитать данные. Прочитанные данные записываются в переменную buffer и возвращаются функцией туда, откуда вызвали memRead. Чтение происходит при помощи функции из WinApi – ReadProcessMemory.

Важную часть мы уже проделали и осталось совсем мало штрихов =)

Далее идет главная функция main нашей программы.
В ней имеется следующее:
Это нужно, если вы работаете с кириллицей в консоли программы. Попробуйте убрать эту строчку кода и сразу поймете, к чему я =)
C++:
setlocale(LC_ALL, "Russian");

C++:
GetAccessProcess((char*)"Название_Окна_Игры");
В ней мы вызываем созданную выше функцию GetAccessProcess и передаем название окна игры. После получения прав доступа к процессу нам нужно проверить, как все работает.

Для этого возьмите любую игру(желательно оффлайн, ибо в онлайн играх есть шанс получения бана, а про обходы АнтиЧитов пока мы не проходили)
Для примера я взял AssaultCube. Поэкспериментируйте с несколькими играми. Практика, практика и только практика!

Запускаем игру и делаем оконный режим, чтобы было удобно.
Значит вызов GetAccessProcess и передача в нее имени окна будет следующим:
C++:
GetAccessProcess((char*)"AssaultCube");

Теперь в целях эксперимента, попробуем найти адрес патронов.
Открываем наш любимый сканер Cheat Engine и полученными знаниями из предыдущей части части [0x1] находим нужный нам адрес.

Скорее всего, у вас найдется не меньше двух адресов, связанно это из-за того, что в одном адресе хранится само значение патронов, а в другой значение, которое отображается в интерфейсе игры.

Попробуйте поэкспериментировать и найти адрес самих патронов, а не адрес интерфейса.
У меня этим адресом оказался 008F2268;

Дальше мы создаем переменную address_ammo и сохраняем в ней найденный адрес 008F2268:
C++:
DWORD address_ammo = 0x008F2268;
DWORD address_ammo = 0x008F2268

Не забываем про 0x перед 16-ым числом.

А теперь настал момент Х =)

Вызываем функцию memRead<int>(address_ammo) и передаем в качестве аргумента переменную.

Заметим, <int> означает, что мы читаем из памяти значение с типом 4 байта. Ведь наше значение патронов целочисленные. Если бы мы хотели вывести (пример*) координаты игрока, то использовали бы float или double.
Попробуем произвести чтение по этому адресу и вывести значение в консоль.
C++:
cout << "Патронов было: " << memRead<int>(address_ammo) << endl;

Хорошо, теперь запишем какое-нибудь новое значение в память при помощи функции memWrite.
C++:
memWrite<int>(address_ammo, 100);
Передаем в функцию адрес и значение, которое нужно записать, например 100.

И снова при помощи memRead прочитаем, чтобы проверить изменилось ли значение.

Да это сработало!
Мы сначала прочитали значение до записи и после, наша программа смогла произвести чтение и запись. Задача выполнена!
Поработав с процессом мы должны закрыть ее хэндл : CloseHandle(targetProc);
Но у нас все еще осталась одна проблема с адресами. После перезапуска они все еще меняются. В этой части мы не смогли обсудить виртуальную память и смещения в памяти.
Статья итак вышла объемной и тема виртуальной памяти/смещений было бы трудно обсудить сразу. Думаю сделаем это уже в след. части.

Сегодня мы смогли написать основу(скелет) нашего будущего софта. В будущих статьях будем реализовывать новые функции и фичи.

Еще добавлю для совсем зеленых в программировании, как и в предыдущей части:
- Если еще не начали изучать какой-либо язык, то рекомендую сделать это как можно скорее.
- Всегда экспериментируйте! Если что-то не получается, то вы всегда можете спросить на этом чудесном форуме!

Просьба к профешионал кодерам и знатокам, попросил бы Вас дополнять или исправлять моменты, которые я мог пропустить. Таким образом вы сделаете вклад в развитие =)

Всем добра и до следующей части!
Подскажите пожалуйста этим способом можно сделать чит для игры Arma 3 по сети , чтоб античит не палил !??!
 
Подскажите пожалуйста этим способом можно сделать чит для игры Arma 3 по сети , чтоб античит не палил !??!
Не совсем =)
Тут уже нужно писать библиотеку и способ инжекта искать.
 
Не совсем =)
Тут уже нужно писать библиотеку и способ инжекта искать.
бро вообще какие знания нужны чтоб самому сделать чит для армы 3 по сети !?
 
бро вообще какие знания нужны чтоб самому сделать чит для армы 3 по сети !?
Привет! Пока не дошли до инжектов в процесс, но дам направление куда копать. Почитай про создание читов в виде dll либ. И для обхода инжектить в процесс игры при помощи метода manual map inject`ором.
 


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