![](/uploads/posts/2022-08/game_behaviour.png)
Содержание статьи
Виды читов и применяемые тактики
Существуют разные виды читов. Можно разделить их на несколько групп.
External — внешние читы, которые работают в отдельном процессе. Если же мы скроем наш external-чит, загрузив его в память другого процесса, он превратится в hidden external.
Internal — внутренние читы, которые встраиваются в процесс самой игры при помощи инжектора. После загрузки в память игры в отдельном потоке вызывается точка входа чита.
Pixelscan — вид читов, который использует картинку с экрана и паттерны расположения пикселей, чтобы получить необходимую информацию от игры.
Network proxy — читы, которые используют сетевые прокси, те, в свою очередь, перехватывают трафик клиента и сервера, получая или изменяя необходимую информацию.
Есть три основные тактики модификации поведения игры.
- Изменение памяти игры. API операционной системы используется для поиска и изменения участков памяти, содержащих нужную нам информацию (например, жизни, патроны).
- Симуляция действий игрока: приложение повторяет действия игрока, нажимая мышкой в заранее указанных местах.
- Перехват трафика игры. Между игрой и сервером встает чит. Он перехватывает данные, собирая или изменяя информацию, чтобы обмануть клиент или сервер.
Большинство современных игр написаны для Windows, поэтому и примеры мы будем делать для нее же.
Пишем игру на C
Про читы лучше всего рассказывать на практике. Мы напишем свою небольшую игру, на которой сможем потренироваться. Я буду писать игру на C#, но постараюсь максимально приблизить структуру данных к игре на C++. По моему опыту читерить в играх на C# очень просто.
Принцип игры прост: нажимаешь Enter и проигрываешь. Не особо честные правила, да? Попробуем их изменить.
Приступим к реверс-инжинирингу
![Исполняемый файл игры](/uploads/posts/2022-08/game_executable.png)
У нас есть файл игры. Но вместо исходного кода мы будем изучать память и поведение приложения.
![Начнем с поведения игры](/uploads/posts/2022-08/game_behaviour.png)
При каждом нажатии Enter жизни игрока уменьшаются на 15. Начальное количество жизней — 100.
Изучать память мы будем при помощи Cheat Engine. Это приложение для поиска переменных внутри памяти приложения, а еще хороший дебаггер. Перезапустим игру и подключим к ней Cheat Engine.
![Подключение CE к игре](/uploads/posts/2022-08/ce_connected.png)
Первым делом мы получаем список всех значений 85
в памяти.
![Все значения, которые нашел CE](/uploads/posts/2022-08/ce_found.png)
Нажмем Enter, и показатель жизней будет равен 70
. Отсеем все значения.
![Значение найдено](/uploads/posts/2022-08/ce_final_value_found_1.png)
Вот и нужное значение! Изменим его и нажмем Enter для проверки результата.
![Значение изменено](/uploads/posts/2022-08/ce_value_changed.png)
![Скрин игры, после того как мы нажали Enter](/uploads/posts/2022-08/game_value_changed_1.png)
Проблема в том, что после перезапуска игры значение будет уже по другому адресу. Каждый раз отсеивать его нет никакого смысла. Необходимо прибегнуть к сканированию AOB (Array Of Bytes — массив байтов).
При каждом новом открытии приложения из-за рандомизации адресного пространства (ASLR) структура, описывающая игрока, будет находиться на новом месте. Чтобы найти ее, необходимо сначала обнаружить сигнатуру. Сигнатура — это набор не меняющихся в структуре байтов, по которым можно искать в памяти приложения.
После нескольких нажатий на Enter количество жизней изменилось на 55
. Снова найдем нужное значение в памяти и откроем регион, в котором оно находится.
![Регион памяти](/uploads/posts/2022-08/ce_memory_1.png)
Выделенный байт и есть начало нашего int32
-числа. 37 00 00 00
— число 55
в десятичной форме.
Я скопирую небольшой регион памяти и вставлю в блокнот для дальнейшего изучения. Теперь перезапустим приложение и снова найдем значение в памяти. Снова скопируем такой же регион памяти и вставим в блокнот. Начнем сравнение. Цель — найти байты рядом с этой сигнатурой, которые не будут меняться.
![Начинаем сравнивать байты](/uploads/posts/2022-08/byte_comparsion.png)
Проверим байты перед структурой.
![Бинго!](/uploads/posts/2022-08/byte_comparsion_found.png)
Как видишь, выделенные байты не изменились, значит, можно попробовать использовать их как сигнатуру. Чем меньше сигнатура, тем быстрее пройдет сканирование. Сигнатура 01 00 00 00
явно будет слишком часто встречаться в памяти. Лучше взять 03 00 00 01 00 00 00
. Для начала найдем ее в памяти.
![Сигнатура не уникальна](/uploads/posts/2022-08/ce_signature_not_unique.png)
Сигнатура найдена, но она повторяется. Необходима более уникальная последовательность. Попробуем ED 03 00 00 01 00 00 00
.
В подтверждение уникальности получим такой результат:
![Сигнатура уникальна](/uploads/posts/2022-08/ce_signature_unique.png)
Нам необходимо найти отступ от сигнатуры, чтобы получить ее стартовый адрес, а не адрес жизней. Пока сохраним найденную сигнатуру и отложим на некоторое время. Не беспокойся, мы к ней еще вернемся.
Жизненный цикл external
Используя функцию OpenProcess
, внешние читы получают дескриптор для нужного процесса и вносят необходимые изменения в код (патчинг) или считывают и изменяют переменные внутри памяти игры. Для модификации памяти используются функции ReadProcessMemory
и WriteProcessMemory
.
Так как динамическое размещение данных в памяти мешает записать нужные адреса и постоянно к ним обращаться, можно использовать технику поиска AOB. Жизненный цикл external-чита выглядит так:
- Найти ID процесса.
- Получить дескриптор к этому процессу с нужными правами.
- Найти адреса в памяти.
- Пропатчить что-то, если нужно.
- Отрисовать GUI, если он имеется.
- Считывать или изменять память по мере надобности.
Скачать:
Скриншоты:
Важно:
Все статьи и материал на сайте размещаются из свободных источников. Приносим свои глубочайшие извинения, если Ваша статья или материал была опубликована без Вашего на то согласия.
Напишите нам, и мы в срочном порядке примем меры.