Часто сижу на форумах читеров, не потому что нравится, а просто разработчику PvP-шутера всегда нужно быть в тонусе. Некоторые из взломщиков в прямом смысле слова выросли на моих глазах вместе с проектом. Вспоминал эти истории после очередного апдейта в проекте и захотелось ими поделиться.
Несколько лет назад мы написали тестовый прототип шутера, который быстро стал популярным — защиты никакой не было, и некоторые побежали этим пользоваться. Мы затыкали пробоины скотчем, читеры находили новые дыры, а мы учились у них — с помощью социальной инженерии и повышения технических навыков. И в итоге пересмотрели отношение к защите будущих мобильных проектов (об этом — в конце статьи).
До появления мобильной Pixel Gun 3D в 2013 году команда Lightmap писала мини-игры на движке Cocos2d. То есть опыта ни в трехмерных, ни тем более мультиплеерных играх у нас тогда не было. Мы посещали конференции, очень много общались с другими разработчиками и, посмотрев на их опыт, все-таки решили попробовать себя в 3D на Unity.
Изучать движок решили на прототипе шутера, чтобы посмотреть, как отреагируют игроки. В итоге проект выстрелил так, что собирать опытную команду (а на старте было всего 3 разработчика) и разрабатывать качественный продукт с нуля уже не было времени. Всему пришлось учиться на своих ошибках после релиза.
Читеры из младших классов
С самого начала база игроков росла достаточно быстро, а с ними приходили первые читеры. Но из-за ограниченных ресурсов в приоритете стояли совсем другие задачи — на старте было мало контента, игровых механик, да и цельной меты как таковой не было. Только когда в обзорах на YouTube слишком часто стали всплывать упоминания о взломах, пришлось заняться этим вопросом.
То есть изначально мы даже не рассматривали проблему появления читеров, а думали только о том, как сделать интересную мобильную игру на Unity. Возможно это и была первая ошибка, но напомню, что мы впервые собирали подобный прототип.
Первыми атаковали самые настоящие школьники — в прямом и переносном смыслах слова, потому что взломать игру было проще простого. Со многими из них мы познакомились заочно, так как пришлось погрузиться в форумы и чаты, где они делились уязвимостями — было даже интересно наблюдать, как их навыки росли вместе с проектом, а с ними росли наши познания в защите.
Сначала они находили элементарные дыры — это использование простых переменных для данных в оперативной памяти и хранение данных на устройстве в незашифрованном виде. Поэтому тот же GameGuardian легко находил слабые места: валюту, время до конца матча, здоровье и так далее.
Сразу же решили передать хранение прогресса в защищенное зашифрованное значение и переделать все переменные, в которых хранились ответственные данные, на классы, где для скрытия используется рандомная соль. Не обошлось без факапов.
В месте, где валюта переносилась из старого хранилища в новое, мы допустили баг, и когда пользователь, который не входил в игру больше недели, обновлялся до новой версии — то вся его валюта обнулялась. Самым сложным было понять, в чем дело, потому что при наших тестах такой сценарий не воспроизводился. Оказалось, что место отправки аналитики входа в игру после долгого отсутствия (в которой косвенно использовалась валюта по коду) располагалось раньше нашей миграции.
Насчет прогресса тоже оставалась дыра — можно было ломать предыдущие версии и накатываться на новые. Тогда мы параллельно ввели новые валюты и сущности в игре, рассчитывая, что хотя бы их не будут ломать. Ценность старой валюты постепенно снижалась, так как новый контент на нее уже было не приобрести.
Также поставили хоть и локальную, но, все-таки, защиту на взлом инапов через LuckyPatcher. Когда позакрывали места, с которыми мог справиться и первоклассник, к взлому подключились «ученики средних классов».
Читеры постарше
Они начали менять код приложения, дописывать свои менюшки для взлома различных параметров, переподписывать и выкладывать apk, которые пользовались успехом на форумах.
Все старания спрятать в коде проверку подписи быстро находились и вырезались, как и другие проверки на взломы приложения. Для обфускации мы стали использовать появившуюся в Unity IL2cpp, но в итоге выяснилось, что движок хранит в проекте дамп всех методов с адресами памяти — чем стали пользоваться уже «старшеклассники».
Ситуация по сути не менялась, так как мы все время боролись с симптомами, а не с болезнью. Даже не смотря на то, что иногда нам удавалось закрыть уязвимости еще до их использования — для этого приходилось выдавать себя за тех самых читеров.
Социальная инженерия против читеров
Мы следили за взломщиками на форумах, общались, изучали, откуда они черпают знания и старались работать на опережение. У нас натурально были постоянные взломщики, которые на глазах перешли из средней школы в институт, набирались опыта и знаний.
Однажды один из таких ребят начал активно общаться с читером, который «ломал» другие игры. Мы это заметили, скачали его взломы, изучили принцип работы и заранее расставили необходимые детекты по своему приложению. Позже на YouTube мы увидели, что «наш» читер действительно пытался сделать аналогичный мод для Pixel Gun, но ничего рабочего в итоге не получилось.
В другой раз мы увидели анонс чита, который готовил один из игроков. По его нику нашли форум взломщиков и прочитали все его сообщения. Так мы поняли, каким инструментарием он пользуется, и даже без чита на руках смогли закрыть уязвимость и выпустить патч еще до релиза.
Но в итоге все равно различные взломы регулярно выпускались разными известными и не очень взломщиками. Все-таки дыра с валютой была лишь одной из многих на тот момент и далеко не самой опасной, потому что за ее использование надо было платить.
А тут случился еще один серьезный факап с нашей стороны. В сеть утекла версия для разработчиков годовалой давности с дев-консолью, в которой можно было легко намешать любую сущность. К этом моменту CCU в пике доходил до 70 тысяч игроков, а DAU стабильно переваливал за миллион пользователей.
Стало понятно, что дальнейшее развитие приложения невозможно при таком состоянии дел со взломами. Все старания с поднятием уровня основных метрик нивелировались, а часто даже занижались вывешенными взломанными версиями.
Приоритетной задачей стало: убрать возможность читерства из приложения.
Решение
Итак, что мы имели на входе:
Огромное приложение с несколькими годами активной разработки и большим легаси.
Возможность изменения версии и выкладки пользователям.
Хранение прогресса локально на девайсе. Как следствие — отсутствие контроля над обновлениями с прошлых версий, в том числе взломанных.
Синхронизация прогресса между девайсами через сторонние сервисы — прогресс можно было беспрепятственно суммировать между игроками.
Отсутствие надежной возможности бана читера — в измененных версиях его тоже вырезали.
Отсутствие надежного отслеживания фактов читерства.
Отсутствие игрового сервера — мультиплеерное взаимодействие было реализовано через Photon Unity Networking (Cloud) без какой-либо серверной логики.
Наличие измененных версий игры в китайских сторонних сторах.
Связь с сервером через www-запросы, которые легко отследить и подделать.
Возможность подставить из кода любой id, который мы не сможем идентифицировать как подставной.
В итоге: безбожное ломание всего кора и меты игры на тот момент. Поэтому решение тоже должно было быть комплексным — от обфускации кода и переноса хранения прогресса на наш сервер до защиты от переподписывания версий и введения собственной аналитической системы.
Причем эффект от всего этого был бы только в случае одномоментного выпуска всех мер защиты, потому что постепенный ввод изменений в проект значительно облегчил бы отслеживание апдейтов взломщиками, которые и так уже хорошо ориентировались в проекте. Другими словами, время, потраченное на взлом, не должно быть сопоставимо с выгодой.
Началась масштабная работа, для чего практически удвоили команду разработчиков, привлекая их с другого проекта.
Пока кратко о том, что мы сделали для перелома ситуации с читерами:
Для обфускации ввели новые стандарты написания кода, позволяющие плагину обфускации сделать свое дело, и переписали под них все приложение. Задача не особо сложная, но ресурсоемкая, так как кодовая база к этому времени уже была довольно большой.
Переработали хранение данных — еще более глобальная и ресурсоемкая задача. Сущностей к этому времени было несчетное количество, данные сохранялись беспорядочно в произвольных форматах, что никак нам не подходило. Для синхронизации прогресса с сервером реализовали постоянное сокетное соединение, переведя на него все функционалы, связанные с метой игры (ну и, само собой, сам сервер, но это отдельная история). Для игроков оставили возможность поиграть оффлайн в несколько режимов, правда без наград и изменения прогресса.
Чтобы мигрировать прогресс игроков, на время разработки глобальной защиты стали выдавать специальные ключи. Перенести прогресс в результате смогли только те, кто заходил в приложение в этот временной интервал. После релиза новой версии выдачу ключей остановили. И хотя эти ключи выдавались всем — и честным игрокам и читерам — основная цель была: исключить взломы через прошлые версии, так как без ключа прогресс не принимался сервером при миграции.
Реализовали надежную систему бана, которую нельзя было вырезать из клиента, так как у забаненного игрока блочится весь серверный функционал (при том что большая часть логики была перенесена на сервер).
Добавили подсчет хеша всех библиотек и начали сравнивать его с разрешенными при авторизации на сервере, куда они добавляются при сборке релизных билдов.
Расставили в дополнительные места защиту от переподписывания версий, лаунчеров (на Android), твиков (на iOS), спрятав уже в обфусцированном коде.
Для предотвращения взлома игровых параметров серверной логикой ввели Photon Plugin, доступный на тарифе Enterprise Cloud. Он позволяет мониторить пересылаемый между пользователями игровой трафик, чтобы вычислять тех, у кого действия выходят за рамки допустимого (жизни, урон, скорость перемещения/стрельбы, использование запрещенных предметов и так далее). Для возможности отслеживания взломов переписали сетевое взаимодействие игроков.
Добавили серверную валидацию инапов.
Добавили защиту от взлома оперативной памяти.
Практически одномоментно выкатили все в продакшен.
Продолжили реализовывать и улучшать собственную систему аналитики.
Сейчас аналитика — основной инструмент постоянного отслеживания попыток взлома. Мы можем отслеживать все действия каждого конкретного пользователя и реагировать даже на единичные попытки взлома, не давая распространиться лазейкам. А когда-то эту роль для команды играли YouTube и профильные сайты.
Путь проделан не маленький и можно еще многое расписать про подходы, инструменты, нашу архитектуру и инхаус-разработки, вроде аналитики. Но рассказ обо всем сразу будет слишком объемный, поэтому лучше оставлю место для вопросов в комментариях и для будущих материалов.
ААА-защита для мобильных игр
Сейчас игра надежно защищена от взломов, у нее, как минимум, нет распространенных методов взлома. Бывают единичные, но их оперативно отслеживаем и реагируем.
К новым проектам в разработке тоже стали относиться по-другому и сразу пишем их с защитой, почти как у полноценных ААА-игр. Максимально возможное количество защит на старте приложения означает, что у взломщиков слишком большой порог входа, который надо преодолеть — это отпугивает абсолютное большинство.
Также всегда проще писать сразу под необходимые меры защиты, а не переделывать проект под них в будущем.
На текущий момент наша программа-минимум для новых проектов это:
Плагин обфускации для осложнения понимания кода приложения.
Хранение и синхронизация прогресса через наш сервер с использованием валидации всех основных операций.
Зашифрованное хранилище на диске, чтобы исключить перенос данных от девайса к девайсу копированием данных.
Надежная система бана.
Photon Plugin, как минимум, для валидации действий пользователя.
Защита от изменения, переподписывания версий, лаунчеров и твиков.
Серверная валидация инапов.
Повсеместное использование засоленных данных — как защита от изменений в оперативной памяти.
Собственная система аналитики с отслеживанием подозрительных действий конкретных пользователей.
И продолжаем сидеть на форумах под видом читеров, общаться и мониторить новые уязвимости. Например, одна из последних — это скрытие использования тиков после выхода джейлбрейка на свежие версии iOS. Об этом тоже узнали из форумов, изучили механизмы работы и оперативно выкатили обновление — но об этом и других кейсах уже в следующий раз.
FDsagizi
Да, все знакомо до боли)
В нашей игре MadOut2 тоже есть подсчет хеша, но старшеклассники его подделывают) правда у нас нет обфускации кода.
А по поводу обфускации, если у пользователей будут ошибки, можно ли будет понять где это произошло, или в callstak будет белеберда? И как быть с публичными переменными в Юнити, они же присваиваются по имени?
Интересно будет узнать, на чем вы написали backend для обработки данных, все таки ccu 70k это приличная нагрузка.
NikolayCherkashin Автор
При обфусцировнии плагин сохраняют в файл соответствия исходных имен и обфусцированных. Мы эти файлы сохраняем и в дальнейшем используем для восстановления коллстеков.
Публичные переменные не обфусцируются, их используем только для интерфейсов, интерфейсы, как правило, не представляют интереса для взлома
FDsagizi
> При обфусцировнии плагин сохраняют в файл соответствия исходных имен и обфусцированных. Мы эти файлы сохраняем и в дальнейшем используем для восстановления коллстеков.
А как это происходит, плагины или ручками по файлу ищите название методов?
NikolayCherkashin Автор
Да, вручную ищем названия методов, процедура не частая, поэтому особо не напрягает
NikolayCherkashin Автор
Он написан на Python, в скором времени планируем рассказать про серверную архитектуру в отдельной статье