Содержание статьи
- Выбор игры
- Поиск значений
- Что такое статический адрес
- Поиск показателей здоровья
- Поиск статического адреса для индикатора здоровья
- Поиск значения числа патронов
- Поиск статического адреса для ammo
- Проверка полученного статического адреса
- Проверка для HP
- Проверка для ammo
- Как будет выглядеть наш указатель в C++
- Написание трейнера
- Injector
- DLL
- Модуль обратных вызовов
- Модуль работы с памятью
- Проверка работоспособности
- Выводы
Выбор игры
Для начала определимся с игрой. Мой выбор пал на Hyper Light Drifter (далее HLD). Если ты планируешь поэкспериментировать с коммерческой игрой, обрати внимание на сайт pcgamingwiki, а также на игры с открытым исходным кодом.
warning
Так как для написания этой статьи я буду использовать коммерческую игру, мне нужно удостовериться, что лицензионное соглашение (EULA) позволяет это делать.
Начав установку и внимательно прочитав текст EULA, я убедился, что в нем явно запрещается написание и распространение только тех читов и трейнеров, которые мешают работе сервиса, а в нашем случае ничего подобного не планируется. Поэтому смело продолжаем установку.
Поиск значений
Для поиска значений, которые будет изменять чит, мы станем использовать Cheat Engine (далее CE).
Запустим игру и в настройках игры выберем оконный режим — нам нужно, чтобы на экране помещалось еще что‑то, кроме игры.
Как видим, в оконном режиме отсутствует панель заголовка, с помощью которой мы могли бы перетаскивать окно игры по экрану. Чтобы исправить эту неприятность, откроем отладчик x64dbg, а именно его 32-битную версию (x32dbg
) и запустим под ним HLD.
Поставим брейк‑пойнты на функции CreateWindowExA
и CreateWindowExW
, которые отвечают за создание окна. Найти их можно на вкладке Symbols, выбрав библиотеку user32.
.
Видим, что наше окно создается с параметром dwStyle
, имеющим значение WS_POPUP
.
Поменяем это значение на WS_OVERLAPPED
.
И вот результат: теперь мы можем перемещать окно.
После того как мы настроили окно игры с помощью отладчика, ненадолго отложим его. Чтобы найти нужные нам значения в Cheat Engine, разберемся с теорией.
Что такое статический адрес
Статический адрес — это адрес, который изменяется предсказуемо по отношению к модулю, которому он принадлежит. Если переменная глобальная, то можно найти ее в сегменте данных.
Статические адреса указываются в формате [
. Например, в library.
мы могли обнаружить значение по адресу 0x700004C0
(base
). Поскольку library.
может перемещаться и ее базовый адрес загрузки будет меняться, чтобы получить доступ к нашему значению, мы не используем этот адрес напрямую. Вместо этого возьмем адрес [library.
]. Следовательно, когда library.
загружается по базовому адресу 0x10000000
, [library.
] дает нам 0x100004C0
и у нас появится доступ к нашему значению.
Если же переменная локальная, то искать нужно в стеке. Для этого получаем TebBaseAddress
определенного потока, а затем второй указатель из этой структуры (FS:[
или GS:[
, в зависимости от разрядности процесса), которая содержит вершину стека. TebBasePointer
может быть получен с помощью NtQueryInformationThread
(если это 64-битный процесс) или же с помощью Wow64GetThreadSelectorEntry
(если это 32-битный процесс в 64-битной системе).
Поиск показателей здоровья
Запускаем Cheat Engine и подключаемся к процессу игры.
Так как мы не знаем, в каком типе хранится показатель здоровья, выставляем следующие параметры для первого сканирования.
Далее продолжаем сканирование, не забывая при этом терять hp (показатель здоровья) в игре. Делаем мы это для того, чтобы отслеживать изменения значения hp в памяти игры через CE, а также уменьшать значение в поиске для следующих сканирований. Делать мы это будем до тех пор, пока не будет достигнуто адекватное количество значений в окне CE. Адекватное количество значений в данном случае — это такое количество адресов, проверка которых займет максимум минут пять.
Мне приглянулись вот эти два адреса, которые я добавил в нижнее окно двойным щелчком мыши на них. Приглянулись они мне в первую очередь потому, что значения по этим адресам среди всех остальных имеют наибольший тип — double. Всегда нужно проверять от большего типа к меньшему. То есть сначала проверяем адреса, хранящие тип double, затем float, после integer и так далее. Более подробно о размере типов данных можно прочитать в документации Microsoft.
Если мы поменяем значение по адресу 0x36501940
, то на экране появится полоса здоровья, но его количество не поменяется.
Если теперь мы поменяем значение по адресу 0x36501A30
, то на экране появится полоса hp и значение изменится. Это значит, что мы нашли адрес, в котором хранится значение здоровья в игре. Значение хранится в формате double (стандарт IEEE 754).
Дадим название найденным нами адресам: hp_bar
и hp
соответственно. Однако, как я уже рассказывал в разделе, посвященном статическим адресам, найденный нами адрес будет бесполезен после того, как мы выйдем в меню или перезапустим игру.
Поиск статического адреса для индикатора здоровья
Для дальнейшего поиска статического адреса вернемся к отладчику. В окне дампа переходим по ранее полученному адресу 0x36501A30
, в котором хранится значение hp.
Ставим по адресу 0x36501A34
аппаратный брейк‑пойнт на запись и теряем в игре здоровье. Брейк‑пойнт срабатывает, и мы видим, что новое значение hp берется из регистра EDI
. Это значение является первым параметром текущей функции.
Выйдя из этой функции, проследим, откуда она получает свой первый параметр. Мы увидим, что передаваемый параметр — это возвращаемое значение функции по адресу 0x003EFCE9
.
Поставим брейк‑пойнт на вызов функции по адресу 0x003EFCE9
, а дальше продолжим отладку, пока не остановимся на ее вызове. Зайдя внутрь функции, выполняем ее до конца. Как только мы достигнем адреса 0x00F88E19
, мы увидим, что регистр EAX
хранит адрес значения hp. Очевидно, что в этой функции происходит доступ к нашему адресу через арифметику с указателями для структур, а именно через прибавление к указателю смещений и дальнейшего его разыменования. Более подробно об этом можно прочитать здесь. Нам нужно будет повторно пройтись по этой функции, чтобы узнать, через какой адрес и смещения она получает адрес значения hp.
После того как мы узнали адрес 0x353F9BB0
, из которого получается адрес значения hp, начинаем выходить из функций. При этом внимательно отслеживаем, что передается им в качестве параметров. Спустя пару выходов мы наткнемся на следующее.
Мы нашли статический адрес! Если посмотреть его расположение в памяти, он находится в секции .
.
Зная все смещения, добавим их в CE, нажав Add
.
Поиск значения числа патронов
Теперь приступим к поиску значения числа патронов (ammo). Первое сканирование делаем с такими же параметрами поиска, как когда мы искали здоровье.
В данном случае мы смогли найти лишь одно значение, и это значение полосы, которая показывает число боеприпасов.
В игре этот индикатор не появился. В отличие от полосы здоровья, он отображается только после нажатия на кнопку E или во время выстрелов.
Поиск статического адреса для ammo
Мы понимаем, что показания индикаторов в игре всегда сравниваются с фактическими. Если одна из полос показывает не то, что нужно, ее длина изменяется. Поэтому возвращаемся к отладчику и начинаем с аппаратного брейк‑пойнта на запись по адресу 0x365014С4
. Как видим по комментариям, эта функция уже нам встречалась.
По аналогии с поиском hp, выходим из функции.
Так как мы уже знаем, что индикатор должен получать значение где‑то раньше, нам придется пролистать окно дизассемблера выше, пока мы не увидим функцию, предположительно получающую фактическое значение ammo.
Мы видим, что в этой функции мы уже были, а это значит, что она тоже получает значение, но уже ammo — 365014E0
. Только какое‑то оно странное.
Добавив это «странное» значение в Cheat Engine, а потом изменив его, к примеру, на 100, мы увидим, что на экране появится индикатор патронов и его значение поменяется. Значит, мы нашли адрес, в котором хранится значение ammo в игре.
Зная все смещения от статического адреса к адресу значений ammo, добавим их в CE, нажав Add
.
info
Скорее всего, боеприпасы в HLD представляют собой заряд энергии и поэтому хранятся в процентах, ведь при их поиске через отладчик можно было увидеть строки, содержащие слово energy
. Которое намекает на то, как будет выглядеть значение в памяти. Например, игра от одной небезызвестной польской компании хранила патроны в памяти вместе, а для игрока показывала раздельно: как рожок, так и количество оставшихся патронов, поэтому при их поиске не удавалось найти каждое из значений, а нужно было искать их сумму.
Проверка полученного статического адреса
Чтобы проверить, правильно ли мы определили адреса, нужно выйти в меню игры и вернуться к игровому процессу или же перезапустить игру.
Проверка для HP
Так выглядит наша cheat table для hp.
А вот так она выглядит после перезапуска игры.
Проверка для ammo
Так выглядит наша cheat table для ammo.
А вот так она выглядит после перезапуска игры.
Как будет выглядеть наш указатель в 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»