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

Виды читов и применяемые тактики

Существуют разные виды читов. Можно разделить их на несколько групп.

  • External — внешние читы, которые работают в отдельном процессе. Если же мы скроем наш external-чит, загрузив его в память другого процесса, он превратится в hidden external.

  • Internal — внутренние читы, которые встраиваются в процесс самой игры при помощи инжектора. После загрузки в память игры в отдельном потоке вызывается точка входа чита.

  • Pixelscan — вид читов, который использует картинку с экрана и паттерны расположения пикселей, чтобы получить необходимую информацию от игры.

  • Network proxy — читы, которые используют сетевые прокси, те, в свою очередь, перехватывают трафик клиента и сервера, получая или изменяя необходимую информацию.

Есть три основные тактики модификации поведения игры.

  1. Изменение памяти игры. API операционной системы используется для поиска и изменения участков памяти, содержащих нужную нам информацию (например, жизни, патроны).
  2. Симуляция действий игрока: приложение повторяет действия игрока, нажимая мышкой в заранее указанных местах.
  3. Перехват трафика игры. Между игрой и сервером встает чит. Он перехватывает данные, собирая или изменяя информацию, чтобы обмануть клиент или сервер.

Большинство современных игр написаны для Windows, поэтому и примеры мы будем делать для нее же.

 

Пишем игру на C

Про читы лучше всего рассказывать на практике. Мы напишем свою небольшую игру, на которой сможем потренироваться. Я буду писать игру на C#, но постараюсь максимально приблизить структуру данных к игре на C++. По моему опыту читерить в играх на C# очень просто.

Принцип игры прост: нажимаешь Enter и проигрываешь. Не особо честные правила, да? Попробуем их изменить.

 

Приступим к реверс-инжинирингу

Исполняемый файл игры
Исполняемый файл игры

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

Начнем с поведения игры
Начнем с поведения игры

При каждом нажатии Enter жизни игрока уменьшаются на 15. Начальное количество жизней — 100.

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

Подключение CE к игре
Подключение CE к игре

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

Все значения, которые нашел CE
Все значения, которые нашел CE

Нажмем Enter, и показатель жизней будет равен 70. Отсеем все значения.

Значение найдено
Значение найдено

Вот и нужное значение! Изменим его и нажмем Enter для проверки результата.

Значение изменено
Значение изменено
Скрин игры, после того как мы нажали Enter
Скрин игры, после того как мы нажали Enter

Проблема в том, что после перезапуска игры значение будет уже по другому адресу. Каждый раз отсеивать его нет никакого смысла. Необходимо прибегнуть к сканированию AOB (Array Of Bytes — массив байтов).

При каждом новом открытии приложения из-за рандомизации адресного пространства (ASLR) структура, описывающая игрока, будет находиться на новом месте. Чтобы найти ее, необходимо сначала обнаружить сигнатуру. Сигнатура — это набор не меняющихся в структуре байтов, по которым можно искать в памяти приложения.

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

Регион памяти
Регион памяти

Выделенный байт и есть начало нашего int32-числа. 37 00 00 00 — число 55 в десятичной форме.

Я скопирую небольшой регион памяти и вставлю в блокнот для дальнейшего изучения. Теперь перезапустим приложение и снова найдем значение в памяти. Снова скопируем такой же регион памяти и вставим в блокнот. Начнем сравнение. Цель — найти байты рядом с этой сигнатурой, которые не будут меняться.

Начинаем сравнивать байты
Начинаем сравнивать байты

Проверим байты перед структурой.

Бинго!
Бинго!

Как видишь, выделенные байты не изменились, значит, можно попробовать использовать их как сигнатуру. Чем меньше сигнатура, тем быстрее пройдет сканирование. Сигнатура 01 00 00 00 явно будет слишком часто встречаться в памяти. Лучше взять 03 00 00 01 00 00 00. Для начала найдем ее в памяти.

Сигнатура не уникальна
Сигнатура не уникальна

Сигнатура найдена, но она повторяется. Необходима более уникальная последовательность. Попробуем ED 03 00 00 01 00 00 00.

В подтверждение уникальности получим такой результат:

Сигнатура уникальна
Сигнатура уникальна

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

 

Жизненный цикл external

Используя функцию OpenProcess, внешние читы получают дескриптор для нужного процесса и вносят необходимые изменения в код (патчинг) или считывают и изменяют переменные внутри памяти игры. Для модификации памяти используются функции ReadProcessMemory и WriteProcessMemory.

Так как динамическое размещение данных в памяти мешает записать нужные адреса и постоянно к ним обращаться, можно использовать технику поиска AOB. Жизненный цикл external-чита выглядит так:

  1. Найти ID процесса.
  2. Получить дескриптор к этому процессу с нужными правами.
  3. Найти адреса в памяти.
  4. Пропатчить что-то, если нужно.
  5. Отрисовать GUI, если он имеется.
  6. Считывать или изменять память по мере надобности.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Check Also

Шах и мат! Как устроен нашумевший эксплоит checkm8 и как им воспользоваться

До недавнего времени джейлбрейка для iOS 13 не существовало. Точнее, до тех пор, пока хаке…

10 комментариев

  1. Аватар

    0ri0n

    11.04.2019 at 20:44

    Классно!
    Интересно!

    но не чего не понятно!

  2. Аватар

    Teach BlackBeard

    14.04.2019 at 15:24

    Крутая идея вставлять код в статью небольшими фрагментами — легко воспринимается. Скажите, пожалуйста, а откуда берутся значения в переменных у enum ProcessAccessFlags ? Просто не очень понятно

  3. Аватар

    mitrofanzzz

    21.05.2019 at 11:31

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

  4. Аватар

    slinkin

    03.06.2019 at 23:24

    Не совсем понятно касательно страниц памяти. Как я понимаю, переменная жизней должна храниться в регионе с правами на запись. Следовательно проверки: mbi.Protect == PAGE_READONLY и mbi.Protect == PAGE_EXECUTE_READ не имеют смысла. Разве нет?
    Также момент касательно функции LoadLibraryA — по хорошему нужно использовать LoadLibraryW. Это практически никак не усложняет код, зато сразу делает его с поддержкой Юникода.

  5. Аватар

    TOM227

    25.10.2019 at 01:31

    0x25CBFC4F, как я могу с тобой связаться? Есть интересный для тебя по этой теме проект 🙂

  6. Аватар

    hatemaself

    14.11.2019 at 11:58

    Не понимаю, _patternParts это откуда взято? У меня ошибка

Оставить мнение