Содержание статьи
- Выбор игры
- Поиск значений
- Что такое статический адрес
- Поиск показателей здоровья
- Поиск статического адреса для индикатора здоровья
- Поиск значения числа патронов
- Поиск статического адреса для ammo
- Проверка полученного статического адреса
- Проверка для HP
- Проверка для ammo
- Как будет выглядеть наш указатель в C++
- Написание трейнера
- Injector
- DLL
- Модуль обратных вызовов
- Модуль работы с памятью
- Проверка работоспособности
- Выводы
Выбор игры
Для начала определимся с игрой. Мой выбор пал на Hyper Light Drifter (далее HLD). Если ты планируешь поэкспериментировать с коммерческой игрой, обрати внимание на сайт pcgamingwiki, а также на игры с открытым исходным кодом.
warning
Так как для написания этой статьи я буду использовать коммерческую игру, мне нужно удостовериться, что лицензионное соглашение (EULA) позволяет это делать.
Начав установку и внимательно прочитав текст EULA, я убедился, что в нем явно запрещается написание и распространение только тех читов и трейнеров, которые мешают работе сервиса, а в нашем случае ничего подобного не планируется. Поэтому смело продолжаем установку.
![EULA HLD EULA HLD](https://xakep.ru/wp-content/uploads/2022/03/378874/1_EULA.png)
Поиск значений
Для поиска значений, которые будет изменять чит, мы станем использовать Cheat Engine (далее CE).
Запустим игру и в настройках игры выберем оконный режим — нам нужно, чтобы на экране помещалось еще что‑то, кроме игры.
![Оконный режим Оконный режим](https://xakep.ru/wp-content/uploads/2022/03/378874/2_windowed.png)
Как видим, в оконном режиме отсутствует панель заголовка, с помощью которой мы могли бы перетаскивать окно игры по экрану. Чтобы исправить эту неприятность, откроем отладчик x64dbg, а именно его 32-битную версию (x32dbg
) и запустим под ним HLD.
Поставим брейк‑пойнты на функции CreateWindowExA
и CreateWindowExW
, которые отвечают за создание окна. Найти их можно на вкладке Symbols, выбрав библиотеку user32.
.
![Вкладка символов Вкладка символов](https://xakep.ru/wp-content/uploads/2022/03/378874/3_symbols.png)
Видим, что наше окно создается с параметром dwStyle
, имеющим значение WS_POPUP
.
![Значение параметра dwStyle, равное WS_POPUP Значение параметра dwStyle, равное WS_POPUP](https://xakep.ru/wp-content/uploads/2022/03/378874/4_WS_POPUP.png)
Поменяем это значение на WS_OVERLAPPED
.
![Параметр dwStyle, измененный на WS_OVERLAPPED Параметр dwStyle, измененный на WS_OVERLAPPED](https://xakep.ru/wp-content/uploads/2022/03/378874/5_WS_OVERLAPPED.png)
И вот результат: теперь мы можем перемещать окно.
![Оконный режим с панелью заголовка окна Оконный режим с панелью заголовка окна](https://xakep.ru/wp-content/uploads/2022/03/378874/6_windowed.png)
После того как мы настроили окно игры с помощью отладчика, ненадолго отложим его. Чтобы найти нужные нам значения в Cheat Engine, разберемся с теорией.
Что такое статический адрес
Статический адрес — это адрес, который изменяется предсказуемо по отношению к модулю, которому он принадлежит. Если переменная глобальная, то можно найти ее в сегменте данных.
Статические адреса указываются в формате [
. Например, в library.
мы могли обнаружить значение по адресу 0x700004C0
(base
). Поскольку library.
может перемещаться и ее базовый адрес загрузки будет меняться, чтобы получить доступ к нашему значению, мы не используем этот адрес напрямую. Вместо этого возьмем адрес [library.
]. Следовательно, когда library.
загружается по базовому адресу 0x10000000
, [library.
] дает нам 0x100004C0
и у нас появится доступ к нашему значению.
Если же переменная локальная, то искать нужно в стеке. Для этого получаем TebBaseAddress
определенного потока, а затем второй указатель из этой структуры (FS:[
или GS:[
, в зависимости от разрядности процесса), которая содержит вершину стека. TebBasePointer
может быть получен с помощью NtQueryInformationThread
(если это 64-битный процесс) или же с помощью Wow64GetThreadSelectorEntry
(если это 32-битный процесс в 64-битной системе).
Поиск показателей здоровья
Запускаем Cheat Engine и подключаемся к процессу игры.
![Подключение к процессу игры Подключение к процессу игры](https://xakep.ru/wp-content/uploads/2022/03/378874/7_pl.png)
Так как мы не знаем, в каком типе хранится показатель здоровья, выставляем следующие параметры для первого сканирования.
![Первое сканирование Первое сканирование](https://xakep.ru/wp-content/uploads/2022/03/378874/8_fs.png)
Далее продолжаем сканирование, не забывая при этом терять hp (показатель здоровья) в игре. Делаем мы это для того, чтобы отслеживать изменения значения hp в памяти игры через CE, а также уменьшать значение в поиске для следующих сканирований. Делать мы это будем до тех пор, пока не будет достигнуто адекватное количество значений в окне CE. Адекватное количество значений в данном случае — это такое количество адресов, проверка которых займет максимум минут пять.
![Найденные адреса и их значения Найденные адреса и их значения](https://xakep.ru/wp-content/uploads/2022/03/378874/9_search_hp.png)
Мне приглянулись вот эти два адреса, которые я добавил в нижнее окно двойным щелчком мыши на них. Приглянулись они мне в первую очередь потому, что значения по этим адресам среди всех остальных имеют наибольший тип — double. Всегда нужно проверять от большего типа к меньшему. То есть сначала проверяем адреса, хранящие тип double, затем float, после integer и так далее. Более подробно о размере типов данных можно прочитать в документации Microsoft.
![Добавленные адреса Добавленные адреса](https://xakep.ru/wp-content/uploads/2022/03/378874/10_add_ct.png)
Если мы поменяем значение по адресу 0x36501940
, то на экране появится полоса здоровья, но его количество не поменяется.
![Индикатор hp Индикатор hp](https://xakep.ru/wp-content/uploads/2022/03/378874/11_hp_bar.png)
Если теперь мы поменяем значение по адресу 0x36501A30
, то на экране появится полоса hp и значение изменится. Это значит, что мы нашли адрес, в котором хранится значение здоровья в игре. Значение хранится в формате double (стандарт IEEE 754).
![Изменение hp Изменение hp](https://xakep.ru/wp-content/uploads/2022/03/378874/12_hp.png)
Дадим название найденным нами адресам: hp_bar
и hp
соответственно. Однако, как я уже рассказывал в разделе, посвященном статическим адресам, найденный нами адрес будет бесполезен после того, как мы выйдем в меню или перезапустим игру.
Поиск статического адреса для индикатора здоровья
Для дальнейшего поиска статического адреса вернемся к отладчику. В окне дампа переходим по ранее полученному адресу 0x36501A30
, в котором хранится значение hp.
![Значение по адресу 0x36501A30 в окне дампа Значение по адресу 0x36501A30 в окне дампа](https://xakep.ru/wp-content/uploads/2022/03/378874/13_hp_addr.png)
Ставим по адресу 0x36501A34
аппаратный брейк‑пойнт на запись и теряем в игре здоровье. Брейк‑пойнт срабатывает, и мы видим, что новое значение hp берется из регистра EDI
. Это значение является первым параметром текущей функции.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/14_static_addr.png)
Выйдя из этой функции, проследим, откуда она получает свой первый параметр. Мы увидим, что передаваемый параметр — это возвращаемое значение функции по адресу 0x003EFCE9
.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/15_static_addr.png)
Поставим брейк‑пойнт на вызов функции по адресу 0x003EFCE9
, а дальше продолжим отладку, пока не остановимся на ее вызове. Зайдя внутрь функции, выполняем ее до конца. Как только мы достигнем адреса 0x00F88E19
, мы увидим, что регистр EAX
хранит адрес значения hp. Очевидно, что в этой функции происходит доступ к нашему адресу через арифметику с указателями для структур, а именно через прибавление к указателю смещений и дальнейшего его разыменования. Более подробно об этом можно прочитать здесь. Нам нужно будет повторно пройтись по этой функции, чтобы узнать, через какой адрес и смещения она получает адрес значения hp.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/16_static_addr.png)
После того как мы узнали адрес 0x353F9BB0
, из которого получается адрес значения hp, начинаем выходить из функций. При этом внимательно отслеживаем, что передается им в качестве параметров. Спустя пару выходов мы наткнемся на следующее.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/17_final_static_addr.png)
Мы нашли статический адрес! Если посмотреть его расположение в памяти, он находится в секции .
.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/18_final_static_addr_memory_map.png)
Зная все смещения, добавим их в CE, нажав Add
.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/19_static_addr_ct.png)
Поиск значения числа патронов
Теперь приступим к поиску значения числа патронов (ammo). Первое сканирование делаем с такими же параметрами поиска, как когда мы искали здоровье.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/20_ammo_fs.png)
В данном случае мы смогли найти лишь одно значение, и это значение полосы, которая показывает число боеприпасов.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/21_ammo_bar.png)
В игре этот индикатор не появился. В отличие от полосы здоровья, он отображается только после нажатия на кнопку E или во время выстрелов.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/22_show_ammo_bar.png)
Поиск статического адреса для ammo
Мы понимаем, что показания индикаторов в игре всегда сравниваются с фактическими. Если одна из полос показывает не то, что нужно, ее длина изменяется. Поэтому возвращаемся к отладчику и начинаем с аппаратного брейк‑пойнта на запись по адресу 0x365014С4
. Как видим по комментариям, эта функция уже нам встречалась.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/23_ammo.png)
По аналогии с поиском hp, выходим из функции.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/24_ammo.png)
Так как мы уже знаем, что индикатор должен получать значение где‑то раньше, нам придется пролистать окно дизассемблера выше, пока мы не увидим функцию, предположительно получающую фактическое значение ammo.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/25_ammo.png)
Мы видим, что в этой функции мы уже были, а это значит, что она тоже получает значение, но уже ammo — 365014E0
. Только какое‑то оно странное.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/26_ammo.png)
Добавив это «странное» значение в Cheat Engine, а потом изменив его, к примеру, на 100, мы увидим, что на экране появится индикатор патронов и его значение поменяется. Значит, мы нашли адрес, в котором хранится значение ammo в игре.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/27_ammo.png)
Зная все смещения от статического адреса к адресу значений ammo, добавим их в CE, нажав Add
.
![](https://xakep.ru/wp-content/uploads/2022/03/378874/28_static_addr_ct.png)
info
Скорее всего, боеприпасы в HLD представляют собой заряд энергии и поэтому хранятся в процентах, ведь при их поиске через отладчик можно было увидеть строки, содержащие слово energy
. Которое намекает на то, как будет выглядеть значение в памяти. Например, игра от одной небезызвестной польской компании хранила патроны в памяти вместе, а для игрока показывала раздельно: как рожок, так и количество оставшихся патронов, поэтому при их поиске не удавалось найти каждое из значений, а нужно было искать их сумму.
Проверка полученного статического адреса
Чтобы проверить, правильно ли мы определили адреса, нужно выйти в меню игры и вернуться к игровому процессу или же перезапустить игру.
Проверка для HP
Так выглядит наша cheat table для hp.
![Таблица до выхода в меню / перезапуска игры Таблица до выхода в меню / перезапуска игры](https://xakep.ru/wp-content/uploads/2022/03/378874/29_hp.png)
А вот так она выглядит после перезапуска игры.
![Таблица после запуска игрового процесса Таблица после запуска игрового процесса](https://xakep.ru/wp-content/uploads/2022/03/378874/30_hp.png)
Проверка для ammo
Так выглядит наша cheat table для ammo.
![Таблица до выхода в меню / перезапуска игры Таблица до выхода в меню / перезапуска игры](https://xakep.ru/wp-content/uploads/2022/03/378874/31_ammo.png)
А вот так она выглядит после перезапуска игры.
![Таблица после запуска игрового процесса Таблица после запуска игрового процесса](https://xakep.ru/wp-content/uploads/2022/03/378874/32_ammo.png)
Как будет выглядеть наш указатель в C++
В нашем чите доступ к найденным адресам значений будет таким.
static_addr = (DWORD)GetModuleHandle(0);static_addr = *(DWORD*)(static_addr + 0x255AF150);static_addr = *(DWORD*)(static_addr);static_addr = *(DWORD*)(static_addr + 0xD48);static_addr = *(DWORD*)(static_addr + 0x0C);static_addr = (DWORD*)(static_addr + 0xC4);static_addr = *(DWORD*)(*static_addr + 0x08);static_addr = *(DWORD*)(static_addr + 0x44);static_addr = *(DWORD*)(static_addr + 0x10);drifter_hp = (double*)(DWORD*)*(DWORD*)(static_addr + 0x1FD8);drifter_ammo = (double*)(DWORD*)*(DWORD*)(static_addr + 0x268C);
Написание трейнера
По принципу действия читы можно разделить на две группы: внутренние и внешние. Внешние читы — это отдельное приложение, запущенное в системе в виде процесса. Внутренние читы обычно реализованы как динамическая библиотека, внедряемая в процесс игры.
Мы будем писать внутренний чит, поэтому нам понадобится не только сама библиотека, но и инжектор, который внедрит нашу библиотеку в процесс игры. Инжектор получит список процессов, найдет процесс игры, выделит в ней память, в которую запишет наш внутренний чит, а после создаст удаленный поток внутри игры для выполнения кода нашего чита.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»