0,0
рейтинг
9 сентября 2015 в 14:32

Разработка → Практическое руководство по взлому (и защите) игр на Unity перевод tutorial



Когда речь идёт о программном обеспечении, термин «взлом» зачастую ассоциируют с пиратством и нарушением авторских прав. Данная статья не об этом; напротив, я решительно не одобряю любые действия, которые прямо или косвенно могут навредить другим разработчикам. Тем не менее, эта статья всё же является практическим руководством по взлому. Используя инструменты и методы о которых далее пойдёт речь, вы сможете проверить защиту собственной Unity игры и узнаете, как обезопасить её от взлома и кражи ресурсов.

Введение


В основе взлома лежит знание: необходимо понимать особенности компиляции Unity-проекта, чтобы его взломать. Прочитав статью, вы узнаете, каким образом Unity компилирует ресурсы игры и как извлечь из них исходные материалы: текстуры, шейдеры, 3D-модели и скрипты. Эти навыки будут полезны не только для анализа безопасности проекта, но также для его продвинутой отладки. В связи с закрытостью исходного кода, Unity часто работает как «черный ящик» и порой единственный способ понять, что именно в нём происходит — это изучение скомпилированной версии скриптов. Кроме прочего, декомпиляция чужой игры может стать серьёзным подспорьем в поиске её секретов и «пасхальных яиц». Например, именно таким образом было найдено решение финальной головоломки в игре FEZ.



Находим ресурсы игры


Рассмотрим для примера игру, собранную под ОС Windows и загруженную через Steam. Чтобы добраться до директории, в которой находятся нужные нам ресурсы, откроем окно свойств игры в библиотеке Steam и в закладке «Local files» нажмём «Browse local files…».



Когда Unity компилирует проект под Windows, ресурсы всегда упаковываются по схожей схеме: исполняемый файл (например, Game.exe) будет находится в корне директории игры, а по соседству расположится директория, содержащая все игровые ресурсы — Game_Data.

Извлекаем текстуры и шейдеры


Большинство ресурсов Unity-проекта упаковываются в файлы проприетарного формата с расширениями .assets и .resources. Наиболее популярный на сегодняшний день инструмент для просмотра таких файлов и извлечения из них ресурсов — Unity Assets Explorer.



Графический интерфейс программы не отличается удобством, а также она страдает от нескольких критических багов. Не взирая на это, программа вполне способна извлечь большинство текстур и шейдеров из игры. Полученные в результате текстуры будут иметь формат DDS, который можно «прочитать» с помощью Windows Texture Viewer.

С шейдерами ситуация обстоит сложнее: они извлекаются в уже скомпилированным виде и, насколько мне известно, решений для их автоматической трансляции в удобочитаемый формат не существует. Тем не менее, это обстоятельство не мешает импортировать и использовать полученные шейдеры в другом Unity-проекте. Не забывайте, однако, что подобная «кража» нарушает авторские права и является актом пиратства.

Извлекаем 3D-модели


Трёхмерные модели в типовой Unity-сборке «разбросаны» по различным ресурсам, а некоторые из них и вовсе могут генерироваться во время игры. Вместо копания в файлах, существует интересная альтернатива — получить данные о геометрии прямиком из памяти графического ускорителя. Когда игра запущена, вся информация о текстурах и моделях, видимых на экране, находится в памяти видеокарты. С помощью утилиты 3D Ripper DX можно извлечь всю эту информацию и сохранить в формате, понятном 3D-редакторам (например, 3D Studio Max). Учтите, что программа не самая простая в обращении — возможно, придётся обратиться к документации.



Взламываем PlayerPrefs


PlayerPrefs — это класс из стандартной библиотеки Unity, который позволяет сохранять данные в долговременную память устройства. Он часто используется разработчиками для хранения различных настроек, достижений, прогресса игрока и другой информации о состоянии игры. На ОС Windows эти данные сохраняются в системном реестре по следующему пути: HKEY_CURRENT_USER\Software\[company name]\[game name].



С помощью стандартной утилиты regedit можно легко модифицировать любые значения PlayerPrefs, изменяя тем самым конфигурацию и статус игры.

Защищаем PlayerPrefs


Помешать пользователю редактировать значения в системном реестре мы не в силах. А вот проверить, изменялись ли эти значения без нашего ведома — вполне реально. В этом нам помогут хеш-функции: сравнив контрольные суммы хранимых данных, мы сможем убедиться, что никто и ничто, кроме нашего кода эти данные не изменяло.

public class SafePlayerPrefs 
{
    private string key;
    private List<string> properties = new List<string>();

    public SafePlayerPrefs (string key, params string [] properties)
    {
        this.key = key;
        foreach (string property in properties)
            this.properties.Add(property);
        Save();
    }

    // Вычисляем контрольную сумму
    private string GenerateChecksum ()
    {
        string hash = "";
        foreach (string property in properties)
        {
            hash += property + ":";
            if (PlayerPrefs.HasKey(property))
                hash += PlayerPrefs.GetString(property);
        }
        return Md5Sum(hash + key);
    }

    // Сохраняем контрольную сумму
    public void Save()
    {
        string checksum = GenerateChecksum();
        PlayerPrefs.SetString("CHECKSUM" + key, checksum);
        PlayerPrefs.Save();
    }

    // Проверяем, изменялись ли данные
    public bool HasBeenEdited ()
    {
        if (! PlayerPrefs.HasKey("CHECKSUM" + key))
            return true;

        string checksumSaved = PlayerPrefs.GetString("CHECKSUM" + key);
        string checksumReal = GenerateChecksum();

        return checksumSaved.Equals(checksumReal);
    }

    // ...
}


Приведенный выше класс — упрощенный пример реализации, работающий со строковыми переменными. Для инициализации ему необходимо передать секретный ключ и список PlayerPrefs-ключей, значения которых должны быть защищены:

SafePlayerPrefs spp = new SafePlayerPrefs("MyGame", "PlayerName", "Score");


Затем его можно использовать следующим образом:

// Сохраняем данные в PlayerPrefs как обычно
PlayerPrefs.SetString("PlayerName", name);
PlayerPrefs.SetString("Score", score);

spp.Save();

// Проверяем, редактировались ли значения
if (spp.HasBeenEdited())
    Debug.Log("Error!");


При каждом вызове метода Save, на основе значений всех параметров, ключи которых были переданы классу при инициализации, вычисляется и сохраняется контрольная сумма. Используя метод HasBeenEdited мы затем можем проверить, изменялись ли защищенные параметры без нашего ведома.

Взламываем исходный код


Для Windows-сборок Unity компилирует и сохраняет исходный код всех игровых скриптов в директорию Managed. Интересуют нас следующие библиотеки: Assembly-CSharp.dll, Assembly-CSharp-firstpass.dll и Assembly-UnityScript.dll.



Для декомпиляции и просмотра managed-кода .NET библиотек (коими и являются наши жертвы) существуют довольно удобные и при этом бесплатные утилиты: IlSpy и dotPeek.



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

Защищаем исходный код


Раз Unity не заботится о сохранности нашего кода — сделаем это сами. Благо, существует утилита, готовая автоматически зашифровать плоды нашего интеллектуального труда: Unity 3D Obfuscator.



И хотя программа отлично справляется со своими обязанностями, многие классы, адресуемые извне родной библиотеки, всё же не могут быть зашифрованы без риска нарушения связанности — будьте осторожны!

Взламываем память игры


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



Эта программа пользуется тем фактом, что разработчики игр очень редко защищают значения переменных. Рассмотрим следующий пример: в некой игре у нас есть 100 патронов; используя Cheat Engine, можно выполнить поиск участков памяти, которые хранят значение «100». Затем мы делаем выстрел — запас патронов составляет 99 единиц. Снова сканируем память, но теперь ищем значение «99». После нескольких подобных итераций можно с легкостью обнаружить расположение большинства переменных игры и произвольно их изменять.

Защищаем память игры


Cheat Engine столь эффективна от того, что значения переменных хранятся в своём изначальном виде, без какой-либо защиты. Серьёзно усложнить жизнь «читерам» довольно просто: нужно лишь немного изменить способ работы с переменными. Создадим структуру SafeFloat, которая послужит нам безопасной заменой стандартного float:

public struct SafeFloat 
{
    private float offset;
    private float value;

    public SafeFloat (float value = 0) 
    {
        offset = Random.Range(-1000, +1000);
        this.value = value + offset;
    }
	
    public float GetValue ()
    {
        return value - offset;
    }

    public void Dispose ()
    {
        offset = 0;
        value = 0;
    }

    public override string ToString()
    {
        return GetValue().ToString();
    }

    public static SafeFloat operator +(SafeFloat f1, SafeFloat f2) 
    {
        return new SafeFloat(f1.GetValue() + f2.GetValue());
    }

    // ...похожим образом перегружаем остальные операторы
}


Использовать нашу новую структуру можно следующим образом:

SafeFloat health = new SafeFloat(100);
SafeFloat damage = new SafeFloat(5);

health -= damage;

SafeFloat nextLevel = health + new SafeFloat(10);

Debug.Log(health);


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

Заключение


К сожалению, существует не так уж много способов защитить игру от взлома. Будучи установленной на пользовательское устройство, она фактически раскрывает все ваши текстуры, модели и исходный код. Если кто-то захочет декомпилировать игру и украсть ресурсы — это лишь вопрос времени.

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

Дополнительная информация


  • Unity3D Attack By Reverse Engineering: Интересная статья, описывающая распространённые ошибки безопасности при реализации систем подсчёта очков в играх на Unity;
  • disunity: Одна из лучших утилит для просмотра и извлечения ресурсов из Unity игр. К сожалению, она несовместима с последней версией движка;
  • Unity Studio: Программа для визуализации и извлечения 3D моделей. Также не работает с Unity 5.
Перевод: Alan Zucconi
Артём Советников @Elringus
карма
15,2
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (30)

  • +5
    Самая свежия версия Unity Assets Explorer лежит на дропбоксе. На Nexus'е версия немного устарела.
    Начал делать ее еще в январе 2013го для русификации игр на Unity. Первой ласточкой стала One Late Night.
    За перевод статьи спасибо.
    • 0
      Обновил ссылку в статье. Спасибо.
  • +2
    Для декомпиляции и просмотра managed-кода .NET библиотек (коими и являются наши жертвы) существуют довольно удобные и при этом бесплатные утилиты: IlSpy и dotPeek.

    Еще удобная программа .NET Reflector с плагином Reflexil. Помимо просмотра позволяет еще и править код. Что может использоваться как для взлома игр, так и активации всяческих чит-режимов или исправления багов.
    • 0
      Рефлектор платный, плюс ко всему его делали те же ребята, что пилят сейчас ILSpy, так что полагаю там этот режим тоже введут.
    • +1
      Я бы для декомпиляции IL рекомендовал использовать dnSpy, форк ILSpy от известного 0xd4d (автор Confuser, de4dot и многих других полезных продуктов).
      Там много полезных вещей для реверсера — отладка, редактирование без пересборки, более устойчивая к обфускации dnLib (вместо Mono.Cecil) и т.д.
  • +1
    Есть еще такой вариант, как проверить, играет ли пользователь со взломанной .apk-шки (т.е. используя патченную Assembly-CSharp.dll) — хранить хеш сборки на сервере для каждой версии. При старте игры вычисляется хеш локальной сборки, затем отправляется серверу. Сервер ее проверяет, и, если она неверная, запоминает это. Затем через определенное время включается банхаммер, и всем юзерам, от которых был прислан неверный хеш, присваивается бан, показывается окно «поди прочь» (или еще что-то, зависит от реализации). Конечно, вычисление и отправку хеша нет смысла класть в Assembly-CSharp — если в ней поломали уже что-то, то могут поломать и это, а значит нужно как можно меньше зависимости от c# и java. Вариант — сделать либу на JNI, которая этим занимается. Тогда ее использование сводится к строке System.loadLibrary(«you_native_library_name») (при использовании JNI_OnLoad), которую найти посложнее. Но и это не гарантирует полной защиты, конечно. Тем не менее планка взлома слегка повышается. Так же, отложенный банхаммер не дает сразу понять читеру, на основании чего он произошел.
    • +1
      Ничего не мешает изменить dll и отправлять корректный хэш.

      Недавно как раз отучал от защиты Битву чемпионов от kabam.

      1. На данные, отправляемые на сервер, вычисляется hmac и добавляется в сообщение.
      Соль зашита в игре.

      2. При логине происходит следующие действия
      Вычисляется sha1 для dll файлов в папке managed и отправляется на сервер
      Если хэш верен, то в ответ приходит соль, с некоторой модернизацией которой вычисляется hmac все тех же dll

      Все вычисления и логика встроены в so библиотеку.
      Расковыряв алгоритм, в классе защиты заменил вызовы внешней библиотеки на реализующий его код.

      Профит. Теперь можно менять тем же рефлектором все что нам нужно
      • 0
        Я не писал, что что-то этому мешает, вопрос лишь в сложности. C# сборку легче расковырять чем найти .so и расковырять ее. Соответственно, из .so не должны торчать концы (кроме как loadLibrary), и уж точно она не должна общаться или обмениваться данными с managed-кодом (в идеале). Опять же, расковырять все что угодно можно, конечно. Но иногда достаточно увеличить сложность взлома, и энтузиастов это сделать может уже не найтись. А могут и найтись :)
      • 0
        Если бы логику отправки хеша встроили в .so причем зашифровав содержимое каким AES-ом с ключем, забитым там же в native — врядли бы вы что-то с этим сделали.
        • 0
          Пффф, это только немного усложнит задачу
          • 0
            Напишите, как бы вы взломали? А то вы привели слишком много аргументов.
            • 0
              Я так понял вы имели ввиду зашифровать код, который отправляет хеш. Т.к. ключ зашит в саму либу, то просто ищем место когда расшифровывается код, оттуда берем ключ и адреса по каким идет расшифровка. Расшифровываем сами код, патчим, шифруем обратно, заменяем либу, профит. Ну это вариант для симметричного шифрования. А если взять например RSA с ключом более 1024 бит, то такой трюк не прокатит. Тогда делаем немного по другому. Мы можем расшифровать код и посмотреть что и как там делается, узнаем адрес по которому нужно пропатчить. Далее в либе после кода расшифровки втыкаем переход на свой код, который патчит в памяти уже расшифровываний код, и переходит к дальнейшему выполнению оригинального кода. Это самые простые решения в лоб, но их может быть намного больше.
    • 0
      Как бы вы не старались защитить целостность файлов на клиенте это невозможно. И взлом защит не занимает много времени.
      К примеру на ПК Eve Online считает хеши файлов и сравнивает их с образцом. Алгоритм зашит внутри своей библиотеки. Обходится довольно просто:

      1) Берется EasyHooks
      2) Вешается хук на fopen
      3) При первом(2,3,4) срабатывании, когда считается хеш, подсовывается оригинал, во всех остальных модифицированный файл

      Если файл считывается с диска один раз, то ищется функция хеширования и вешается на неё хук, туда идет оригинальный файл, во все остальные места уже модифицированный.
      • 0
        Разве я писал, что это возможно? Наоборот в конце подчеркнул, что вовсе нет
        • 0
          Вы не подчеркнули что это «принципиально невозможно», а все попытки только делают желания взломать тверже :)
          • 0
            «Расковырять можно все, что угодно» равно по смыслу «принципиально невозможно защитить», как по мне :) Я писал про то, что можно усложнить это по максимуму. Выводя проверку хешей в нативку, а баны и контрольные чеки различной логики на сервер. Если брать не сферический проект в вакууме, а реальный, этого часто бывает достаточно. Конечно, есть много факторов, которые на это влияют, в частности объем аудитории и тип проекта (оффлайн — онлайн — частично онлайн)
  • 0
    Нормально, спасибо за пост, еще бы «CHECKSUM» в реестре переименовать в популярный параметр который якобы используется в игре, и без реверса не будет понятно что hash вообще используется.

    Трюк — заводим switch case конструкцию, определяем первые 2-3 варианта (которые по хорошему стилю кодирования должны быть наиболее часто встречаемыми) с похожими на правду параметрами, а настоящая работа будет проходить в других ветках или вообще в default. Потом самому не запутаться, но это чуть сложнее обфускации на которую будет антиобфускация :)
  • 0
    Как раз начал изучать вопросы анти-читов для своей игры; статья очень в тему, спасибо! Кстати, не пробовали ли использовать AntiCheat Toolkit из Asset Store? Какие есть о нём отзывы?

    И ещё, интересно, как бы вы стали защищать взаимодействие с внешними сервисами? Вроде покупки каких-нибудь внутриигровых ништячков или выгрузки статистики из игры.
    • +3
      Клиент должен только совершать транзакцию и отправляет данные о ней на сервер для проверки.
      Логику, различные проверки и данные пользователя хранить там же. Ключевая статистика только от сервера.
      И всегда старайтесь выбирать для хранения типы данных, которые не позволяют параметрам применять отличные от задуманных значения. Опять же проверки, проверки и еще раз проверки. Все что приходит от клиента должно подвергаться сомнению.

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

      Опишу один из многочисленный фэйлов того же kabam в игре Герои камелота.

      В игре есть «хранилище» с различными итемами (в том числе картами героев). Для всех итемов хранится их количество, но используют для этого не целое число, а с точкой. При этом при использовании на сервер уходит количество итемов. Отсутствие проверки на сервере дает два замечательных чита. Первый — это использование количества в виде 0.0000001. Что есть по сути бесконечный итем. Второе — использование отрицательных значений, т.е. возможность клонирования.

    • 0
      Возможно уже неактуально, но вы упомянули Anti-Cheat Toolkit.
      Я был бы рад помочь, если у вас ещё есть по нему вопросы.
  • 0
    не много не в тему, но в защиту Ассетов ссылка
    • 0
      Эфемерная защита. Берем метод для дешифровки из приложения и получаем нормальный ассет
  • 0
    Автор статьи много полезных деталей опустил =(

    Стоило бы упомянуть, что уже сейчас вполне можно применять IL2CPP как отличное средство защиты от просмотра и редактирования вашего кода.
    Копаться в в коде, который хоть и предназначен для VM, но уже всё-таки нативный — то ещё «удовольствие» по сравнению с простым и доступным ковырянием IL.
    IL2CPP пока доступен не на всех платформах, но развитие и добавление поддержки новых платформ не стоит на месте.

    Кроме того, читеры ещё много чем увлекаются, применяют Speed Hack'и, Wall Hack'и, инжектят свои managed сборки прямо в Unity приложения и добавляют в вашу игру свои OnGUI менюшки, и это лишь малая часть.
    • 0
      в данный момент IL2CPP доступен только на Ios, на Android еще бета версия. а на остальных платформах вообще нету.
      • 0
        Вы забыли про WebGL, и я написал выше:
        IL2CPP пока доступен не на всех платформах, но развитие и добавление поддержки новых платформ не стоит на месте.

        Бэта под Андроид уже весьма стабильна (если сравнивать с первой публичной бэтой), что лишь прибавляет уверенности в том, что вскоре мы увидим IL2CPP и на других платформах.
        • 0
          гдеж это стабильно, когда при билде на андройд 5.2.2p2 ошибки летают?

          p.s скрин IL2CPP под webGL (ей богу не нашел)
          • 0
            Стабильно, если сравнивать с первой публичной бэтой.
            Я и не говорил, что он работает идеально и его можно смело использовать в продакшене. Но если сравнить то, что было в первой бэтке и то что есть сейчас — разница очень существенная.

            Да и 5.2.2 может не содержать последнюю версию IL2CPP под дроид, её надо в 5.3 тестировать для объективных результатов.

            p.s скрин IL2CPP под webGL (ей богу не нашел)

            WebGL билдится только через IL2CPP, что вы не нашли — не понятно.
            • 0
              5.3 сама по себе еще бетка, действительно будете компилировать под Android IL2CPP свои рабочие проекты? может 5.4 альфу тогда лучше юзать? (уже есть). Покуда IL2CPP выйдет под все платформы, уж больно очень много времени пройдет, а вскрыть как консервную банку билд под Windows не составит труда. Так что статья пока что более чем актуальна, тем более статья публиковалась 2 месяца назад, или тогда IL2CPP под android тоже был нормальным по вашему?
              p.s и в правду по умолчанию IL2CPP под WebGL используется. но Официальный релиз стабильной версии WebGL обещают к 5.4 версии.
              • 0
                Я знаю, что 5.3 ещё бэтка, но я также знаю, что вовсю готовится 5.3.0f1, т.е. не долго 5.3 в бэте оставаться осталось.
                И я ещё раз повторю, что не призываю собирать рабочие проекты под IL2CPP Android, я лишь отмечаю, что работа по IL2CPP постоянно идёт, появляется поддержка новых платформ, фиксятся тонны багов и т.д.
                Потому не упомянуть IL2CPP в статье в контексте защиты кода как-то неправильно.

                вскрыть как консервную банку билд под Windows не составит труда…
                статья пока что более чем актуальна

                Я и не говорил, что статья неактуальна. Я отмечал, что она неполноценна и автор много полезных моментов в ней не отметил.

                Кстати, всё описанное в статье никак не помешает вскрыть билд под Windows как консервную банку.
                Даже раздел касательно защиты кода довольно таки скуп. Если уж использовать обфускаторы, то что-то посерьёзнее и посвежее, например Eazfuscator последних версий, или свой билд ConfuserEx.

                статья публиковалась 2 месяца назад, или тогда IL2CPP под android тоже был нормальным по вашему?

                2 месяца назад был IL2CPP iOS, IL2CPP WebGL, и уже был IL2CPP Android, не важно в каком состоянии, важно что к тому моменту уже было очевидно, что IL2CPP продолжает своё «наступление» и его нельзя игнорировать.

                Официальный релиз стабильной версии WebGL обещают к 5.4 версии.

                Ну так отлично же! Хотя как вам наверняка известно, это не мешает многим разработчикам запускать проекты под WebGL уже сейчас.
                • 0
                  Поправка, 5.3 уже в RC стадии =)

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