[Разработка мобильных приложений, Разработка игр, Unity, Дизайн игр] Первые пять шагов для перелома ситуации с читерами в PvP-шутере
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Мы прошли долгий путь от появления в игре первых читеров до полного пересмотра подхода к разработке, чтобы создавать защищенные по умолчанию проекты. О том, как в игре появились читеры, я рассказал в прошлом материале. И там же привел список подзадач, которые выкатили одновременно, чтобы закрыть вопрос со взломами, — от обфускации кода до подсчета хеша всех библиотек и надежной системы бана.Итак, эти шаги:
- Обфускация.
- Хранение данных.
- Миграция прогресса.
- Система бана.
- Подсчет хеша всех библиотек.
- Защита от переподписывания версий.
- Photon Plugin.
- Серверная валидация инаппов.
- Защита от взлома оперативной памяти.
- Собственная аналитика.
- И одновременный релиз всех решений.
Сегодня поговорим про первые пять пунктов.Для удобства я пронумеровал все разделы, но напомню, что приведенные ниже решения мы разрабатывали и выкатили одновременно. Потому что постепенный ввод изменений сильно облегчит взломщикам отслеживание апдейтов. Шаг №1. ОбфускацияПервым делом ввели новые стандарты написания кода, позволяющие плагину обфускации сделать свое дело, и переписали под них всё приложение. Задача не особо сложная, но ресурсоемкая, потому что кодовая база к этому времени уже была довольно большая.Для защиты приложения от взлома обфускация играет большую роль, так как на всех платформах можно изменять код приложения, подменяя результаты выполнения различных свойств и методов тем или иным способом. И чем проще он читается, тем ниже порог для входа. Цена инапов, жизни, урон скорость и так далее — все это нужно обязательно скрывать.Мы используем плагин Beebyte Obfuscator. Но для работы плагинов обфускаторов надо придерживаться определенных правил оформления кода, иначе названия свойств и методов не будут скрываться.Какие-то из приведенных ниже правил были выведены изучением материалов о работе обфускаторов, что-то получили экспериментальным путем, ну а что-то нам подсказал разработчик плагина, с которым мы связывались при внедрении. Наш набор:
- internal вместо **public** и **private** или **internal** вместо **protected**.
- Все члены классов, помеченных [Serializable], не обфусцируются.
- Свести к минимуму использование Parse/ToString для enum (при обфускации результат, как правило, бесполезен), но если это все-таки необходимо, то помечаем атрибутом **[Obfuscation(Exclude = true)]**.
Например:
[Obfuscation(Exclude = true)]
public enum GameEventItemContainerContentType
{
None = 0,
SingleItem = 1,
ItemsCollection = 2,
Start = 3,
}
- По возможности заменяем `const` на `static`. Статики нельзя использовать в switch. Например, вместо internal const string A_B = "my_constant" делать internal static readonly string A_B = "my_constant" либо internal static string A_B { get{...} }.
- События анимаций — их нужно оставлять/делать public или помечать атрибутом [Obfuscation(Exclude = true)].
- Лямбды — имя лямбды включает имя того метода/класса, в котором она определена, вне зависимости от модификатора доступа метода или класса. По возможности нужно заменять лямбды методами.
- Kорутины в IL не обфусцируются. Поэтому их можно обфусцировать вручную, например, назвать vfg45_00.
Сейчас, когда под плагин переделано абсолютное большинство нашего кода, весь новый мы пишем согласно этим правилам. Также периодически пересматриваем дамп сборки со списком методов в проекте на предмет, нет ли каких-то важных необфусцированных методов. Отмечу минус использования обфускации — время сборки неминуемо увеличивается (у нас примерно на 30%), но польза несравнимо выше.На случай, когда надо получить сборку максимально быстро, мы добавили на билд-сервере возможность сборки без обфускации. Но обычно стараемся собирать с ней, потому что есть опасность пропустить баги связанные с обфускацией (обычно это места, где результат выполнения кода зависит от имени метода или энума). Шаг №2. Хранение данныхПри реализации общего плана по защите от взломов игры это был один из самых важных и в то же время масштабных пунктов. Важным, потому что без полного контроля над прогрессом игроков невозможно полноценно защититься от взломов. А масштабным, потому что более чем за пять лет жизни проекта в игре было огромное количество сущностей и функционала, каждый из которых хранил данные в собственном формате, сохранялся беспорядочно и не имел никаких ограничений по количеству обращений к диску на чтение и запись.Для реализации этой задачи необходимо было переработать все функционалы, в которых что-либо сохранялось. На этапе составления плана действий мы попытались найти кого-нибудь с подобным опытом, прошерстили интернет и пообщались с коллегами из других студий, но многие вообще не верили, что это возможно, учитывая количество накопившегося у нас легаси.Что ж, для начала составили критерии, которым должна удовлетворять наша система хранения данных:
- Пользовательский опыт, когда все действия в игре происходят моментально без задержек на серверную валидацию, не должен пострадать.
- При запуске приложения одним пользователем на нескольких устройствах игрок должен видеть абсолютно одинаковые состояния.
- Нельзя допустить, чтобы какие-либо проблемы с сервером приводили к запрету входа в игру.
- Минимизировать трафик и нагрузку на сервер.
- После реализации основного этапа поддержка и развитие новых функционалов должны быть максимально простыми и быстрыми.
- Все должно быть надежным и исключать возможность несанкционированного накручивания прогресса.
- Желательно оставить возможность пользоваться игрой оффлайн, так как такие режимы востребованы среди наших игроков.
Потом начали составлять план работ. Выделили несколько глобальных этапов:
- Введение постоянного сокетного соединения клиент-сервер. Используемая до этого связь через https-запросы сильно ограничивала нас в реализации необходимых функционалов. Виделось, что при ней реализовать систему по всем требованиям не получится.
Тогда опыта использования постоянного сокетного соединения у нас еще не было (предполагалось, что в дальнейшем без соединения нельзя будет находиться в основных разделах игры), поэтому решили обкатать соединение постепенно.
Сначала сделали фоновое подключение и продублировали часть второстепенных функционалов на сокетное соединение с возможностью удаленного переключения, каким каналом связи клиенту пользоваться. Выкатили на пользователей, настроили сервера и убедились, что соединение работает стабильно.
Затем ввели полноценную работу с аккаунтами и добавили поддержку сокетного соединения у большего количества функционалов. До этого момента мы поддерживали оба канала связи. Когда убедились, что новая архитектура держит постоянную связь с сервером и справляется с нагрузками, то можно было выкатывать полноценную работу с прогрессом и все остальные переработанные функционалы.
- Формат хранения данных определили JSON. Может он и не самый оптимизированный по трафику и работе, но удобен в использовании. Его мы расширяем и часто используем по проекту.
Глобально прогресс представляет собой Dictionary<int, object>, где каждая пара — это так называемые слоты для хранения данных. Каждый слот служит для хранения своего типа данных: слот валюты, слот инвентаря, слот ачивок и так далее. Ключ — он же номер слота данных — решили сделать интовым, чтобы сократить использование трафика, значение в каждом слоте может иметь свой формат, но это всегда JSON.
Чтобы все действия на клиенте отрабатывались моментально, прогресс хранится и на клиенте, и на сервере (команды по изменению прогресса пишутся и там, и там). Отрабатывают сначала на клиенте: визуально пользователь видит, что все окей, а в это время на сервер отправляются параметры для команды. Там они проходят необходимые проверки, и если все хорошо, то прогресс меняется аналогичным способом, как и в клиенте. По результату сравниваются итоговые хеши изменившихся слотов на сервере и клиенте, если не сходятся — рвется соединение с клиентом, клиент переавторизовывается и подтягивает последнее валидное состояние слотов.
Чтобы сократить трафик между клиентом и сервером, для сравнения слотов отправляется хеш слота, а не слот целиком. И только при несовпадении хешей пересылается состояние слота от сервера клиенту при авторизации.
На случай временной потери интернета (или если, например, приложение закроется до отправки команды на сервер), команды сохраняются локально и при следующем запуске перед началом сравнения слотов на сервере и клиенте сначала отправляются они и только потом хеши имеющихся слотов.
Чтобы прогресс всегда был в консистентном состоянии, добавили возможность отправки нескольких команд на изменение прогресса в виде одной и назвали это снапшотом. Если не проходит валидацию одна из команд в снапшоте, то все команды в нем фейлятся и не применяются. Где это необходимо использовать: при добавлении опыта повышается уровень игрока, а за это ему необходимо выдать награду. Если не сойдется слот уровня (например, в случае локального его изменения до команды), то и добавление опыта не запишется, не создав ситуацию, когда у игрока опыта больше, чем может быть на уровне.
- Для сохранения возможности входа в Pixel Gun 3D в те моменты, когда необходимо по каким-либо причинам остановить сервер прогресса, реализовали для него аварийный режим. При его включении все команды отрабатывают только локально. А когда включается штатный режим, клиент присылает серверу текущие слоты. В эти моменты, конечно, есть возможность что-либо накрутить через какой-нибудь мод. Но аварийный режим мы включаем крайне редко, да и визуально на клиенте никак этого не понять, поэтому вероятность, что этим воспользуются — минимальна.
- Оставили возможность входа в игру при отсутствии интернета. Игрокам доступны одиночные режимы без наград и ряд других локальных возможностей, которые не требуют изменений прогресса.
Шаг №3. Миграция прогрессаПри переходе от хранения прогресса игроков в сторонних сервисах к хранению на собственных серверах, необходимо было мигрировать эти данные, относящиеся именно к состоянию профиля. То есть полноценно перенести прогресс у честных игроков и не допустить, чтобы после ввода новой схемы работы прогресса можно было взламывать на прошлых версиях и путем обновления переносить данные к нам на сервер.Для этого мы добавили выдачу уникальных ключей всем, кто заходил в игру во время реализации новой схемы прогресса (примерно полгода). В дальнейшем, как только выпустили версию с обновленным прогрессом, мы отключили на сервере выдачу этих ключей, а принимали мигрированный прогресс только от тех, у кого был ключ и только один раз (чтобы нельзя было вернуться в прошлую версию и начислить прогресс тем, кто этого не сделал ранее).Так мы мигрировали прогресс всем активным игрокам и решили проблему взлома через старые незащищенные версии.Шаг №4. Система бана
Реализовали надежную систему бана, которую нельзя вырезать из клиента, так как у забаненного игрока блокируется весь серверный функционал (при том, что большая часть логики уже была перенесена на сервер).Ранее, когда не было постоянного соединения с сервером, не было возможности надежно забанить клиент — информация о бане запрашивалась отдельным http-запросом с сервера, и в случае положительного ответа в клиенте выводился баннер, что аккаунт забанен. Хоть мы и старались это спрятать в коде, появлялись различные моды, где весь мешающий жизни функционал был нейтрализован.Сейчас, когда реализована постоянная связь с сервером, большинство функционалов просто не работает без соединения. Для бана достаточно запретить авторизацию при открытии соединения с отдельной ошибкой (для показа баннера, что игрок забанен), чтобы сделать невозможным пользование приложением с заблокированным аккаунтом.Для защиты онлайна на серверах фотона от забаненных мы также сделали проверку с плагина фотона на факт бана. Если там выясняется, что такой id забанен, то игрока выкидывает из комнаты. Шаг №5. Подсчет хеша всех библиотекОдним из традиционных способов взлома является модификация библиотек приложения напрямую. В случае с приложениями на Unity — это libil2cpp.so (при билде через IL2CPP).Для обнаружения таких изменений может использоваться проверка на несовпадение контрольных сумм (хеша библиотек). Самым контролируемым способом будет вычисление текущего хеша на клиенте и отправка его на сервер (где он будет сравнен с эталонным).Получить путь до наших библиотек можно так:
public string GetLibraryDirectory()
{
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
if (unityPlayer == null)
throw new InvalidOperationException("unityPlayer == null");
var _currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
if (_currentActivity == null)
throw new InvalidOperationException("_currentActivity == null");
AndroidJavaObject packageManager = _currentActivity.Call<AndroidJavaObject>("getPackageManager");
if (packageManager == null)
throw new InvalidOperationException("packageManager == null");
string packageName = _currentActivity.Call<string>("getPackageName");
if (string.IsNullOrEmpty(packageName))
throw new InvalidOperationException("string.IsNullOrEmpty(packageName)");
const int GetMetaData = 128;
AndroidJavaObject packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", packageName, GetMetaData);
if (packageInfo == null)
throw new InvalidOperationException("packageInfo == null");
AndroidJavaObject applicationInfo = packageInfo.Get<AndroidJavaObject>("applicationInfo");
if (applicationInfo == null)
throw new InvalidOperationException("applicationInfo == null");
string nativeLibraryDir = applicationInfo.Get<string>("nativeLibraryDir");
if (string.IsNullOrEmpty(nativeLibraryDir))
throw new InvalidOperationException("string.IsNullOrEmpty(nativeLibraryDir)");
return nativeLibraryDir;
}
Для автоматизации процесса при сборке билдов можно использовать OnPostprocessBuild в Unity и производить расчет эталонного хеша. Обратите внимание на то, что при сборке с включением нескольких платформ (ARM, x86) необходимо вычислять хеш по каждой платформе.Что дальшеВ следующий раз поговорим про остальные решения, а именно: защиту от переподписывания версий, Photon Plugin, серверную валидацию инапов, защиту от взлома оперативной памяти, одновременный релиз всех решений и собственную аналитику. А про некоторые объемные пункты уже готовим отдельные, более подробные материалы.
===========
Источник:
habr.com
===========
Похожие новости:
- [Управление сообществом, Монетизация игр, Развитие стартапа, Управление продуктом, IT-компании] Детские шалости: как Roblox стала одной из самых дорогих игровых компаний современности
- [Разработка мобильных приложений, Flutter] С чего начать изучение Flutter в 2021 году (перевод)
- [Разработка мобильных приложений, Разработка под Android] Материалы митапа для андроид-инженеров: поиск проблем сборки, защита от них и работа с Gradle
- [Разработка мобильных приложений, Функциональное программирование, Карьера в IT-индустрии, Конференции] Этот поезд в окне: анонс TechTrain 2021 Spring
- [Программирование, Java, Разработка мобильных приложений, Разработка под Android] Как написать простое Android ToDo-приложение на Java
- [Разработка мобильных приложений, Разработка под Android, Аналитика мобильных приложений] Выходим на рынок Huawei, или Как мы адаптировали приложение для работы с HMS
- [Разработка под iOS, Разработка мобильных приложений, Swift] Память в Swift от 0 до 1
- [Разработка мобильных приложений, Интерфейсы, Дизайн мобильных приложений, Графический дизайн, Дизайн] Адаптация таблиц под мобильные устройства
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Дизайн мобильных приложений, Дизайн] Исправляем Госуслуги малой кровью — добровольный редизайн мобильного приложения
- [Разработка игр, Дизайн игр] Восстание игроков: о главном принципе Искусства, применимом и к творению игр
Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_igr (Разработка игр), #_unity, #_dizajn_igr (Дизайн игр), #_unity, #_chitery (читеры), #_shuter (шутер), #_pvp, #_gejmdev (геймдев), #_razrabotka_igr (разработка игр), #_cheats, #_gamedev, #_obfuskatsija (обфускация), #_blog_kompanii_lightmap (
Блог компании Lightmap
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_razrabotka_igr (
Разработка игр
), #_unity, #_dizajn_igr (
Дизайн игр
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:35
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Мы прошли долгий путь от появления в игре первых читеров до полного пересмотра подхода к разработке, чтобы создавать защищенные по умолчанию проекты. О том, как в игре появились читеры, я рассказал в прошлом материале. И там же привел список подзадач, которые выкатили одновременно, чтобы закрыть вопрос со взломами, — от обфускации кода до подсчета хеша всех библиотек и надежной системы бана.Итак, эти шаги:
[Obfuscation(Exclude = true)]
public enum GameEventItemContainerContentType { None = 0, SingleItem = 1, ItemsCollection = 2, Start = 3, }
Реализовали надежную систему бана, которую нельзя вырезать из клиента, так как у забаненного игрока блокируется весь серверный функционал (при том, что большая часть логики уже была перенесена на сервер).Ранее, когда не было постоянного соединения с сервером, не было возможности надежно забанить клиент — информация о бане запрашивалась отдельным http-запросом с сервера, и в случае положительного ответа в клиенте выводился баннер, что аккаунт забанен. Хоть мы и старались это спрятать в коде, появлялись различные моды, где весь мешающий жизни функционал был нейтрализован.Сейчас, когда реализована постоянная связь с сервером, большинство функционалов просто не работает без соединения. Для бана достаточно запретить авторизацию при открытии соединения с отдельной ошибкой (для показа баннера, что игрок забанен), чтобы сделать невозможным пользование приложением с заблокированным аккаунтом.Для защиты онлайна на серверах фотона от забаненных мы также сделали проверку с плагина фотона на факт бана. Если там выясняется, что такой id забанен, то игрока выкидывает из комнаты. Шаг №5. Подсчет хеша всех библиотекОдним из традиционных способов взлома является модификация библиотек приложения напрямую. В случае с приложениями на Unity — это libil2cpp.so (при билде через IL2CPP).Для обнаружения таких изменений может использоваться проверка на несовпадение контрольных сумм (хеша библиотек). Самым контролируемым способом будет вычисление текущего хеша на клиенте и отправка его на сервер (где он будет сравнен с эталонным).Получить путь до наших библиотек можно так: public string GetLibraryDirectory()
{ var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); if (unityPlayer == null) throw new InvalidOperationException("unityPlayer == null"); var _currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); if (_currentActivity == null) throw new InvalidOperationException("_currentActivity == null"); AndroidJavaObject packageManager = _currentActivity.Call<AndroidJavaObject>("getPackageManager"); if (packageManager == null) throw new InvalidOperationException("packageManager == null"); string packageName = _currentActivity.Call<string>("getPackageName"); if (string.IsNullOrEmpty(packageName)) throw new InvalidOperationException("string.IsNullOrEmpty(packageName)"); const int GetMetaData = 128; AndroidJavaObject packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", packageName, GetMetaData); if (packageInfo == null) throw new InvalidOperationException("packageInfo == null"); AndroidJavaObject applicationInfo = packageInfo.Get<AndroidJavaObject>("applicationInfo"); if (applicationInfo == null) throw new InvalidOperationException("applicationInfo == null"); string nativeLibraryDir = applicationInfo.Get<string>("nativeLibraryDir"); if (string.IsNullOrEmpty(nativeLibraryDir)) throw new InvalidOperationException("string.IsNullOrEmpty(nativeLibraryDir)"); return nativeLibraryDir; } =========== Источник: habr.com =========== Похожие новости:
Блог компании Lightmap ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_razrabotka_igr ( Разработка игр ), #_unity, #_dizajn_igr ( Дизайн игр ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:35
Часовой пояс: UTC + 5