Найти в Дзене
Adr Asd

Взлом игр, ковыряем Night of the Dead v2.0.5.7

Оглавление

Привет. В прошлой статье обещал взлом Night.of.the.Dead, вот он. Конечно - эта статья в основном для начинающих, заканчивающим просьба не обращать внимания ;-) С помощью x64Dbg ( и возможно Ida 8.2 Free ) мы будем придавать игре нужные нам свойства. Отучим патроны - уменьшаться, здоровье - повреждаться, выносливость - падать, а износ оружия - таять. ;-) Хотя, если вы игрок, здоровье я бы оставил как есть, иначе играть совсем не интересно.

Скорее всего игра уже обновлена, можете взять торрент Night.of.the.Dead.v2.0.5.7 по ссылке (24 Гб), или попытаться разобраться с новой версией самому основываясь на статье как на примере.

Ida 8.2 Free - теперь умеет сохранять результаты работы. Можно обойтись без Ida, я использую её для анализа кода, а в x64dbg провожу основные операции копания в коде на живую и патчинга. Локальный отладчик Ida - работал на несколько порядков тормознее чем x64Dbg, в виду ресурсоёмкости игры и довольно старого моего ноута. Ещё понадобятся ArtMoney и CheatEngine, для поиска нужных нам значений в игре. Сразу скажу - CheatEngine не приятно удивил, на отрез отказавшись искать неизвестные значения, выводя сообщение об ошибке ( выяснил, требователен к свободному месту на диске для временных файлов ). В свою очередь ArtMoney 8.2 отказался запускаться в Win7x64 (старая версия ArtMoney 8.0 работает) , что меня тоже озадачило. Использую для взлома отдельную ОС, а точнее несколько ОСей, Win11x64 и Win7x64, так как дебаггеры ведут себя в них по разному. Например - в w11 хардварные бряки (аппаратные точки останова) в x64Dbg часто приводили к падениям UnrealEngine , тогда как в w7 падений почти не было. Иногда при отключении или удалении аппаратных бряков в Win11 игра виснет или работаем в Win7, Win10. Ну и для удобства использую 2 монитора, хотел бы 3 но старое железо так не умеет ;-) Игра на мониторе ноутбука, отладка на втором мониторе - побольше.

-2

F12- пауза, F9- продолжить, F7- шаг вперёд, F8- шаг вперёд без входа в функции, F2- установить/убрать точку останова (бряк).

Все программы у нас есть, начинаем.

Для начала создадим папку, в которую будут хранится все наши материалы по взлому, текстовые файлы с фрагментами кода и запомненной информацией, резервные базы от x64Dbg, Ida и прочее. Назовём её как игру - Night.of.the.Dead.v2.0.5.7_dbg Для начала заархивируем оригинальный "экзэшник" игры ..\Night.of.the.Dead.v2.0.5.7\LF\Binaries\Win64\LF-Win64-Shipping.exe в LF-Win64-Shipping_original.zip, чтоб в случае чего - мы могли вернуть его.

-3

Запускаем x64Dbg и открываем в нём LF-Win64-Shipping.exe и если в настройках x64Dbg "Параметры->События" (Прерываться на) установлен один флажок "Точка входа", то отладчик там и остановится

-4

Перейдём на вкладку Карта памяти и скопируем адрес Базы, по которому загружен главный исполняемый файл игры lf-win64-shipping.exe.

-5

Сохраним его в текстовый файл, например с именем 1_.txt, всегда при перезагрузке игры - будем сохранять эту Базу рядом с фрагментами кода в текстовике, чтоб не потеряться в кодах при крахе и перезагрузке по другому адресу. В этом случае - с помощью Ida, мы можем произвести Rebase и найти нужное место.

Для начала избавимся от IsDebuggerPresent - функции, определяющей наличие отладки помещая в eax 1. Но x64Dbg конечно же определяет этот архаичный анти-отладочный приём и пресекает его, но мы всё равно от него избавимся ;-) Убеждаемся что текущий модуль в отладчике является главным исполняемым файлом LF-Win64-Shipping.exe

-6

Щёлкаем в коде ПКМ и в контекстном меню Поиск в->Текущий модуль->Межмодульные вызовы

-7

В окне Ссылки отобразится список результатов поиска. В низу, в стоке поиска введём IsDebuggerPresent и список отфильтрует лишнее. Видим, что в коде присутствуют две функции

-8

Можно поставить бряки ( breakpoints ) и посмотреть как ведёт себя код под отладкой, но мы просто занупаем ( nop - No Operator ) их. Щёлкаем дважды по первой строке IsDebuggerPresent и переходим к коду и видим

call qword ptr ds:[<&IsDebuggerPresent>]
cmp eax,1 --- что сразу после его вызова идёт сравнение eax с 1, а кодом далее какие то переходы.

Щёлкаем ПКМ в строке вызова IsDebuggerPresent и в контекстном меню Двоичные операции -> Заполнить командами NOP

-9

И в открывшемся маленьком окне с заголовком "Размер" щёлкаем ОК. И вместо вызова теперь пустые операторы NOP. Тоже проделываем и со вторым вызовом IsDebuggerPresent, на вкладке Ссылки дважды щёлкаем по строке вызова, попадаем в код и нупим второй вызов. А теперь сохраним наши изменения, ПКМ в коде и в контекстном меню Исправления

-10

В открывшемся окне исправлений нажимаем кнопку Исправить файл и в диалоговом окне задаём имя сохраняемого файла LF-Win64-Shipping_patch1.exe или LF-Win64-Shipping_p1.exe После успешного сохранения у нас есть пропатченный LF-Win64-Shipping_p1.exe рядом с оригиналом. Выгружаем из отладчика игру.

Если нужно отменить сделанные в коде изменения, то в контекстном меню есть пункт Восстановить данные в выделенной позиции

-11

Вернёмся в папку Night.of.the.Dead.v2.0.5.7_dbg , где мы решили сохранять бэкапы и наработки и создадим там папку "патчи", зазипим LF-Win64-Shipping_p1.exe в LF-Win64-Shipping_p1.zip и и сохраним его в Night.of.the.Dead.v2.0.5.7_dbg\патчи\ Удаляем LF-Win64-Shipping.exe (у нас есть архив с ним) , а LF-Win64-Shipping_p1.exe переименовываем в LF-Win64-Shipping.exe.

Открываем новый LF-Win64-Shipping.exe в отладчике, идём на вкладку "Карта памяти" и копируем в 2_.txt ( каждый новый цикл исследования - новый файл ;- ) адрес базы для lf-win64-shipping.exe, он мог изменится. Старую базу я обычно тоже оставляю, на случай необходимости восстановления предыдущих манипуляций с фрагментами кода, сохранёнными в 2_.txt со старыми адресами. ( во загнул ;- )

А теперь мы просто нажимаем кнопку Выполнить и запускаем игру. При появлении не запланированных исключений

-12

идём в меню Параметры -> Параметры и на вкладке Исключения нажимаем Пропустить последнее , отвечаем Yes в окошке подтверждения и сохраняем по кнопке Сохранить. Продолжаем выполнение по Shift+F9. С остальными исключениями поступаем так же.

Приступаем к поискам.

-- Адреса в отладчике у вас будут другие.--

Игра работает, начинаем новую, ломаем что нибудь железное и делаем нож. С ножом получаем ветви дерева, делаем 20 или 25 джавелинов и сохраняемся. Итак, у нас есть копья-джавелины (у меня 24)

-13

и сейчас мы будем искать фрагмент кода отнимающего у нас боеприпасы. Запускаем CheatEngine , выбираем в процессах NOTD , в поле Значение вносим количество джавелинов в инвентаре а не в нижнем поле, нажимаем Поиск. В поле результатов будет найдено не малое их количество. Возвращаемся в игру, кидаем один джавелин куда нибудь, в CheatEngine в поле поиска уменьшаем значение на 1 и нажимаем Отсев. Получаем заметно меньше значений и повторяем процесс. Когда осталось приемлемое количество результатов или один, кликаем по ним перенося их в нижнее поле. Заморозим их для проверки, а то ли мы нашли. И если то, будем размораживать по очереди для обнаружения основного значения. Адрес сохранения количества боеприпасов найден, копируем этот адрес. CheatEngine можно закрыть. Переходим в x64Dbg, останавливаем игру кнопкой Приостановить , переходим в Дамп, нажимаем Ctrl+G и в окне Введите выражение Для Перехода, в строке вставляем адрес найденный CheatEngine.

-14

Видим там количество джавелинов в шестнадцатеричном виде, в моём случае 0x14 == 20 джавелинов. Щёлкаем ПКМ по этому числу и в контекстном меню Точка останова -> Аппаратная, Запись -> Dword

-15

На вкладке Точки останова появляется наш бряк.

-16

Возобновляем выполнение игры кнопкой Выполнить и в игре бросаем ещё один джавелин. Отладчик остановит выполнение игры сразу после записи по нашему адресу, то есть строкой ниже.

sub eax,r8d
mov dword ptr ds:[rbx+E8],eax <-- запись произошла здесь
mov rax,qword ptr ds:[rcx] <-- остановка здесь

Перейдём на строку выше и нажав F2 установим новый бряк, а на вкладке Точки останова , клавишей пробел деактивируем старый аппаратный бряк на запись или удаляем. Видим что чуть выше производится вычитание из eax числа в r8, поставим туда бряк F2 и посмотрим. Возобновляем выполнение по F9, бросаем джавелин и отладчик остановится на нашем вычитании

-17

Как видим в eax шестнадцатеричное количество наших джавелинов (в инвентаре), а в r8 единица. Тут простой декремент и если мы заменим r8 на ноль, то вычитаться ничего не будет. Попробуем.

sub eax,r8d -- удаляем бряк по F2
mov dword ptr ds:[rbx+E8],eax -- тут нажав клавишу ":" напишем комментарий "патч боеприпасов", бряк деактивируем на вкладке Точки останова. Теперь Щёлкаем мышью по строке sub eax,r8d и нажимаем Пробел. В окне Ассемблирование меняем r8 на 0

-18

И закрываем окно Ассемблирование. Получаем такой код

-19

Возобновляем выполнение игры по F9 и проверяем наш патч. Количество джавелинов перестало уменьшаться и мы можем сохранить изменения. Приостанавливаем выполнение, нажимаем ПКМ -> Исправления , в окне Исправлений по кнопке исправить файл. Задаём имя нового файла LF-Win64-Shipping_p2.exe и сохраняем его. Выгружаем игру из отладчика. Мы пропатчили игру от уменьшение боеприпасов, при чём всех. Зипуем LF-Win64-Shipping_p2.exe и LF-Win64-Shipping_p2.zip копируем в Night.of.the.Dead.v2.0.5.7_dbg\патчи\ Удаляем LF-Win64-Shipping.exe а LF-Win64-Shipping_p2.exe переименовываем в LF-Win64-Shipping.exe

Давайте сохраним базу с данными отладки X64Dbg, она находится в папке ..\x64dbg\release\x64\db , создадим в нашей рабочей папке подпапку bckp и сохраним туда папку ..\x64dbg\release\x64\db добавив дату к имени и заархивируем её. Внутри надо оставить файлы относящиеся к отлаживаемой программе, остальные удалить если есть. Это позволит в случае повреждения базы при крахе вернуть её.
Теперь отучим игру отнимать у нас аптечки, они же еда. Для этого надо побегать и понаходить какой нибудь еды. Запускаем игру в отладчике и ищем еду. У меня шоколадки. Запускаем
CheatEngine и ищем число нашей еды так же как и боеприпасы. Определив адрес сохранения числа закрываем CheatEngine, приостанавливаем игру в отладчике, с помощью Ctrl+G переходим в дампе к нашему адресу и ставим на него аппаратную точку останова на запись DWORD.

-20

Возобновляем выполнение и съедаем единицу еды, остановка происходит строкой ниже.

dec dword ptr ds:[rax+rbp+E8] <-- здесь подозрительный декремент
mov rsi,qword ptr ds:[rdi+118] <-- здесь остановка

Щёлкнув ПКМ по строке dec dword ptr ds:[rax+rbp+E8] и в меню Перейти к дампу -> rax+rbp+E8 , то мы попадём как раз на наш адрес, на который мы установили аппаратный бряк.

-21

Установим бряк F2 на строку, где мы сейчас остановились и клавишей ":" добавим описание "патчь аптечек". На вкладке Точки останова удалим аппаратный бряк и отключим пробелом тот что с описанием "патчь аптечек". А теперь занупаем строку декремента dec dword ptr ds:[rax+rbp+E8]. ПКМ -> Двоичные операции -> Заполнить командами NOP возобновим выполнение и проверим что получилось. Еда перестала уменьшаться. ПКМ -> Исправления .. Исправить файл, сохраняем его с именем LF-Win64-Shipping_p3.exe, выгружаем из отладчика, архивируем и переименовываем в LF-Win64-Shipping.exe.

Полоса Здоровья

А теперь у нас довольно ответственный момент, мы хотим найти фрагменты кода изменяющие здоровье, а оно у нас является индикатором в виде полосы и мы не знаем ни его максимальное число, ни тип значения в котором оно хранится. Но мы всё равно найдём ;-) Запускаем игру в отладчике. Для поиска неизвестного значения можно использовать ArtMoney, а вот CheatEngine отказался на отрез. Запускаем ArtMoney, выбираем процесс игры NOTD, нажимаем кнопку Искать.

-22

Скорее всего число здоровья хранится в типе FLOAT. Я находил теже данные но в HEX виде, но это менее наглядно. В окошке Поиск (шаг1) - в поле Искать оставляем Неизвестное значение, кнопочкой с тремя точками открываем окно с типами данных и ставим галочку на С точкой 4 байта и ОК. В окошке Поиск (шаг1) нажимаем ОК и начинается поиск. После завершения процесса нажимаем ОК в окне поиска. Возвращаемся в игру и повреждаем немного здоровья, после этого в ArtMoney щёлкаем по кнопке Отсеять, в строке Искать - оставляем Неизвестное значение , а в строке Значение выбираем Уменьшилось и нажимаем ОК.

-23

Начнётся довольно длительный процесс и будет найдено приличное количество совпадений. Возвращаемся в игру, лечимся и в ArtMoney снова щёлкаем по кнопке Отсеять, в строке Значение выбираем Увеличилось и нажимаем ОК.

-24

Следующий процесс пройдёт быстрее. Повторяем эти два шага - пока количество результатов не уменьшится до вменяемого числа. В моём случае количество результатов остановилось на 75 и перестало уменьшаться. Вылечим персонажа в игре полностью и предположим что здоровье может быть 100.0 Произведём ещё одно отсеивание. Добавим все адреса из левого поля в правое с помощью контекстного меню.

-25

В моём случае таких чисел было всего 2, заморозим их и проверим в игре повреждая перса. У меня сработал первый адрес со значением 0239C7687DC0 == 100.00 Копируем его в наш рабочий текстовик. Переходим в x64Dbg в дамп, с помощью Ctrl+G переходим к нашему адресу и видим там перевёрнутое значение 0000C842 - оно же 0x42C80000 - оно же 100.00 float. Устанавливаем туда аппаратную точку останова на запись DWORD

-26

иииии -- у меня в Win11 под отладкой падает UnrealEngine ;-)

-27

Но теперь мы знаем максимальное число здоровья 0x42C80000 и найти его теперь не составит труда. Если у вас происходит то же, перезагружаем игру в x64Dbg, запускаем игру, лечим перса до 100% и запускаем ArtMoney (или CheatEngine).

-28

Выбираем искать точное значение. В строку значение вносим 100.00 , по кнопке с 3 точками выбираем тип Сточкой 4 байта и ищем. И в этот раз результатов будет меньше. Проделываем операции отсеивания не забывая указать - что изменённое значение не известно, а искомый тип С точкой 4 байта.

-29

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

-30

Что ж продолжаем. Переходим в дамп x64Dbg и ставим аппаратный бряк на запись DWORD на наш адрес и возобновляем игру.

-31

И останавливаемся сразу после записи, здесь

movss xmm0,dword ptr ds:[rcx+104] |
movss dword ptr ds:[rcx+100],xmm3 | запись здоровья
mov byte ptr ss:[rsp+20],r8b | -- остановка hbp

Устанавливаем бряк по F2 на строку с xmm3 и с помощью ";" добавляем комментарий "запись здоровья" , а на вкладке Точки останова убираем аппаратный бряк. Видим - что наш адрес представлен кодом [rcx+100]. Если посмотреть по коду выше то заметим и [rcx+104], адрес рядом с нашим, а в регистре RCX сейчас,( когда мы остановились после записи) находится адрес не далёкий от нашего.

-32

В моём случае RCX == 00815FA880, а наше здоровье хранится по 00815FA980 (у вас будет другой), то есть в rcx+100. Логично предположить - что в RCX передаётся адрес какой то структуры хранящей здоровье и прочие свойства главного героя. Сохраним RCX == 00815FA880 в текстовичёк чтоб не забыть. Давайте деактивируем бряк , на вкладке Точки останова, обозначенный как "запись здоровья" и понаблюдаем как изменяется число здоровья по найденному адресу в дампе. Возобновляем игру и изменяем здоровье, число соответственно изменяется. А ещё изменяется число правее нашего rcx+104 когда мы прыгаем, бегаем или прикладываем усилия, ну то есть не мы а главный герой ( ГГ ) в игре ;-) Похоже что заодно мы обнаружили и место хранения выносливости ( энергии ) и его максимальное значение такое же как у здоровья, 0x42C80000 или 100.00 float. То есть, чтобы обнаружить структуру с данными ГГ, нам достаточно активировать бряк на код [rcx+100],xmm3 подписанный нами как "запись здоровья" и перейти в дамп по адресу в регистре RCX, и на 0x100 байт ниже будет адрес здоровья, а за ним адрес энергии.

Вот теперь и начинаем реверсное исследование вызываемых функций. Я продолжаю статью после перезагрузки, по этому адреса на скриншотах другие. И так, выполнение остановлено на бряке "запись здоровья", поищем начало функции, оно чуть выше (sub rsp,48).

sub rsp,48 | начало функции (запись здоровья)
mov rax,qword ptr ds:[1452FFC98] |
xor rax,rsp |
...
movss xmm0,dword ptr ds:[rcx+104] |
movss dword ptr ds:[rcx+100],xmm3 | запись здоровья
mov byte ptr ss:[rsp+20],r8b |

Установим на начало бряк F2 и подпишем комментарий к нему - "начало функции (запись здоровья)", но этот код выполняется не только для ГГ , но и для зомбей (NPC), что можно проверить возобновив игру. Чтоб остановка срабатывала только при смене здоровья ГГ , нужно установить условие для нашего бряка. Щёлкаем ПКМ по бряку "начало функции (запись здоровья)" и в контекстном меню Точки останова -> Редактирование

-33

а в окне редактирования, в строке Условие остановки пишем , RCX ==00815FA880 , адрес структуры данных ГГ

-34

и нажимаем Сохранить. Теперь наш бряк стал "условным", что видно на вкладке бряков и остановка произойдёт кода будет производится работа с данными ГГ,

-35

но этот код обрабатывает ещё и энергию ГГ, так что нам нужно выяснить различия при обработке здоровья и энергии. Возобновляем выполнение игры и сразу происходит остановка, в RCX наш адрес. Похоже функция выполняет какие то действия в цикле, мы не знаем какие ещё данные хранит структура ГГ и какие счётчики там содержаться, но мы можем выяснить состояние регистров при остановке на бряке "запись здоровья", активируем его и деактивируем остальные, возобновляем игру и применяем аптечку или повреждаем здоровье, остановка. Смотрим в регистры, а в регистрах у нас: RCX равен адресу нашей структуры ГГ, а в младшем байте RDX, он же DL - находится 1 и в регистре R8 тоже 1.

-36

В коде функции мы можем обнаружить что, запись в r8 происходит из DL, а далее производятся сравнения r8 с числами и переходы по условию сравнения ( == равно, != не равно, && и)

sub rsp,48 | начало функции (запись здоровья)
...
movzx r8d,dl | запись в r8 из dl (RDX low byte)
...
cmp r8b,2 | сравнение r8 с 2
jne lf-win64-shipping.140CDBA34
...
cmp r8b,1 | сравнение r8 с 1 и если ==1 то к записи здоровья если !=1
jne lf-win64-shipping.140CDBA51 | перепрыгиваем через запись здоровья
movss xmm0,dword ptr ds:[rcx+104]
movss dword ptr ds:[rcx+100],xmm3 | запись здоровья
mov byte ptr ss:[rsp+20],r8b

Похоже что в DL - код операции. Попробуем к условию бряка "начало функции (запись здоровья)" добавить dl==1 ( r8==1 не срабатывает) и посмотрим что будет. Условие выглядит так: rcx==00815FA880 && dl==1

-37

Возобновляем выполнение и уменьшаем здоровье прыгая с сарая , бряк срабатывает. Остановлены на начале функции изменяющей здоровье ГГ, а в стёке мы видим адрес возврата из этой функции, то есть строкой выше возврата будет место вызова.

===== в виду перезагрузки - адреса изменились , но не суть =====

-38

Щёлкаем в стёке один раз по строке с адресом возврата и нажимаем Enter, и мы перейдём в окно кода к этому адресу, или ПКМ по строке с адресом и в меню Перейти по QWORD в дизассемблерный код

-39

В окне кода, вызов нашей функции будет сразу над адресом возврата, поставим на неё бряк F2 и добавим описание "к здоровью 1"

-40

На таком этапе поисков, я обычно подключаю Ida, там удобнее и нагляднее определять что откуда приходит. искать строки, переменные и масса полезного, но вот косвенные адреса вызовов вроде, call qword ptr [rax+468h] , можно определить лишь во время отладки, тут есть отладчики, но X64Dbg у меня шустрее работает. Так же в Ida расставляю те же бряки что и в x64Dbg.

-41

А Edit ->Segments ->Rebase programm , позволяет синхронизировать адреса с x64Dbg.

-42

И так, мы знаем что функция "к здоровью 1" выполняется в цикле и для ГГ, и для NPC, по этому мы не можем патчить её ( так как всё станут бессмертными ;-). Продвигаясь с помощью адресов возврата по вызовам в обратном направлении, мы должны найти код, выполняемый только для ГГ и пропатчить вызов этого кода - ведущего к уменьшению здоровья ГГ.

Так как адрес структуры передаётся в функцию в rcx, то смотрим, откуда он берётся в rcx здесь. Поизучав текущую функцию, из которой вызывается функция "к здоровью 1" и проверив - что она вызывается в том же цикле ( потыкав F9 ), определим нужное состояние регистров, для остановки на здоровье ГГ. Строкой выше функции "к здоровью 1" видим, mov rcx,rdi - что значит копировать значение в RCX из RDI, а в начале текущей функции видим что:

mov qword ptr ss:[rsp+10],rbx | к здоровью 2
push rdi |
sub rsp,40 |
movzx ebx,dl | заносим что то в EBX из DL
mov rdi,rcx | заносим что то в RDI из RCX

Установим бряк F2 на начало функции ( mov qword ptr ss:[rsp+10],rbx ) и прокомментируем её как "к здоровью 2". Похоже что адрес структуры ГГ приходит так же в RCX а код операции в DL. По этому добавим к нашему бряку условие: rcx==00815FA880 && dl==1 , а остальные бряки можно деактивировать на вкладке бряков. Возобновляем игру и уменьшаем здоровье, остановка. Смотрим в стёке адрес возврата и переходим по Enter в код , а строкой выше наш вызов:

call lf-win64-shipping.140CCB6F0 | к здоровью 3

Установим бряк F2 и подпишем как "к здоровью 3". Понажимав F9, понимаем что выполняется всё в том же цикле, а в RCX наблюдаем в основном адрес нашей структуры ГГ и в DL (младший разряд RDX) число 4. Для интереса, можно проверить, происходит ли здесь остановка с адресами кроме нашего. Добавим условие к бряку "к здоровью 3" rcx!=00815FA880 и возобновим выполнение. Изменим здоровье и сработает бряк "к здоровью 2", а это значит что вызов функции "к здоровью 3" срабатывает только для ГГ. Уберём условие для бряка "к здоровью 3" и деактивируем его. А на начало функции mov qword ptr ss:[rsp+8],rbx бряк поставим и подпишем "к здоровью 4". По нажимаем F9 и убедимся что в RCX сохраняется адрес структуры ГГ, а в DL число 4. Давайте определим адрес возврата при изменении здоровья, то есть при DL==1, добавим к бряку "к здоровью 4" условие: dl==1 , возобновляем выполнение, уменьшаем здоровье и останавливаемся. По адресу возврата в стёке переходим в код и выше видим вызов (косвенной адресации):

call qword ptr ds:[rax+480]

Установим бряк и подпишем его как "к здоровью 5" а так же бряк на начало функции и подпишем "к здоровью 6". Деактивируем все бряки кроме "к здоровью 6" , возобновим игру и уменьшим здоровье, произойдёт остановка. По адресу возврата в стёке переходим в код и выше видим вызов.

call lf-win64-shipping.141458AF0 | к здоровью 7

Установим бряк и подпишем как "к здоровью 7" и бряк в начало функции с комментом "к здоровью 8". Начало этой функции такое:

mov qword ptr ss:[rsp+20],r9 | к здоровью 8
push rbp
push rdi

Давайте все остальные бряки деактивируем, чтоб не проходить по ним при каждой остановке. Вы спросите чего мы ищем и не пытаемся патчить в уже найденном коде? Мы ищем самыё первый вызов выполняемый при изменении здоровья ГГ. Вы можете попробовать патчить уже найденные участки кода (для того мы и тренируем интеллект ;-), но некоторые приводят к падению Unreal, другие к сбоям. Мы попробуем отсечь всё дерево вызовов. И так, у нас активен один бряк "к здоровью 8" и тут интересный момент, он срабатывает только на уменьшение здоровья, проверьте прыгнув с сарая и если так - мы на верном пути.

Остановка происходит один раз с одним адресом возврата. Перейдём к нему в коде, установим бряк F2 и комментарий "к здоровью 9", а прочие бряки отключаем.

Таким реверсом мы проходим ещё несколько вызовов:

call lf-win64-shipping.1413DA320 | к здоровью 9
...
push rbp | к здоровью 10

call qword ptr ds:[rax+598] | к здоровью 11
...
mov qword ptr ss:[rsp+10],rbp | к здоровью 12

call lf-win64-shipping.143165160 | к здоровью 13
...
push rbx | к здоровью 14

и приходим к вызову - который снова работает в цикле, то есть срабатывает постоянно, вне зависимости от действий ГГ:

call qword ptr ss:[rbp+D8] | ???

Кому то покажется излишним, такое количество бряков и комментов, но поверьте, когда идёт сложная работа и голова начинает уставать и запутываться, а особенно при сбоях, тщательное документирование шагов очень помогает распутаться :-).

А теперь попробуем по патчить и по обходить найденные вызовы. Отключаем все бряки. Остановим выполнение по F12 и перейдём к бряку "к здоровью 13" который установлен на вызов call lf-win64-shipping.143165160, щёлкнем ПКМ по этому вызову и в меню Двоичные операции-> Заполнить командами NOP , в окошке размер нажимаем OK

-43

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

А давайте проверим, может ли ГГ по прежнему лечится. Если забыли, то активировав бряк "запись здоровья" и остановившись на нём мы получим в RCX адрес структуры с данными ГГ а в RCX+0x100 число здоровья, останавливаемся на нём и переходим в дамп по адресу в RCX

-44

При полном здоровье - там число 42С80000, изменим C8 на 00, двойной щелчок мышкой по C8 и в окне редактирования меняем на 00

-45

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

call lf-win64-shipping.14132A320 | к здоровью 9

И протестируем оба типа урона, падение и от зомбей. Если хотите разобраться как получается значение здоровья, по трассируйте обратно от "запись здоровья" обращая внимание на xmm регистры не забывая про значение в dl.

Но остаётся ещё урон от голода. Можно его оставить ради интереса, но если нужно - то.

Включим один бряк начало функции (запись здоровья), а условие установим такое rcx==00815FA880 && dl != 1 && dl != 2 && dl != 4

В RCX у вас будет свой адрес, а во dl не должно содержать 1, 2 и 4, так как эти цифры мы уже проверяли. Но придётся немного поиграть, голод срабатывает не сразу. Продвигаясь реверсом мы проходим уже обозначенные вызовы:

//условие бряка rcx==00815FA880 && dl != 1 && dl != 2 && dl != 4
mov qword ptr ss:[rsp+10],rbx | к здоровью 2

//условие бряка dl != 1 && dl != 2 && dl != 4
mov qword ptr ss:[rsp+8],rbx | к здоровью 4 + энергия

mov qword ptr ss:[rsp+8],rbx | к здоровью 6
...
call qword ptr ds:[rax+480] | к здоровью 5

и остановка на "к здоровью 6" приведёт нас к ( урон от голода )

comiss xmm0,xmm2 | сравнение
movss dword ptr ds:[rcx+198],xmm0 |
jb lf-win64-shipping.14118DD8C | jmp патч урон от голода
xorps xmm2,xmmword ptr ds:[143D771F0] |
mov dl,3 |
call lf-win64-shipping.141158AF0 | урон от голода

Заменив JB на JMP в переходе, мы заставим код всегда обходить вызов урона от голода.

Всё работает, сохраняем изменения.

-46

ПКМ в меню Исправления и в окне исправлений кнопка Исправить файл, сохраняем новый с именем LF-Win64-Shipping_p4.exe и архивируем его. Выгружаем игру из x64Dbg. Удаляем LF-Win64-Shipping.exe , а LF-Win64-Shipping_p4.exe переименовываем в LF-Win64-Shipping.exe и далее работаем с ним. А для того чтоб Ida увидела изменения File->Load file->Reload the input file . Можно пропатчить и урон от голода.

Теперь найдём где скрывается код отбирающий нашу энергию.

Мы знаем что значение энергии сохраняется в RCX+0x104, рядом со здоровьем RCX+0x100, то есть бряк "запись здоровья", через RCX приводит нас и к ячейке энергии. Активируем его, применим аптечку и остановимся. ПКМ в регистрах по RCX -> Перейти к дампу

-47

В данном случае rcx == 05B4F3A40 , а энергия в rcx+0x104 == 05B4F3B44. Отключаем все бряки и устанавливаем на адрес энергии аппаратный бряк на запись DWORD, возобновляем игру и подпрыгиваем. Помним - что при апппаратном бряке на запись остановка происходит после. Остановка здесь:

cmp r8b,2 |
jne lf-win64-shipping.14133BA34 |
movss xmm0,dword ptr ds:[rcx+104] |
movss dword ptr ds:[rcx+104],xmm3 | запись энергии
mov byte ptr ss:[rsp+20],r8b | остановка здесь

Мы внутри функции подписанной нами ранее как "начало функции (запись здоровья)". В RCX адрес нашей структуры а DL==2. Видимо ключ 2 в DL означает обработку энергии ГГ. Строкой выше поставим бряк и подпишем как "запись энергии" и сразу отключим бряк. Продолжаем по F9 и видим что аппаратный бряк срабатывает несколько раз на одном коде. Проверим, откуда производятся вызовы. Убираем аппаратный бряк и активируем бряк "начало функции (запись здоровья)". Так как этот бряк с условием - изменим его,

rcx==05B4F3A40 && dl==2 , продолжим игру и заставим ГГ подпрыгнуть. После остановки, переходим по адресу возврата в стёке и попадаем на отмеченную ранее

call lf-win64-shipping.14133B9D0 | к здоровью 1

а начало этой функции

mov qword ptr ss:[rsp+10],rbx | к здоровью 2

активируем бряк "к здоровью 2" и так как он с условием, изменим его на rcx==05B4F3A40 && dl==2 , а предыдущий бряк отключим. Возобновим игру и заставим ГГ подпрыгнуть. Остановка на "к здоровью 3", а начало функции - "к здоровью 4", дополним описание - "к здоровью 4 + энергия" . Бряк на "к здоровью 4 + энергия" имеет условие, подправим его на dl==2 и подпрыгиваем ГГ. Если понажимать F9, то видно, что первая остановка происходит по одному адресу, а несколько следующих по второму адресу. давайте проверим первый. Повторим прыжок и при первой остановке, переходим по адресу возврата в стёке.

push rbx | к энергии 2
...
call qword ptr ds:[rax+480] | к энергии 1

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

cmp byte ptr ds:[rbx+F0],3 |
jne lf-win64-shipping.1412ABB4E |
...
call lf-win64-shipping.1413895E0 | к энергии 3

ставим на него бряк и коммент "к энергии 3". Давайте посмотрим на начало функции, не далеко от начала находится сравнение чего то с 3

cmp byte ptr ds:[rbx+F0],3

и условный переход jne lf-win64-shipping.1412ABB4E , который обходит почти всё содержимое функции включая и наш вызов. А давайте изменим его на без условный JMP. Сначала поставим на переход бряк и подпишем как "патч энергии прыжка", а потом нажав Пробел изменяем его.

-48

Отключаем бряк "патч энергии прыжка", возобновляем игру и прыгаем. Энергия перестала тратится. Не ленимся сохранять наши шаги в текстовый файл с текущей главной базой.

Если сделать резкий уклон, бросок метательного оружия, махать топором или быстрый бег, то энергия продолжит тратиться. Поправим всё это. Так как прыжок уже пропатчен, отключаем бряк "к энергии 3", а к "к энергии 2" активируем. Идём в игру, включаем спринт и останавливаемся на "к энергии 2". Перейдём по адресу возврата в стёке

call lf-win64-shipping.1413895E0 | к энергии спринт

ставим бряк и коммент "к энергии спринт". Если поизучать функцию повнимательнее, а ещё удобнее в Ida, станут видны потенциальные обходы этого вызова

0141384B5C | test cl,cl |
0141384B5E |
je lf-win64-shipping.141384C82 | обход энергия спринт ???
...
0141384C78 |
call lf-win64-shipping.141389380 | к энергии спринт
0141384C7D | movaps xmm6,xmmword ptr ss:[rsp+40] |
0141384C82 | movaps xmm1,xmm7 |

Попробуем изменить переход находящийся на 0x11A байт выше нашего вызова (адрес вызова минус 0x11A == адрес перехода). Изменим JE на JMP , отключим бряк "обход энергия спринт ???" и посмотрим что получилось. Похоже теперь мы можем спринтовать и прыгать без усталости. Можно убрать "???" из описания. Отключим все бряки, активируем один "к здоровью 4 + энергия" и сделаем резкое уклонение в игре. После остановки на бряке, переходим по адресу возврата в стёке

je lf-win64-shipping.141337C50 | обход к энергии уклонение
...
call qword ptr ds:[rax+480] | к энергии уклонение
0141337C50
| lea rcx,qword ptr ds:[rbx+178] | <- адрес возврата из стёка

Поставим бряк на вызов выше адреса возврата и прокомментируем как "к энергии уклонения". Несколькими строками выше видим условный переход огибающий наш вызов, проверим его в деле? Поставим на него бряк и коммент "обход к энергии уклонение", сразу деактивируем его и изменим JE на JMP. Деактивируем бряк "к здоровью 4 + энергия" и проверяем в игре - делая резкие уклонения, работает.

Можно предварительно сохранить изменения в файл LF-Win64-Shipping_p5.exe , через контекстное меню Исправления.

Теперь пропатчим метательное оружие. Активируем один бряк "к здоровью 4 + энергия" , достаём джавелин или сюрикен и бросаем.

Попадаем реверсом к уже известной функции "к энергии 2", активируем её, снова бросаем оружие, и при остановке переходим по адресу возврата

cmp qword ptr ds:[rax+rdx*8],r8 | сравнение чего то
jne lf-win64-shipping.14140F7A4 | обход энергии метательного ор
...
call lf-win64-shipping.1412BFD40 | к энергии метательное оружие
mov rcx,rbx | <- адрес возврата из стёка

Поставим бряк на вызов и подпишем "к энергии метательное оружие", а парой строк выше виден условный переход, давайте изменим его на без условный JMP. Возвращаемся в игру, бросаем всё что у нас есть и энергия не изменяется.

Ну и пропатчим энергозатраты на махание топором или ножом. Что то подсказывает, что можно сразу активировать бряк "к энергии 2" и он сработает, делаем так и машем в игре ножом или топором. Остановка, переход по адресу возврата в стёке:

je lf-win64-shipping.140D031F2 | обход к энергия махание ножом
...
call lf-win64-shipping.140E095E0 | к энергия махание ножом

и строкой выше наш вызов, бряк и комментарий "к энергия махание ножом", ну и четырьмя строками выше виден условный переход - который мы сделаем без условным. Меняем JE на JMP , возвращаемся в игру и проверяем взмахи ножом. И если всё норм, то сохраняем исправления как LF-Win64-Shipping_p5.exe , архивируем его, выгружаем игру из отладчика и переименовываем в LF-Win64-Shipping.exe .

Ну и остаётся износ оружия.

Переходя к другому объекту исследования, я обычно сохраняю в архив базу данных x64Dbg, её путь -> ..\x64dbg\release\x64\db\ файлы с именем отлаживаемой программы. Сохраним её на случай обнаружения ошибок в будущем, а вдруг мы что то упустили. С базой Ida поступаю также, она хранится рядом с exe-файлом отлаживаемой программы, если не изменялись настройки.

Для патча износа оружия, оружие сначала нужно раздобыть, пистолет или автомат обычно находится в городе, в большом кирпичном здании с двумя воротами.

-49

Или в полицейском участке не подалёку. Раздобыли, продолжаем. Отключаем все бряки, а игру можно приостановить F12. Запускаем ArtMoney, подключаемся к процессу, выбираем искать неизвестное значение , а тип с точкой 4 байта, запускаем поиск. После остановки поиска, возвращаемся в игру и стреляем до перезарядки (что б в инвентаре патроны начали уменьшаться), благо патроны у нас уже пропатчены. Приостанавливаем игру, в ArtMoney - Отсеять - Неизвестное значение - Уменьшилось, тип сточкой 4 байта и ждём. Повторяем шаги отсеивания. Следующие отсеивания идут заметно быстрее. Если количество результатов перестало уменьшаться, но их всё равно слишком много для анализа, убираем оружие и побегав немного - ищем не изменившееся значение. Когда количество результатов уменьшиться до вменяемого, добавляем их в правое окно и изучаем. Отрицательные, нулевые и слишком большие числа убираем и остаётся совсем не много.

-50

Чуть ранее я выяснил, что максимальное число здоровья оружия - 0x49000000 == 524288.00 float , и по этому ищем число близкое к 524288.00

-51

Но мы будем по честному. Берём из ArtMoney адрес - например 0001270C8170, переходим по нему в дампе, ставим аппаратный бряк на запись и делаем выстрел. Остановка здесь

-52

mov eax,dword ptr ds:[rbx+50] | получаем в EAX новое здоровье оружия
mov dword ptr ds:[rdi+60],eax | запись здоровья оружия (из EAX)
mov rdi,qword ptr ss:[rsp+38] | остановка

Видим, что строкой выше остановки, запись нового здоровья оружия по адресу rdi+60 из EAX. Поставим сюда бряк и прокомментируем как "запись здоровья оружия", а аппаратный бряк уберём. Возобновим игру, выстрелим ещё раз и остановимся на бряке. Посмотрим в регистр EAX и на адрес [rdi+60] в дампе . Выполним шаг с помощью F7 , число из EAX будет записано по этому адресу. Теперь с бряком "запись здоровья оружия", мы можем определять адрес хранения здоровья оружия. Если выбрать другое огнестрельное оружие, мы можем найти адрес хранения его здоровья, перейдя в дамп по rdi+60.

Теперь наша задача в том, что бы перед записью из EAX, поместить в EAX нужное нам значение ( 0x49000000 ). Если сделать это прямо в строке mov eax,dword ptr ds:[rbx+50] - то в виду длинны команды, мы испортим следующую инструкцию и нарушим код. По этому напишем небольшой патч.

Давайте пометим бряком инструкцию mov rdi,qword ptr ss:[rsp+38] и подпишем её как "возврат из патча".

После окончания функции - последнего оператора ret ,

add rsp,30
pop rsi
ret

имеется свободное место,

-53

которого хватит для патча. Смотрим адрес сразу за ret и сохраняем его в текстовик или запоминаем. Теперь идём к нашему бряку "запись здоровья оружия" и строкой выше - где mov eax,dword ptr ds:[rbx+50] нажимаем пробел и вписываем команду перехода к адресу за ret.

jmp 0x0140FFC7D2

Переходим к этому адресу и так же пробелом создаём код.

mov eax,0x49000000 | заносим в eax максимальное значение

в следующей строке мы восстанавливаем инструкцию записи

mov [rdi+0x60],eax

смотрим адрес строки "возврат из патча" и дописываем оператор перехода к этому адресу.

jmp 0x140FFC6C7

---------------Получим такой код ( адреса у вас будут другими ):

0140FFC6C1 | jmp lf-win64-shipping.140FFC7D2 | переход к патчу - за ret
...
0140FFC6C7 |
mov rdi,qword ptr ss:[rsp+38] | возврат из патча
...
0140FFC7D1 |
ret
0140FFC7D2 |
mov eax,49000000 |
0140FFC7D7 |
mov dword ptr ds:[rdi+60],eax
0140FFC7DA |
jmp lf-win64-shipping.140FFC6C7 | к "возврат из патча"

---------------Он же на рисунке:

-54

Отключаем все бряки, возвращаемся в игру, стреляем и наблюдаем в дампе за адресом хранящим здоровье оружия. Если всё сделано правильно, там будет 49000000. Проверим работу с другим оружием. Установим бряк на строку mov dword ptr ds:[rdi+60],eax в нашем патче, выстрелим и после остановки, перейдём по адресу rdi+60. Отключим бряк и постреляв, убедимся - что у другого оружия хранится 49000000. Если всё норм, сохраняем исправления как LF-Win64-Shipping_p6.exe и далее сохраняем рабочий exe, переименовываем LF-Win64-Shipping_p6.exe d LF-Win64-Shipping.exe, проверяем. Порядок.

Те - кто только решил изучать взлом и отладку в Windows, лучше начать с курса по OllyDbg от Рикардо Нарвахи на wasm.ru и продолжить курсом по IdaPro от него же.

Вот, курсы по взлому от Рикардо Нарвахи в формате html в архивах:

Учебник OllyDbg Рикардо Нарваха Zip.

Учебник IdaPro Рикардо Нарваха Rar (пароль 123).

пы.сы. А с Cheat Engine всё было просто, он глючил при ресурсоёмких операциях при нехватке места на диске - где он сохраняет свои файлы ( по умолку системный Temp). И например, при поиске неизвестных значений, а их бывают миллионы, его файлы сильно раздуваются. Решение - в Настройки -> Настройки поиска -> Расположение временных файлов. Указать на диск - где места достаточно.

Вроде всё. Всем удачи.

-55