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

Взлом игр (андроид). Ковыряем SkyForceReloaded_1.96-(200150).apk

Привет всем.
Заглядывал недавно один подписчик, прочитав статью "Взлом игр, ковыряем Night of the Dead v2.0.5.7" и попробовал таким же образом взломать игру для андроид на эмуляторе, ну и я решил тоже слегка поковырять игру для андроид, SkyForceReloaded_1.96 - о чём и статья ;-) Для начинающих, опытным просьба пинать без мата. Играл я в эту штуку ранее и помню - что там и ломать то особо незачем , разве что можно подкрутить себе количество здоровья ради спортивного интереса, чем и займёмся. Игра написана на Unity - а значит нам предстоит анализировать и патчить библиотеку \lib\arm64-v8a\libil2cpp.so - содержащую логику игры, а для этого нам понадобятся: 1) BatchApkTool - для декомпиляции/рекомпиляции apk-файла игры
2) Il2CppDumper или Il2CppDumper.GUI- для получения дампа классов
3) dnSpy - для анализа классов полученных Il2CppDumper
4) возможно Hex-редактор (на пример winhex или HexEditor)
5) к сожалению idaPro, так как free отказывается анализировать что то - кроме кода для x
Оглавление

Привет всем.

Заглядывал недавно один подписчик, прочитав статью "
Взлом игр, ковыряем Night of the Dead v2.0.5.7" и попробовал таким же образом взломать игру для андроид на эмуляторе, ну и я решил тоже слегка поковырять игру для андроид, SkyForceReloaded_1.96 - о чём и статья ;-)

Для начинающих, опытным просьба пинать без мата.

Играл я в эту штуку ранее и помню - что там и ломать то особо незачем , разве что можно подкрутить себе количество здоровья ради спортивного интереса, чем и займёмся.

Игра написана на Unity - а значит нам предстоит анализировать и патчить библиотеку \lib\arm64-v8a\libil2cpp.so - содержащую логику игры, а для этого нам понадобятся:

1) BatchApkTool - для декомпиляции/рекомпиляции apk-файла игры
2)
Il2CppDumper или Il2CppDumper.GUI- для получения дампа классов
3)
dnSpy - для анализа классов полученных Il2CppDumper
4) возможно Hex-редактор (на пример winhex или
HexEditor)
5) к сожалению idaPro, так как free отказывается анализировать что то - кроме кода для x86, а нам нужно arm64
6)
Nox Android-Player (android эмулятор) - для тестирования того - что мы наворотим в коде. Или любым другим эмулятором или способом, например на живом смартфоне.

И Notepad++ не помешает.

Ну и подопытный - SkyForceReloaded_1.96.zip - внутри игра SkyForceReloaded_1.96-(200150).apk и архив SkyForceReloaded_1.96-(200150).zip для распаковки его в /sdcard/Android/obb

Скачиваем всё что требуется по ссылкам, благо почти всё бесплатное, а на счёт ida решать вам ;-) Так же рекомендуется установить питон 2,7 и плагин KeyPatch к Ida, сильно облегчает правку кода. ( об Ida и плагинах, читайте в цикле обучающих статей от Рикардо Нарвахи )

Сразу скажу - что весь процесс происходил в Windows7Maximum_x64, так как под W10 и w11 - у меня эмуляторы начисто отказались запускать игры.

Подготовка.

Создадим отдельную папку, например atest - где и будем творить наше безобразие. В этой папке распакуем BatchApkTool (у меня - BatchApkTool375.7z). Ну и не лишним будет полистать форум на 4pda по теме его применения. Распаковываем dnSpy ( dnSpy-net-win64.zip ) и Il2CppDumper.GUI.

У нас в atest 3 папки:

BatchApkTool\
dnSpy\
Il2CppDumper.GUI.2.1.0\

В папке Il2CppDumper.GUI.2.1.0 - создадим ещё 2 папки ..\1 и ..\2 - о них позже.

Устанавливаем Nox (или другой андроид эмулятор), кнопкой "Добавить эмулятор" - создаём Android 7(x64) и проверяем в работе. Что важно - это режим ROOT, который в Nox устанавливается одной галкой в настройках виртуальной машины, он может понадобится к примеру для сохранения или распаковки сохранений игры в системной папке \data\data

-2

В свеже установленный Андроид7, я установил:
1)
SD_Maid_Pro_v5.5.4(50504)P.apk - для отключения и настройки ненужных системных программ и сервисов
2)
ES_File_Explorer_Pro-vPro_1.1.4.1-Modv2.apk - для более удобной работы с файлами
3)
Nova_Launcher-Prime-v6.2.19_b62019-Mod.apk - заменяет стандартный ланчер и не тянет рекламу.

Устанавливаем Nova_Launcher-Prime, назначаем его по умолчанию, а потом замораживаем стандартный с помощью SD_Maid_Pro.

-3

Начинаем.

И так, андроид работает, возвращаемся в windows. Копируем SkyForceReloaded_1.96-(200150).apk в BatchApkTool\_INPUT_APK\ и запускаем BatchApkTool.exe, выбираем язык (8) , читаем меню команд, набираем 81 для выбора нашего файла игры. Для декомпиляции вводим 1 и Enter, игра декомпилируется.

-4

и в папке BatchApkTool\_INPUT_APK\ появляется папка SkyForceReloaded_1.96-(200150) с декомпилированным содержимым apk-файла. Зайдём туда и удалим всё из папки _backup, оставив её пустой. Теперь идём в папку ..\lib\arm64-v8a\ и видим наш файл libil2cpp.so - это и есть наша цель. В unity-играх андроид libil2cpp.so - это аналог файла Assembly-CSharp.dll в сборках Unity-игр для Windows.

Теперь нам нужно проанализировать его содержимое, но dnSpy переварит только Assembly-CSharp.dll. Для получения дампа классов в libil2cpp.so - мы используем Il2CppDumper.GUI, он создаст для нас "фиктивный" Assembly-CSharp.dll из libil2cpp.so, с объявлениями классов и переменных и мы сможем их поизучать. Скопируем 2 файла ..\lib\arm64-v8a\libil2cpp.so и ..\assets\bin\Data\Managed\global-metadata.dat в Il2CppDumper.GUI.2.1.0\1\ и запустим Il2CppDumper GUI x64.exe В его окне, с помощью кнопок Select нужно выбрать файлы libil2cpp.so и global-metadata.dat из папки Il2CppDumper.GUI.2.1.0\1, а так же указать папку для вывода результатов, это папка Il2CppDumper.GUI.2.1.0\2. Нажимаем Start dumping

-5

После процесса дампинга, в папке ..\2 появятся разные файлы, в том числе и ..\DummyDll\Assembly-CSharp.dll, о них в процессе. В файле dump.cs описание классов с адресами RVA и Offset, будем сверяться с ним при необходимости.

Запускаем dnSpy, идём в меню Файл->Открыть и открываем atest\Il2CppDumper.GUI.2.1.0\2\DummyDll\Assembly-CSharp.dll

-6

dnSpy автоматически подгружает связанные библиотеки.

Начинаем анализ.

Когда не знаешь чого искать, на ум приходят простые вещи вроде health, player, damage и подобные - вот с них и начнём. Напишем в поле поиска health и посмотрим чего найдётся

-7

А нашлось много чего и в нижнем поле с права мы видим присутствие некоего класса Player зелёным цветом. Щёлкнем по нему два раза и в левом поле выделение перейдёт на строку с классом Player, развернём его содержимое и посмотрим что есть

-8

Различные методы, события и члены класса Player, а на счёт health не особо. Щёлкнем в левом поле по классу Player, в правом верхнем откроется его содержимое, скопируем весь текст например в Notepad++ и поищем health

Находим:
public static event Action<float, Player> EventHealthChanged
protected virtual bool HasHealthRegen
protected virtual float HealthRegenSpeed
и всё, среди членов (переменных) тоже нет никого со словом
health.

Поищем в левом поле Событие EventHealthChanged, они розовые, видим там два метода add_EventHealthChanged и remove_EventHealthChanged Щёлкнем по первому и посмотрим аргументы public static void add_EventHealthChanged(Action<float, Player> value) тоже не особо информативно. А давайте поищем слово damage , вводим его в правой строке поиска и листаем результаты

-9

Находим метод Player.TakeDamage и понимаем - что это получение урона. Найдём его слева и откроем. Смотрим на его аргументы, а среди них dmg, fEnergyShieldPenetration, energyShieldBreaking

dmg - видимо число урона, но что за Shield в двух аргументах, может оно есть здоровье (health), поищем его. Набираем в строке поиска Shield и ищем всё что относится к Player. Много всего, и среди всего находим Player.Shield

-10

Если покрутить в низ левое поле, в списке Player можно найти Shield с методами set_Shield и get_Shield - похоже эти методы для записи и считывания значения Shield, но где переменная что хранит это значение? Давайте пролистаем левое поле ещё ниже, к списку членов(переменных,констант) класса Player и поищем что то содержащее Shield

-11

Их несколько, а среди них: m_fCurrentShield, m_fCurrentMaxShield и прочие, но похоже что m_fCurrentShield - текущее значение, а m_fCurrentMaxShield - максимально возможное. Щёлкнем по m_fCurrentShield и в правом поле видим Offset 0x21C

[Token(Token = "0x40016AF")]
[FieldOffset(Offset = "0x21C")]
protected float m_fCurrentShield;

- если не ошибаюсь смещение относительно начала класса Player, а для нас это число - по которому мы идентифицируем эту переменную в коде idaPro И если мы хотим подправить количество здоровья, то видимо нужно увеличить число - которым инициализируется m_fCurrentShield при старте. Возможно что наша переменная инициализируется в функции содержащей слово Init (инициализация), поищем их в классе Player. Их всего три- Init(), InitFirerate(), InitShipEquipment(), последняя более всего подходит по названию - (Инициализировать судовое оборудование) Щёлкнем по нему и посмотрим адрес RVA = "0x77DD58".

А внимательные исследователи наверно заметили наличие в членах класса Player переменной - god_mode
[FieldOffset(Offset = "0x28")] public bool god_mode;

и если поискать по слову god, то находим функции:
GameSettings.
get_GodMode() и GameSettings.set_GodMode() , но об этом после и это слишком легко ;-)

Если бы мы ковыряли unity-игру для windows, то могли бы править и сохранять код реальной - игровой- Assembly-CSharp.dll прямо в dnSpy.

Что ж, пришло время открыть idaPro.

Тем у кого затруднения с ida- вот ссылки на самоучитель от Рикардо Нарвахи на wasm.in или rar-архив- Учебник IdaPro Рикардо Нарваха в html (пароль 123).

Приступаем к поиску и патчингу

Запускаем IdaPro, открываем файл libil2cpp.so и ожидаем окончания анализа. Нажмём в ida клавишу G , введём RVA = "0x77DD58" адрес InitShipEquipment() и перейдём в неё. С помощью клавиши N переименуем функцию в Player_InitShipEquipment и с помощью F2 поставим бряк, для быстрого нахождения если что.

Если не забыли, мы ищем где инициализируется m_fCurrentShield, вернёмся в dnSpy, щёлкнем в левом поле m_fCurrentShield и посмотрим справа его Offset = "0x21C". Поищем в открытой в Ida функции Player_InitShipEquipment присутствие этого числа. Так как функция не маленькая, можно в режиме графа посмотреть адреса начала и конца функции, перейти пробелом в строковой режим ( режим листинга ), выделить всё с помощью меню Edit->Begin Selection,скопировать Ctrl+C, вставить в Notepad++, найти число 0x21C и отменить выделение в ida Edit->Abort Selection. Находим 077E588 STR S0, [X19,#0x21C] - это запись содержимого регистра S0 в нашу переменную m_fCurrentShield находящуюся по смещению 0x21C

-12

На скриншоте :
1 - загрузка в регистр
S8 какого то значения из [X8 + смещение 0x20] (в X8 - наверно адрес начала класса Player)
2 - загрузка в регистр
S0 какого то значения из [X8 + смещение 0x28]
3 - умножить
S8 на S0 и поместить в S0
4 - запись значения из
S0 в [X19 + 0x218] - 0x218 = m_fCurrentMaxShield, видим в dnSpy
5 - запись значения из S0 в [X19 + 0x21С] - 0x21С = m_fCurrentShield, видим в dnSpy

То есть, чтобы увеличить число здоровья - надо загрузить желаемое число в регистр S8 на скриншоте под номером 1. Следует учитывать что регистры Sx хранят числа в формате float , число с точкой. И функции для работы с ними примерно такие: FMOV, FMUL, FSUB, FDIV, ... Если поместить в S8 достаточно большое число, то мы добьёмся увеличения здоровья нашего драндулёта при записи S0.

Давайте нажмём на иконку сохранения базы данных в ida и после скопируем в резервную папку файл базы ida libil2cpp.so.i64 и подопытную библиотеку libil2cpp.so - для возможности восстановления при неудачных экспериментах, сохранили в сторонку - продолжаем.

Если в Ida установлен плагин KeyPatch, то щёлкаем правой кнопкой по строке 0x77E558 , в контекстном меню Keypatch->Patcher в окне патчера в строке Assembly пишем FMOV S8, #31.0 и нажимаем кнопку Patch и закрываем окно кнопкой Cancel. Без плагина, идём в меню Options->General... и в окне настроек на вкладке Disassembly в поле Number of opcode bytes вносим количество отображаемых опкодов 8

-13

Теперь в дополнение к ассемблерному коду мы видим и опкоды, тоже можно включить и в строковом режиме ( листинга ).

-14

Для редактирования без KeyPatch - выделяем нужную строку, идём в меню Edit->Patch programm ->Change byte... Видим - что нашей команде соответствуют 4 первые кода в окне Patch Bytes

-15

Меняем их на такие 08 F0 27 1E - получаем FMOV S8, #31.0

-16

Теперь запишем изменения в файл Edit->Patch programm ->Apply patches to input file... В открывшемся окне, в строке Input file должен быть путь к BatchApkTool375\_INPUT_APK\SkyForceReloaded_1.96-(200150)\lib\arm64-v8a\libil2cpp.so нажимаем ОК и в окне ida Output сообщает что патч выполнен - Applied 3/3 patch(es)

Закрываем ida без сохранения базы данных, файл libil2cpp.so.i64 с пометками мы сохранили ранее, а этот удалим перед рекомпиляцией apk. Идём в папку BatchApkTool\_INPUT_APK\SkyForceReloaded_1.96-(200150)\lib\arm64-v8a\ и удаляем файл базы данных ida libil2cpp.so.i64

Удаляем SkyForceReloaded_1.96-(200150).apk из BatchApkTool\_INPUT_APK\ оставляя только папку SkyForceReloaded_1.96-(200150) с исходниками для пересборки.
Запускаем BatchApkTool.exe ,в поле действие выбираем
3 - для рекомпиляции и Enter, ожидаем. После окончания, выбираем 4 и Enter чтобы переподписать файл apk. Пересобранный apk находится в BatchApkTool\_OUT_APK\ Закрываем BatchApkTool и запускаем андроид эмулятор, у меня Nox.

Устанавливаем наш исправленный BatchApkTool\_OUT_APK\SkyForceReloaded_1.96-(200150).apk в андроид эмуляторе. Файловым менеджером (ES Commander Pro) распаковываем архив SkyForceReloaded_1.96-(200150).zip в /sdcard/Android/obb и запускаем игру. Первый раунд тестовый и в нём урон и так не большой, проходим его, погибаем, начинаем второй, ловим все пули и смотрим на скорость убывания здоровья. Если всё сделано аккуратно, то тушкой драндулёта можно остановить около 40 или 50 попаданий.

Также можно патчить опкоды libil2cpp.so с помощью Hex-редактора, например Hex Editor Neo, если например из анализа в dnSpy стало известно где патчить,

-17
-18

вот только анализ для патча мы вряд ли проведём в простом Hex-редакторе.

Теперь по поводу god_mode. Запустим dnSpy и наберём в поиске god, среди результатов будет функция get_GodMode() с адресом RVA = "0x9A3DAC" принадлежащая классу GameSettings Вернём непатченный libil2cpp.so из резервной папки и откроем в ida. Клавишей G перейдём по адресу 0x9A3DAC и увидим такой код:

il2cpp:00000000009A3DAC E0 03 1F 2A MOV W0, WZR ; заносит в W0 ноль
il2cpp:00000000009A3DB0 C0 03 5F D6 RET ; возврат

то есть эта функция get_GodMode возвращая ноль - не включает GodMode и если мы поместим в W0 1 - то GodMode будет активирован. Но в этом случае нам не удастся пройти первый раунд, так как там следует умереть а этого не получится. Для решения этой проблемы - проходим первый раунд в непатченном режиме, идём в андроид файловым менеджером в /data/data/ и архивируем папку pl.idreams.SkyForceReloaded2016, а после установки вновь пропатченного apk распаковываем этот архив и не забываем распаковать и SkyForceReloaded_1.96-(200150).zip в /sdcard/Android/obb

Итак, в функции get_GodMode заменяем WZR на #1

il2cpp:00000000009A3DAC 20 00 80 52 MOV W0, #1 ; заносим в W0 1
il2cpp:00000000009A3DB0 C0 03 5F D6 RET ; возврат

Применяем патч, удаляем файл libil2cpp.so.i64 из исходников игры, пересобираем BatchApkTool, подписываем, тестируем на эмуляторе.

Ну и можете поковырять в функциях Player.TakeDamage RVA = "0x767124" и Player.set_Shield RVA = "0x770C70" - ориентируясь на запись в переменную m_fCurrentShield --> STR S0, [X19,#0x21C]

//C# код функции Player.set_Shield к примеру:
public void set_Shield(float value)
{
this.m_fCurrentShield = Mathf.Clamp(value, 0f, this.m_fCurrentMaxShield +
this.m_fMaxExtraShield);
if (Player.EventHealthChanged != null)
{
Player.EventHealthChanged(this.m_fCurrentShield /
this.m_fCurrentMaxShield, this);
}
this.SetDamageMaterial(1f - this.Shield01());
}

А например в функции ShipPanel.EvHealthChanged RVA = "0x8826E0" - здоровье превращается в проценты. В общем есть где практиковаться.

-19

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