Защита от читеров на примерах для Unity

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! С вами снова Илья и мы продолжаем серию статей по разработке игр на Unity. Сегодня мы разберем процесс защиты ваших игр на примерах. Объяснять я буду исходя из нашей открытой библиотеки, созданной для Pixel Incubator - сообщества, в котором мы учим делать игры и не только.

Начало работы

Итак, все начинается с библиотеки. Естественно, в ходе статьи мы разберем, как работают элементы анти-чита, но пока я предлагаю просто взять себе готовый и бесплатный простой пример:

https://github.com/TinyPlay/Pixel-Anticheat

Данная библиотека включает в себя:

  • Несколько видов детектеров читов (Speed Hack, Wall Hack, Teleport Hack, Time Hack, Assembly Injection, Memory Hack);

  • Защищенные типы, шифрующие свои значения;

  • Классы для защищенного хранения данных в Player Prefs или файлах;

  • Библиотеки шифрования и хеширования (AES, RSA, SHA, MD5, Base64, xxHash);

  • Библиотека для получения сетевого и локального времени;

  • Библиотеки-утилиты;

  • Сцена-пример работы с UI анти-чита;

  • Единый интерфейс управления;

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

Детекторы читов работают в автоматическом режиме с минимальными настройками. Далее мы разберем, как они определяют читеров и защищают ваши игры.

Стоит сказать, что какой-бы античит не был у вас, в идеале не стоит хранить и обрабатывать какие-либо данные на стороне клиента. Если есть возможность - делайте все критические манипуляции с данными на сервере.

Подключение детекторов читов:

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

AntiCheat.Instance().AddDetector<MemoryHackDetector>().InitializeAllDetectors();

Также вы можете подключить все детекторы сразу:

AntiCheat.Instance()
    .AddDetector<MemoryHackDetector>()
    .AddDetector<InjectionDetector>()
    .AddDetector<SpeedHackDetector>()
    .AddDetector<WallHackDetector>()
    .AddDetector<TeleportDetector>()
    .AddDetector<TimeHackDetector>()
    .InitializeAllDetectors();

Некоторые из них могут содержать параметры. Например:

AntiCheat.Instance().AddDetector<SpeedHackDetector>(new SpeedhackDetectorConfig(){
    coolDown = 30
}).InitializeAllDetectors();

Чит-детекторы

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

В нашей библиотеке мы предоставили пример, в котором при запуске таких событий отображается UI с подозрением читерства:

namespace PixelAnticheat.Examples
{
    using UnityEngine;
    using System.Collections.Generic;
    using PixelAnticheat.Detectors;
    using PixelAnticheat.Models;

    public class SampleScript : MonoBehaviour
    {
        [Header("Anti-Cheat References")] 
        [SerializeField] private Transform _playerTransform;
        
        [Header("UI Referneces")] 
        [SerializeField] private AntiCheatUI _antiCheatUI;

        private void Start()
        {
            // Initialize All Detectors
            AntiCheat.Instance()
                .AddDetector<MemoryHackDetector>(new MemoryHackDetectorConfig())
                .AddDetector<InjectionDetector>()
                .AddDetector<SpeedHackDetector>(new SpeedhackDetectorConfig(){
                    coolDown = 30,
                    interval = 1f,
                    maxFalsePositives = 3
                })
                .AddDetector<WallHackDetector>(new WallhackDetectorConfig(){
                    spawnPosition = new Vector3(0,0,0)
                })
                .AddDetector<TeleportDetector>(new TeleportDetectorConfig(){
                    detectorTarget = _playerTransform,
                    availableSpeedPerSecond = 20f
                })
                .AddDetector<TimeHackDetector>(new TimeHackDetectorConfig(){
                    availableTolerance = 120,
                    networkCompare = true,
                    timeCheckInterval = 30f
                })
                .InitializeAllDetectors();

            // Add Detectors Handlers
            AntiCheat.Instance().GetDetector<MemoryHackDetector>().OnCheatingDetected.AddListener(DetectorCallback);
            AntiCheat.Instance().GetDetector<InjectionDetector>().OnCheatingDetected.AddListener(DetectorCallback);
            AntiCheat.Instance().GetDetector<SpeedHackDetector>().OnCheatingDetected.AddListener(DetectorCallback);
            AntiCheat.Instance().GetDetector<WallHackDetector>().OnCheatingDetected.AddListener(DetectorCallback);
            AntiCheat.Instance().GetDetector<TeleportDetector>().OnCheatingDetected.AddListener(DetectorCallback);
            AntiCheat.Instance().GetDetector<TimeHackDetector>().OnCheatingDetected.AddListener(DetectorCallback);
        }

        private void OnDestroy()
        {
            AntiCheat.Instance().GetDetector<MemoryHackDetector>().OnCheatingDetected.RemoveAllListeners();
            AntiCheat.Instance().GetDetector<InjectionDetector>().OnCheatingDetected.RemoveAllListeners();
            AntiCheat.Instance().GetDetector<SpeedHackDetector>().OnCheatingDetected.RemoveAllListeners();
            AntiCheat.Instance().GetDetector<WallHackDetector>().OnCheatingDetected.RemoveAllListeners();
            AntiCheat.Instance().GetDetector<TeleportDetector>().OnCheatingDetected.RemoveAllListeners();
            AntiCheat.Instance().GetDetector<TimeHackDetector>().OnCheatingDetected.RemoveAllListeners();
        }
        
        private void DetectorCallback(string message){
            Debug.Log("Cheating Detected: " + message);
            if (_antiCheatUI != null)
            {
                _antiCheatUI.SetContext(new AntiCheatUI.Context
                {
                    message = message,
                    OnCloseButtonClicked = QuitGame,
                    OnContactsButtonClicked = GoToSupport
                }).ShowUI();
            }
        }

        private void QuitGame()
        {
            Application.Quit();
        }

        private void GoToSupport()
        {
            Application.OpenURL("https://example.com/");
        }
    }
}

Если развить эту идею и объединить с серверной частью - можно отправлять репорты о чите модераторам, которые будут проверять честность/нечестность игры. Но в идеале, все данные лучше хранить на сервере и проверять там же.

А теперь, немного теоретической части.

Детектор Speed Hack

Спидхак - по своей сути чит, ускоряющий игровое время, за счет чего игрок начинает быстро перемещаться. Для того, чтобы искоренить это - мы сравниваем пройденное время внутри игрового цикла Unity через Time.deltaTime и время, прошедшее в системе.

Если это время не совпадает (с определенной погрешностью и с допустимым количеством пропусков), мы выдаем событие о читерстве.

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

Детектор Wall Hack

Wall Hack - грубо говоря хождение сквозь стены. Он же может быть включен и в NoClip хаки. У объектов отключаются некоторые (или все) коллайдеры. Чтобы защититься от этого - мы создаем сервисные объекты RB или Character Controller, с помощью которых постоянно проверяем возможность хождения сквозь стену при помощи сервисного объекта стены.

Таким образом, при помощи сервисных объектов (фейк-стене и фейк-игроках), мы проверяем работоспособность коллизий в игре.

Детектор изменения времени (Time Hack)

Дополнительный способ защиты, при котором проверяется наличие хака на отмотку времени (вперед или назад) для быстрого фарма ресурсов, привязанного ко времени. Особенно такое распространено в различного рода айдлерах, фермах и т.д.

Сверять время можно локально, либо при помощи интернета. У нас в библиотеке реализовано оба метода.

В чем же их смысл?

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

Если же через 10 секунд, мы получаем разницу в интернет-времени в 10 секунд, то при перемотке времени на телефоне - мы получаем разницу в 10 секунд + определенный промежуток времени, на который мы отмотались.

Таким образом мы можем вычислить перемотку времени и исключить её, создав дополнительный слой защиты.

Детектор внедрения зависимостей (Assembly Injection)

Здесь все достаточно просто - мы задаем белый список библиотек, которые могут быть подключены в финальный билд нашей игры и, если они не совпадает со списком, при запущенной игре, значит, либо её код был изменен (или код .dll библиотек), либо кто-то внедрился к нам в игру.

Опять же, для большей устойчивости, не забудьте провести обфускацию вашего кода, а также использовать IL2CPP вместо Mono среды.

Детектор изменения памяти (Memory Hack)

Данный детектор работает в связке с защищенными типами. Защищенные типы хранят в себе реальное значение и его зашифрованный хэш. Если же реальное значение изменяется из вне, то его хеш остается неизменным, а значит, доступ к памяти был произведен из вне (например, изменен через Cheat Engine).

Таким образом, мы можем безопасно работать с нашими данными, используя защищенные типы для хранения значений.

Детектор телепорта (Teleport Hack)

Данный способ, помогает частично избавиться как от некоторых видов спидхака, так и от хаков на телепорт. Его суть проста - каждый определенный промежуток времени (к примеру раз в 10 секунд) мы проверяем дистанцию между текущей позицией игрока и его новой позицией. Если эта позиция изменилась больше допустимого - игрок телепортировался.

Здесь же можно дополнительно использовать защищенные типы для шифрования векторов.

Защищенные типы

Суть проста - в них мы храним несколько значений. Реальное и шифрованное. Если они не совпадают, значит кто-то изменил их из вне. И нам нужно проверять регулярно эти изменения, что в нашем случае делает детектор памяти.

Защищенными могут быть как базовые типы (вроде float, int, string и пр.), так и какие-то кастомные (Vector2, Vector3, Color, Quaternion и др).

В заключении

Защищать вашу игру, несомненно важно. И чтобы максимально добиться этого - нужно использовать несколько слоев защиты. Однако, всегда взвешивайте пользу и вред, ведь защита - это дополнительная нагрузка на устройства, а иногда и неудобства для конечного пользователя.

Второй вывод, хотя я его писал во вводной, но все равно повторюсь. Используйте клиент только для отображения ваших данных. Реализуйте бизнес-логику и работу с данными на серверах, конечно же учитывая соотношение пользы/вреда.

Спасибо за прочтение статьи. Надеюсь, она была вам полезна, как и библиотека, которую я приложил выше.

Удачи в ваших проектах. И конечно же, буду рад обсудить с вами.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы добавляете защиту в ваши игры?
0% Локальную 0
0% Локально + проверка на сервере 0
0% Нет 0
Никто еще не голосовал. Воздержавшихся нет.
Источник: https://habr.com/ru/post/589899/


Интересные статьи

Интересные статьи

Мы постоянно тестируем новые решения для наших проектов и недавно решили разобраться, что под капотом у Cisco Umbrella. Сам вендор заявляет, что это облачное решение для ...
Сегодня я расскажу о том, как некоторые проекты в Pixonic пришли к тому, что для всего мирового фронтэнда уже давно стало нормой, — к реактивному связыванию. Подавляющее большинств...
Добрый день, коллеги! Сегодня начинаем новый цикл статей, посвященный решениям информационной безопасности от компании Group-IB. Компания работает на рынке информационной безопасн...
Привет! Эта статья о создании собственных визуальных компонентов в UI на примере компонента для визуализации системы частиц в Canvas’e. Данная информация будет полезна для реализации различных...
Привет, Хабр! Меня зовут Борис, и я отвечаю за информационную безопасность клиентских и внутренних сервисов в Linxdatacenter. Сегодня мы поговорим о том, как защитить от утечки и потери данны...