Взлом игр на движке Unity (Перевод)

Начинающий
Статус
Оффлайн
Регистрация
22 Фев 2020
Сообщения
9
Реакции[?]
0
Поинты[?]
0
Статья из зарубежного форума
Автор данной темы @
Пожалуйста, авторизуйтесь для просмотра ссылки.

База (Глава 1)
Существует довольно много игр, работающих на движке Unity (например, Rust, Albion Online, Gwent, Strafe, Yooka-Laylee, Pokemon Go и многие другие), большинство из которых являются однопользовательскими играми. К счастью, это не имеет значения с точки зрения взлома, поэтому этот учебник можно применить для взлома как многопользовательских, так и однопользовательских игр Unity.
Когда я познакомился со взломом игр Unity, я сам начал создавать некоторые хаки для игр Unity (Human Fall Flat, DeadCore, Clustertruck, Dusk и некоторые другие) и получил массу удовольствия от процесса. В UC появилась новая игра Unity, Totally Accurate Battlegrounds, привлекающая к себе внимание, что дает прекрасную возможность поделиться с вами своими знаниями и подходом к взлому игр Unity, надеюсь, это позволит некоторым из вас создавать свои собственные взломы.
В этом уроке я сосредоточусь на играх Unity, использующих серверные части сценариев C#.

Движок
В отличие от других популярных движков, таких как Unreal или Source, движок Unity организует код в «скриптах», которые прикреплены к «GameObjects», которые, в свою очередь, организованы в «сцены». Это так называемый движок, основанный на компонентах. Позвольте мне разбить их простым способом:
- Сцены по сути являются уровнями/картами. Они содержат ландшафт (геометрию), молнии и игровые объекты.
- Игровые объекты — это в значительной степени «сущности», такие как реквизит, игроки или двери: они часто имеют какое-то тело и имеют «Трансформацию».
- Преобразования содержат информацию о позиции, вращении и масштабе. К каждому игровому объекту привязана трансформация, и наоборот.
- Скрипты — это фрагменты кода, прикрепленные к игровым объектам; они компоненты. У них могут быть настраиваемые переменные, и они могут выполнять свой код в каждом кадре, чтобы манипулировать своим GameObject (или другими в сцене). Они также могут взаимодействовать с другими скриптами.
Это очень грубое описание, нигде не полное и не точное. Тем не менее, это даст вам представление о том, как эти термины связаны друг с другом.

Редактор
При работе над хаками для игр Unity вы обычно не работаете с редактором. Так зачем утруждать себя изучением этого? Потому что это важно для понимания того, как думают и работают разработчики.
jq0Hdfe.png
1.Это сцена "Иерархия". Вот игровые объекты, которые вручную помещаются в сцену.
2.Представленный «Проект» содержит различные папки, в которых можно размещать плагины, модели, материалы, текстуры и т. д. Сейчас там только пустая папка, так как здесь пока нечего видеть.
3. «Сцена» просто предоставляет панель, показывающую текущую сцену. Можно перемещаться по сцене, выбирать объекты и управлять ими.
4. С правой стороны есть вид «Инспектор». Он показывает атрибуты текущего выбранного GameObject и его компонентов.


rftTCj5.png

1. В иерархии вы можете видеть, что я добавил несколько дочерних элементов (цилиндров) в куб.
2. В окне «Сцена» мы можем увидеть, как теперь размещены эти дочерние элементы.
3. Представление инспектора показывает преобразование выбранного объекта. Как мы узнали выше, он показывает положение, масштаб и поворот. Однако он показывает эти атрибуты относительно своего родителя!

Скрипты/Компоненты
Скрипты используются для добавления логики в геометрию. Логика написана в коде, а код написан на каком-то языке программирования. Unity предлагает скриптовые бэкенды для C# и Javascript, хотя мне пока не удалось найти ни одной игры, написанной на JavaScript.
Скрипты пишутся в отдельных классах, наследуемых от "MonoBehaviour". Как и в любом классе .NET, у них есть конструкторы и деструкторы, однако Unity предоставляет свои собственные методы, которые вызываются, если они найдены в классе:

void Start() вызывается, если GameObject включен
void Awake() вызывается до Start()
void Update() вызывается каждый кадр, может быть пропущен на несколько кадров, чтобы поддерживать FPS.
void FixedUpdate() вызывается каждый кадр, не будет пропущен
void LateUpdate() вызывается после всех остальных методов Update
void OnEnable()/OnDiable() вызывается, когда GameObject включен/отключен
void OnDestroy() вызывается при уничтожении GameObject (через GameObject.Destroy)
void OnGUI() вызывается при рисовании и позволяет сценарию использовать GUI-API (описано позже)

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


Код вращения объекта в Unity:
public class RotateScript : MonoBehaviour {
    public Vector3 rotationAxis;
    public float speed;

    void Update () {
    transform.Rotate(rotationAxis * speed * Time.deltaTime);       
    }
}
Он содержит Vector3, описывающий множители для каждой оси, используемые для вращения, и число с плавающей запятой, представляющее скорость вращения, применяемую к GameObject. В методе Update мы позволяем преобразованию вращаться с помощью ConfiguredAxisMultipliers * SpeedValue * SecondsSinceLastFrame.
Добавив этот скрипт к нашему объекту в Unity, он начнет вращаться со всеми своими дочерними объектами.

Получение компонентов / Нахождение объектов

Unity предоставляет две функции, очень полезные для взлома игр: «GameObject.FindObjectsOfType» и «GameObject.GetComponent».
- static T GameObject GameObject.FindObject<T>() будет перебирать все созданные экземпляры GameObject и возвращать компоненты указанного типа.
- T GameObject.GetComponent<T>() (и его производные) будет искать Компоненты данного типа в GameObject, для которого вы это вызвали.
Однако используйте эти функции с осторожностью: они занимают много времени и могут резко снизить ваш FPS, если использовать их слишком часто.

Реверс (Глава 2)
Теперь, когда вы знаете основы, мы перейдем к следующей части, обращению к играм Unity.
Используя простую программу dnSpy, которую вы можете найти на GitHub. Дает возможность декомпилировать dll и изменить ее.
Обычно эти файлы находятся в основной папке игры с подпапкой "*_Data", которая содержит кое-что интересное:

Управляемый — наиболее актуальный, содержит скомпилированные скрипты и библиотеки DLL движка.
Плагины — содержат дополнительные сторонние библиотеки (например, синтаксические анализаторы JSON или библиотеки Steam).
level*-files - Реальные уровни/сцены игры

В папке Managed вы найдете два файла: «Assembly-CSharp.dll» и «Assembly-CSharp-firstpass.dll».

Декомпиляция
В отличие от, например. C или C++, C# компилируется в байт-код (IL — «промежуточный язык»), который компилируется точно в срок при выполнении. Хотя это позволяет игре работать на любой платформе, на которой реализован такой компилятор, это также позволяет нам лучше понять код. Просто откройте «Assembly-CSharp.dll» и «Assembly-CSharp-firstpass.dll» в dnSpy, и он покажет:
ZCwG8PY.png
Он использует эту раскраску:

Желтый — пространства имен (namespace)
Зеленый - классы
Оранжевый - Методы
Поля — пурпурный
Статические элементы — того же цвета, что и нестатические элементы, но светлее.
Перемещаясь по сборке, вы можете выбрать класс и проверить его код, просто выбрав метод (например, «Gun.Shoot» в Totally Accurate Battlegrounds)
Когда вы нашли нужный вам метод или класс, кликните правой кнопкой мыши и выберете вариант изменения, который вам больше подходит: изменить класс, изменить метод. Изменим к примеру способ перезарядки, сделав ее бесконечной. Скомпилируйте dll обратно сохранив свои изменения в методе.

Внедрение пользовательских скриптов
Редактирование — это мощное средство, но оно имеет свои ограничения. Реализация новых функций вполне возможна, но невероятно неудобна при быстром прототипировании. Для этого (и, в конечном итоге, для более чистого кода) вы можете написать свои собственные скрипты и внедрить их в игру.
Вы захотите создать два класса:

Hack-класс (сам код вашего чита / трейнера) — наследуется от MonoBehaviour, содержит ваши функции
Loader (загрузчик) — предоставляет статический метод, который создает новый GameObject, добавляет ваш Hack-класс в качестве компонента и заставляет UnityEngine не уничтожать его при изменении уровня.

Для этого просто создайте новый проект в Visual Studio (C# — библиотека классов (.NET Framework)). Чтобы иметь возможность использовать любые игровые классы и классы, предоставляемые Unity, щелкните правой кнопкой мыши «Ссылки» в проводнике проекта и добавьте любые библиотеки DLL в подпапку «Управляемые» вашей игры.
Ваш класс Loader может выглядеть:

Loader:
public class Loader
{
    public static void Init()
    {
        Loader.Load = new GameObject();
        Loader.Load.AddComponent<Hax>();
        UnityEngine.Object.DontDestroyOnLoad(Loader.Load);
    }
    private static GameObject Load;
}
Вот пример вашего основного Hax класса:
hax class:
public class Hax : MonoBehaviour
{
    public void Start()
    {
        //Код при запуске dll
    }
    public void Update()
    {
        //Выполняется каждый тик (кадр)
    }
}
Скомпилировав вашу dll, вы можете внедрить ее в игру используя Mono Injector, который также находится в открытом доступе GitHub. Интерфейс очень понятен и даже новичок в программировании поймет.

UI (Пользовательский интерфейс)
Движок Unity имеет простой, но функциональный пользовательский интерфейс. Я предлагаю прочитать документацию, пользовательский интерфейс позволяет сделать ваш хак простым в использовании (хотя и не очень красивым).
Вот небольшой вспомогательный класс, который я написал, который обеспечивает простой доступ к API (не забудьте вызвать его в методе OnGUI):
UI:
public static class UIHelper
{
    private static float
        x, y,
        width, height,
        margin,
        controlHeight,
        controlDist,
        nextControl
    public static void Begin(string text, float _x, float _y, float _width, float _height, float _margin, float _controlHeight, float _controlDist)
    {
        x = _x;
        y = _y;
        width = _width;
        height = _height;
        margin = _margin;
        controlHeight = _controlHeight;
        controlDist = _controlDist;
        nextControlY = 20f;
        GUI.Box(new Rect(x, y, width, height), text);
  
    private static Rect NextControlRect()
    {
        Rect r = new Rect(x + margin, nextControlY, width - margin * 2, controlHeight);
        nextControlY += controlHeight + controlDist;
        return r;
    }
    public static string MakeEnable(string text, bool state)
    {
        return string.Format("{0}{1}", text, state ? "ON" : "OFF");
    }
    public static bool Button(string text, bool state)
    {
        return Button(MakeEnable(text, state));
    }
    public static bool Button(string text)
    {
        return GUI.Button(NextControlRect(), text);
  
    public static void Label(string text, float value, int decimals = 2)
    {
        Label(string.Format("{0}{1}", text, Math.Round(value, 2).ToString()));
    }
    public static void Label(string text)
    {
        GUI.Label(NextControlRect(), text);
    }
    public static float Slider(float val, float min, float max)
    {
        return GUI.HorizontalSlider(NextControlRect(), val, min, max);
    }
}

ESP
Чтобы перевести любые позиции предметов в сцене на ваш экран и отрисовать их, вам сначала нужно будет получить ссылку на вашу камеру, используемую в игре. Вы можете получить его либо через «Camera.main», либо через «FindObjectOfType<Camera>», это, вероятно, не будет работать для игр, которые используют несколько камер одновременно. В этом случае вам нужно будет выяснить, какая камера является основной камерой.
После того, как вы получили эту ссылку, вы можете просто вызвать «WorldToScreenPoint» на камере — возвращенный Vector3 будет содержать 2D-координаты, используемые для рисования на экране, а координата Z указывает, находится ли цель на экране (z > 0) или за камерой (z < 0).\
ESP:
private void DrawCrosshair()
{
    var cam = FindObjectOfType<Camera>(); //Get camera
    var target = cam.transform.position + cam.transform.forward; //Calculate a point facing straight away from us
    var w2s = cam.WorldToScreenPoint(target); //Translate position to screen
    if (w2s.z < 0) //Behind screen?
        return; //Skip
    
    DrawDot(w2s.x, w2s.y); //Draw
}
Чтобы на самом деле нарисовать что-то на экране, вы можете использовать GUI-API и комбинировать простые вызовы Draw. Вот вспомогательный класс, который я написал для этого:
GUI Class:
public static class ZatsRenderer
{
    public static GUIStyle StringStyle { get; set; } = new GUIStyle(GUI.skin.label);
    public static Color Color
    {
        get { return GUI.color; }
        set { GUI.color = value; }
    }
    public static void DrawLine(Vector2 from, Vector2 to, Color color)
    {
        Color = color;
        DrawLine(from, to);
    }
    public static void DrawLine(Vector2 from, Vector2 to)
    {
        var angle = Vector2.SignedAngle(from, to);
        GUIUtility.RotateAroundPivot(angle, from);
        DrawBox(from, Vector2.right * (from - to).magnitude, false);
        GUIUtility.RotateAroundPivot(-angle, from);
    }
    public static void DrawBox(Vector2 position, Vector2 size, Color color, bool centered = true)
    {
        Color = color;
        DrawBox(position, size, centered);
    }
    public static void DrawBox(Vector2 position, Vector2 size, bool centered = true)
    {
        var upperLeft = centered ? position - size / 2f : position;
        GUI.DrawTexture(new Rect(position, size), Texture2D.whiteTexture, ScaleMode.StretchToFill);
    }
    public static void DrawString(Vector2 position, string label, Color color, bool centered = true)
    {
        Color = color;
        DrawString(position, label, centered);
    }
    public static void DrawString(Vector2 position, string label, bool centered = true)
    {
        var content = new GUIContent(label);
        var size = StringStyle.CalcSize(content);
        var upperLeft = centered ? position - size / 2f : position;
        GUI.Label(new Rect(upperLeft, size), content);
    }
}
 
Сверху Снизу