Это статья о том, как мы делали систему защиты браузерной HTML5 игры от взлома и подделки результатов, с какими трудностями мы при этом столкнулись, как их решали и что получили в итоге. Основной и всем знакомой проблемой таких игр является возможность написания бота, который эту игру автоматически пройдет. Разработку подобного бота облегчает тот факт, что код игры находится в публичном доступе. Ситуация осложнялась тем, что были объявлены реальные призы, среди которых iPad, билеты на концерт, USB флеш накопители и т.п.
Статья будет полезна в основном тем, кто делает HTML5 / Flash игры и заботится об их безопасности; тем, кто платит за разработку этих игр; и немного тем, кто призван бороться с ботами. Ну и, конечно, тем, кто написал эту статью. Потому что мы надеемся, что она станет началом продуктивной дискуссии о том, как разработчики браузерных игр могут противостоять кибер-мошенникам.
Что за игра?
Немного о самой игре.
К сожалению, саму игру мы показать не можем по требованию заказчика. Несмотря на все описанные ниже приемы, к сожалению, «незнание» является ключевым элементом защиты. То есть раскрытие методов в привязке к конкретной игре, существенно снизит их эффективность.
- Тип игры: Collect & Run.
- Примеры: всем знакомый Mario Bros. и его клоны
- Продолжительность игры: примерно 1-2 минуты.
- Цель игры: набрать наибольшее количество очков.
- Основные элементы игры:
- Персонаж.
Главное действующее лицо, всегда движется. Может подпрыгивать и пригибаться для того, чтобы собирать бонусы и избегать препятствий. - Бонусы.
Могут увеличивать очки или скорость движения персонажа. Для того, чтобы их собрать, персонаж должен подпрыгнуть. - Препятствия.
Барьеры или движущиеся навстречу персонажу предметы. Для того, чтобы их избежать, персонаж должен пригнуться или подпрыгнуть. При столкновении с препятствием персонаж теряет скорость.
- Персонаж.
- Характеристики игры, используемые при расчете результата игры:
- Набранные очки. Увеличиваются при сборе бонусов.
- Скорость персонажа. Уменьшается со временем. Уменьшается при столкновении с препятствием. Увеличивается при сборе бонусов.
- Затраченное на игру время.
- Используемые технологии:
- Клиент: HTML5. Основание: требовалось, чтобы игра запускалась на всех устройствах. JS-framework: Pixi.js (http://www.pixijs.com)
- Сервер: PHP, Framework: Zend
- Цель заказчика: реклама своего бренда через игру. Игровой процесс был связан с услугами компании; логотипы, фирменные цвета и все такое прочее в наличии.
- Игра запускалась на определенный срок, по истечению которого игроки с лучшими результатами награждались ценными призами.
- Исходя из вышеуказанного, заказчик сразу делал упор на то, что игра должна быть защищена от подделки результатов. Заказчик дорожит репутацией. Он не хотел, чтобы после выхода игры в СМИ появилась информация, что их брендованная игра взломана.
Что за команда?
Мы — web-разработчики, но разработкой игр ранее не занимались. До разработки этой игры gamedev представлялся нам чем-то не понятным. Поэтому в самом начале мы родили много смешных решений. Например, мы сделали важную архитектурную ошибку — решили генерировать уровень на клиенте в браузере; а в качестве защиты уповали на обфускацию JS кода. Правда товарищи из соседних отделов говорили, что для качественной защиты придется “проигрывать” игру на сервере и сравнивать с клиентом. Но мы посмеялись тогда, что это уж очень сложно.
Повторюсь, что главнейшим требованием было обеспечить защиту от накруток. Но наша команда решила сначала сделать так, чтобы игра в принципе работала. А разработку защиты отложили на попозже. Казалось логичным: сначала что-то делаем, и уже потом это “что-то” защищаем.
The Проблема
Как только мы начали заниматься защитой, мы сразу столкнулись с базовой проблемой всех браузерных игр, вытекающей из клиент-серверной архитектуры. А именно: игрок видит весь клиентский код игры и слепо доверять пришедшим на сервер данным нельзя. Если со второй частью проблемы мы обходиться умеем, то возможность анализа и изменения исходников любым пользователем нас реально озадачила.
Мы начали с простого решения — обфускации JS кода и шифрования данных, отправляемых клиентом на сервер. Но у заказчика была убойная команда безопасности, которая за 15 минут смогла записать нереальный игровой счет, имея полностью обфуцированный код. Шифрование данных при передаче тоже не вариант — ключи лежат у клиента, и их можно легко подставить в нужное место. Так что фактически оба этих решения оказывались действенными только для очень ленивых хакеров.
Более того, Flash-игры тоже подвержены подобному реверс-инжинирингу, и те разработчики тоже в поиске решения защиты.
Ситуация осложнялась еще и тем, что у нашей игры есть реальные призы. В этом случае ее взлом перешел из разряда “взломать для развлечения” в разряд “взломать ради денег”. Эта игра более чем привлекательна для специалистов более высокого класса, чем “начинающий хакер”.
Советы из Интернета
Несмотря на то, что разработано уже очень много онлайн игр, информации о методах борьбы с их взломом удалось найти крайне мало. Вот какие рекомендации мы извлекли из просторов Интернета:
- Скрывать, защищать и шифровать игровые данные:
- Ответственные за игровой счет переменные не следует делать в глобальной области видимости. Их рекомендуют объявлять приватными и помещать в замыкания (closure).
- Не отсылать значения счета в открытом виде, а обязательно шифровать.
- Шифровать адреса (uri), по которым передается счет.
- Обфуцировать часть кода, которая ответственна за вычисление и отправку счета.
- Присылать вместе с результатами игры лицензионные ключи и проверять их на сервере.
- Плюсы: относительная простота реализации
- Минусы: как уже было написано выше — взламывается за 15 минут
- Дурачить хакеров:
- При обнаружении накрутки счета все равно показывать хакеру в списке рекордов его результат. Пусть он думает, что дело сделано. Но остальные пользователи этого видеть не должны.
- Когда игра определяется как взломанная, то откладывать наказание на случайный срок (например, от 1 до 3 дней). Хакер будет применять несколько методов и периодически он будет видеть, что у него получается. Но что именно получается, понять ему будет сложно, и на чем конкретно его поймали в итоге — тоже.
- Проверять реалистичность игровых данных:
- Проверять на сервере теоретическую возможность присылаемых значений. Например, энергия не может быть отрицательной, счет не может уменьшаться, позиция игрока всегда увеличивается и т.п.
- Присылать данные игры на сервер и сравнивать данные с теми же данными других пользователей. Например, если игрок сделал 200 выстрелов и достиг счета 200000, а остальные игроки сделав 200 выстрелов достигают счета 5000, то это повод усомниться в честности игры.
- Удалять игры, которые длились менее (более) определенного времени.
- Плюсы: относительная простота реализации
- Минусы: не дает полной защиты, добавляет лишние 5-10 минут на организацию взлома
- Организационно воздействовать на аккаунты:
- Добавить важности аккаунту, чтобы было жалко его потерять. Например, чтобы поиграть в эту игру с реальными призами, надо сначала достичь чего-то в других играх без призов.
- Сделать регистрацию аккаунта платной.
- Банить по IP хакеров при самой первой попытке взлома. Вообще не давать им возможности разобраться.
- Эмулировать игру на сервере:
- Сделать 'instant replay' на сервере. После прохождения уровня присылать данные, по которым восстанавливаются события в игре и заново рассчитывается ее результат. Затем это все сравнивается с тем, что прислал игрок, и если разница большая, то это хак.
- Плюсы: дает большую степень защиты
- Минусы: события, отправляемые в конце игры также могут быть подделаны
Мы провели мозговой штурм и тоже выработали много идей. Были удачные, были и не очень. К разряду не самых удачных можно отнести, например, вывод капчи “до”, “во время” и “после” игры. Или, допустим, получение скриншотов во время игры и отсылка их на сервер для проверки. Далее описаны наиболее интересные из реализованных методов.
Эмулятор игры
После долгих размышлений и сопротивлений сложному решению, мы пришли к выводу, что проигрывание (эмуляция) игры на сервере — вещь явно необходимая. Если не сказать — неизбежная.
Для этого мы стали генерировать игру (расположение бонусов и препятствий) на сервере и
отправлять на сервер не конечный результат игры, а только события, которые в ней происходили: столкновения с препятствиями, бонусами и нажатия клавиш. Для каждого события мы знали время, координаты, нажатые клавиши или бонусы/препятствия с которыми игрок столкнулся.
Был вариант отправлять только нажатия клавиш, а на сервере рассчитывать результат в конце игры. Но мы столкнулись с проблемой разной скорости игры на разных устройствах и в разных браузерах. Велика вероятность сильного расхождения процесса игры на клиенте и на сервере, так как этот метод сильно чувствителен к несоответствиям. Например, если вдруг клиент не пришлет всего одно столкновение с препятствием, то эмулированная игра с этого момента пойдет совершенно по-другому. Ведь при столкновениях теряется энергия и скорость.
Другой вариант: обрабатывать события на сервере и немедленно отправлять результаты события на клиенте. Но при этом возникает очень большой трафик между сервером и клиентом, увеличивается время отклика игры и резко возрастает нагрузка на сервер. Так работают, например, MMO игры. Но для небольшой Collect & Run игры мы посчитали это нецелесообразным.
Особо хочется отметить тот факт, что устройства, на которых запускалась игра, имели разную производительность. На быстрых устройствах игра шла плавно, на медленных — рывками. Из-за этой разницы нам пришлось отказаться от привязки ко времени во время эмуляции. Вместо этого мы рассматривали события игры как нечто, происходящее в некоторых координатах. Например, сначала мы рассматривали прыжок за бонусом как “нажатие кнопки вверх на 10-ой секунде, столкновение с бонусом на 12-той секунде”. Но нам более подошло следующее: “нажатие кнопки вверх при Х-координате игрока равной 1000, столкновение с бонусом при Х-координате игрока равной 1100 и Y-координатой игрока равной 300”. В таком случае и быстрое и медленное устройства эмулировались более-менее одинаково.
В итоге мы решили после игры эмулировать на сервере каждое событие, присланное клиентом, и дополнительно рассчитывать вероятность его возникновения. Одной из задач, которые мы при этом решали, было создание одинаковых алгоритмов расчета столкновений на сервере и на клиенте. Необходимо было добиться такой же точности, в том числе округления.
В результате работы эмулятора мы получаем на сервере последовательность игровых событий в том же виде, что и при реальной игре. После эмуляции событий мы переходим к анализу того, что у нас получилось.
Анализатор игры
Это самый интересный блок. Тут собраны наиболее удачные методы обнаружения читеров. Все они разные, запускаются последовательно, можно добавлять при необходимости еще.
- Координаты столкновения.
Столкновение с препятствием или бонусом произошло в браузере на клиенте в точке, где этот предмет был сгенерирован на сервере. - Увеличение позиции персонажа.
Позиция персонажа в игре должна увеличиваться со временем. Уменьшаться или оставаться одинаковой она не может. - Персонаж достиг конца уровня.
Максимальная позиция персонажа не превышает длины уровня, сгенерированной на сервере. - Длительность игры.
Уровень физически невозможно пройти менее чем за одну минуту. Поэтому игры, прошедшие за 30 секунд или за 2 минуты, считаются взломанными. - Контроль индикаторов скорости и энергии.
Значения индикаторов в браузере на клиенте должны соотноситься с игровыми событиями (сбор бонусов, столкновения с препятствиями). При взятии некоторых бонусов скорость персонажа увеличивается, при столкновении с препятствиями — уменьшается. Также сбор некоторых бонусов увеличивает набранное игроком количество очков. Зная какие предметы собрал игрок, можно вычислить на сервере, какие у него должны быть значения: скорости и суммы очков. - Прыжок перед взятием бонуса.
Бонусы висят высоко, чтобы их собрать персонаж должен подпрыгнуть. Тут проверяется, а был ли прыжок перед взятием. - Прыжок перед препятствием.
Для того, чтобы избежать столкновения с препятствием, лежащим на земле, персонаж должен подпрыгнуть. Для каждого препятствия проверяем, прыгнул ли игрок до препятствия (нажатие кнопки прыжка) и перекрыла ли его траектория прыжка. - Правильные клавиши.
Проверка нажатия строго определенных клавиш: “вверх” и “вниз”. Если нажималось что-то другое, то это подозрительно. - Бонусы и препятствия собираются один раз.
Пользователь может собрать один бонус (или препятствие) не более одного раза. - Столкновения на клиенте.
Сравнение столкновений, произошедших в браузере на клиенте и полученных на эмуляторе. - Столкновения на эмуляторе.
Сравнение столкновений, произошедших на эмуляторе, но не произошедших в игре. Этот метод введен для того, чтобы иметь разные веса этого и предыдущего методов. Мы посчитали, что если есть столкновение на эмуляторе, но нет в реальной игре, то это менее подозрительно, чем когда есть столкновение в реальной игре, но нет на эмуляторе. - Взятие ложных элементов.
При генерации игры мы добавили несколько бонусов, взять которые в игре невозможно. Например, они находятся чуть выше, чем может прыгнуть персонаж; или есть два бонуса, находящихся рядом, но физически можно собрать только один из них. И мы проверяем, попался ли хакер на приманку и взял ли он эти бонусы. - Анализ “отпечатков пальцев”.
До начала игры при регистрации и логине игрока мы собираем информацию о клиенте (плагины браузера, http заголовки, ip, установленные шрифты, размер и цветность экрана). Это не помогает нам понять, была ли игра взломана. Но зато в случае, когда администраторы помечают игрока как хакера, анализатор сканирует всех игроков в системе и находит похожие “отпечатки”. Тем самым можно с определенной вероятностью обнаруживать хакеров, которые пробуют ломать игру под одним аккаунтом, и играть “чисто” под другим.
Каждый метод имеет свой вес при расчете, значение которого настраивается. Кстати, имеет смысл выносить в настройки максимально возможное количество параметров. Ибо заранее не понятно, насколько чувствительным или грубым получится тот или иной алгоритм в реальности.
Анализатор при работе выставляет каждому методу значения от 0 до 1 (0 — игра вне подозрений, 1 — тест полностью провален) и в конце вычисляет “Индекс подозрительности игры” (тоже от 0 до 1).
Этот индекс используется для автоматической пометки игры. Если он больше порога, то игра считается взломанной. Конкретное пороговое значение подбирается опытным путем, у нас оно получилось 0.4.
Администрирование доказательств
Но это еще не все. Надо что-то делать со всеми этими расчетами. Одно дело — запрограммировать описанные выше алгоритмы, другое — подать администраторам игры результаты работы в удобочитаемом виде. Заблокированный за хакерство игрок может обратиться в поддержку с претензиями, и у них должны быть доказательства принятого решения. Поэтому рекомендуется давать администраторам максимальное количество информации, чтобы они разобрались и обосновали, почему та или иная игра была определена как взломанная.
Для этого в админке мы сделали 4 страницы.
- Информация об игре — Эмулятор
Таблица с реальными и эмулированными событиями выбранной игры. Столбцы следующие:- тип элемента (бонус, барьер)
- X- и Y-координаты элемента
- время на клиенте
- X- и Y- координаты персонажа (на клиенте)
- факт столкновения персонажа и элемента (да, нет)
- X- и Y-координаты персонажа (эмулированные)
- вероятность столкновения в указанное время на клиенте
- Информация об игре — Анализатор
Общий индекс подозрительности игры и таблица с результатами анализатора по каждому методу. - Анализатор
Показывает таблицу вообще всех подозрительных игр, исходя из порога подозрительности (0 — нет подозрений, 1 — точно взломали). По опыту у нас получилось значение 0.4. - Страница игрока
Добавили в профиль игрока “Индекс подозрительности игрока”. Он вычисляется на основе индексов подозрительности всех его игр.
К моменту запуска игры мы не успели сделать автоматическое блокирование подозрительных пользователей. Это значительно упростило бы работу администраторам игры, так как человек не в состоянии просмотреть тысячи подозрительных игр за день.
Как все прошло
Анализировать топ игроков через несколько дней после запуска игры было весьма занятно. Один взломщик пошел по простому пути и собрал все элементы, но ни разу не прыгнул. Он также собрал все “ложные” элементы. Другой “чемпион” эволюционировал последовательно. Сначала у него было с десяток честных игр, затем — очень много “пустых”. А в самых последних играх индекс подозрительности зашкаливал. Некоторые “победители” проходили игру менее чем за 10 секунд. Кое-кто играл в игру всего один раз и сразу попадал в топ. Это, кстати, можно использовать как метод анализатора — мало кто способен с первого раза сделать максимум. Были “рекордсмены” за одну секунду набиравшие нереальное количество очков. И так далее. Все они были заблокированы и до реальных призов не допущены. Жалоб от них не поступило.
Просмотрев несколько десятков реальных игр, мы обновили некоторые параметры пороговых значений в сторону увеличения. Например, максимальное значение индекса подозрительности игры, когда игра считается взломанной, можно увеличить. Иными словами, начальные настройки были очень подозрительными.
В реальности мы получили позитивный результат работы этой системы. Сильно помогало разбираться отображение результатов в админке. В целом можно сказать, что система показала себя вполне жизнеспособной, заказчик остался доволен.
Заключение
При защите от накруток счета мы использовали данные только одной игры и старались поймать ее на внутренних несоответствиях. Мы исходили из того, что игра полностью уязвима: все исходники доступны игроку, а входящие данные недостоверны. Но есть у игры часть, недоступная хакерам — это данные всех игр. Есть предположение, что правильно используя базу данных, сыгранную тысячами игроков, можно повысить точность определения взлома. Сейчас мы считаем, что база данных результатов игр — это основное преимущество разработчиков перед хакерами.
А что думаете вы? Поделитесь своими соображениями и подходами к защите онлайн игр.
Автор: volopoglod