[Из песочницы] Реверс-инжиниринг и патч игры на 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().\u0014); strArray[index4] = name; int index5 = 4; string str5 = » by »; strArray[index5] = str5; int index6 = 5; string str6 = this.GetComponent().\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.0IL_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_0061ldarg.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 вы можете узнать из Википедии.

© Habrahabr.ru