Реверс-инжиниринг и патч игры на Unity3d

в 10:09, , рубрики: .net, unity3d, патч, реверс-инжиниринг

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

Предисловие

Однажды мне в руки попалась игра Unturned, построенная на движке Unity3d. Позже выяснилось, что она не лишена недостатков. Она находится в альфа версии, так что сервер даже не оповещает игроков о убийстве другого персонажа, что было не удобно в боях PvP. До этого случая у меня не было опыта работы с cil и реверс-инжинирингом c# приложений, тем более Unity.

Софт который нам понадобится

  • JetBrains dotPeek (или .NET Reflector, но он не смог дизассемблировать то, что смог dotPeek)
  • ildasm
  • ilasm
  • Редактор поддерживающий UTF-8 (например Notepad++)

Вскрываем программу

Код, который может быть интересен нам, находится в файле Assembly-CSharp.dll.
Сразу не раздумывая открываем его в dotPeek и видим примерно такую картину:

image

Пробуем декомпилировать весь код, загружаем проект в студию и получаем кучу ошибок. Visual Studio не смогла нормально обработать кучу переменных с названием в 1 UTF-8 символ.

Ладно, попробуем другим способом: открываем ildasm и загружаем в него наш файлик, нажимаем dump и сохраняем в удобное место. Мы получили кучу CIL кода. CIL код это как ассемблер для .NET, но намного легче.

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

Открываем консоль и пишем:

ilasm /DLL <путь до файла/файл.cil>

Ура! Получилось. Теперь мы можем менять код и компилировать его обратно.
Заходим в dotPeek и пишем в поиске die, находим некий Life::die(), смотрим код и понимаем что это не то.
Ладно, нажимаем правой клавишей на die, Usages of Symbol, и переходим в некий Life::damage()
В качестве аргумента эта функция принимает какую-то строку, нажимаем Usages of Symbol и переходим, например в shootPlayer(), смотрим в код:

string[] strArray = new string[7];
int index1 = 0;
string str2 = "You were shot in the ";
strArray[index1] = str2;
int index2 = 1;
string str3 = str1;
strArray[index2] = str3;
int index3 = 2;
string str4 = " with the ";
strArray[index3] = str4;
int index4 = 3;
string name = ItemName.getName(this.GetComponent<Clothes>().u0014);
strArray[index4] = name;
int index5 = 4;
string str5 = " by ";
strArray[index5] = str5;
int index6 = 5;
string str6 = this.GetComponent<Player>().u0841.u0887;
strArray[index6] = str6;
int index7 = 6;
string str7 = "!";
strArray[index7] = str7;
string str8 = string.Concat(strArray);
component.damage(num2, str8);

Это существенно облегчит нам работу, просто выведем «Имя DEAD (аргумент 2)».
Теперь ищем имя игрока, смотрим в тот же shootPlayer():

NetworkTools.kick(this.networkView.owner, "Kicking " + this.name + " for cheating their ammo.");

Теперь ищем тот же код в cil:

IL_002f: ldstr "Kicking "
IL_0034: ldarg.0
IL_0035: call instance string [UnityEngine]UnityEngine.Object::get_name()
IL_003a: ldstr " for cheating their ammo."
IL_003f: call string [mscorlib]System.String::Concat(string,
string,
string)
IL_0044: call void NetworkTools::kick(valuetype [UnityEngine]UnityEngine.NetworkPlayer,
string)

Осталось найти способ вывести сообщение в чат.
Ищем по cil файлу строку «connceted.»", находим вызов:

call void NetworkChat::sendAlert(string)

Патчим

Идем в Life::damage() в CIL.
Сразу после вызова die вставляем:

ldarg.0 //суём аргумент 0 в стек
call instance string [UnityEngine]UnityEngine.Object::get_name() //получаем имя(теперь оно в стеке)
ldstr " DEAD (" //суём строку в стек
ldarg.2 //суём аргумент 2 в стек(строка со способом смерти)
ldstr ")" //суём строку в стек
call string [mscorlib]System.String::Concat(string, //стыкуем их вместе
string,
string,
string)
call void NetworkChat::sendAlert(string) //отсылаем в чат

Компилируем ilasm, заходим в игру, хостим сервер, входим, умираем и… в чате аж 3 сообщения, причем 1-е наше а 2 других нет.
Оказалось что после смерти игра каждый проход шлет damage() с уроном равным 1.
Я решил просто сделать проверку на урон и получилось вроде того:

ldarg.1 //суем аргумент 1 в стек(количество урона)
ldc.i4.s 1 //суем число 1 в стек
ble.s IL_0061 //если меньше либо равно то перемещаемся на IL_0061
ldarg.0 //суём аргумент 0 в стек
call instance string [UnityEngine]UnityEngine.Object::get_name() //получаем имя(теперь оно в стеке)
ldstr " DEAD (" //суём строку в стек
ldarg.2 //суём аргумент 2 в стек(строка со способом смерти)
ldstr ")" //суём строку в стек
call string [mscorlib]System.String::Concat(string, //стыкуем их вместе
string,
string,
string)
call void NetworkChat::sendAlert(string) //отсылаем в чат
IL_0061:

Заключение

Более подробно про инструкции CIL вы можете узнать из Википедии.

Автор: AsGreyWolf

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js