Всем привет! С вами снова Илья и мы продолжаем серию статей по разработке игр на 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 и др).

В заключении

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

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

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

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

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


  1. Sazonov
    18.11.2021 02:11
    +1

    Скажите, я правильно понимаю, что описанные вами читы невозможны когда вся игровая механика обсчитывается на сервере? (К примеру как в quake3)

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


    1. Voody2506
      18.11.2021 08:09

      Добрый день, сервер не дает 100% защиту. В зависимости от геймплея, читы вроде AimBot, Wall Hack всегда можно провернуть. Полная симуляиця игры на Сервере спасает от определённого вида читов, но не от всего.


      1. Sazonov
        18.11.2021 09:25

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

        А wallhack - достаточно просто не присылать данные с сервера пока объект действительно не станет виден. А для компенсации возможных лагов (типа враг выскочил из-за угла а клиент увидит это на 50мс позже чем должен) - можно всё таки ввести небольшую опережающую отправку за 100-200мс до предполагаемого попадания в зону видимости. Такие значения не сыграют особой роли и любой wallhack будет бесполезен. Если не ошибаюсь, то подобным образом реализован туман войны в доте2.

        Почему-то подумалось что проекты типа google stadia пресекут читерство в корне, просто за счёт своей архитектуры.


        1. jetcar
          18.11.2021 13:29

          Как мало вы знаете о аим ботах. Есть целые комманды которые работают над тем чтоб писать аймботов и валхаки и потом их продают, так что это нерешимая проблема отмычки и замка.

          Stadia и geforce now не позволят запустить читы, но это как с нетликс и другими стриминговыми платформами у тебя не всё в одном месте, а только кусочек и там куча ограничений, так что не верю что основная масса игроков перейдёт и уж темболее ФПС игр где отклик очень важен.


        1. Voody2506
          18.11.2021 15:08

          @Sazonov

          Про WallHack вы привели в пример Доту. Возьмите игру без тумана войны. Как быть в CallOfDuty? В шутерах вы 100% знаете кто находится в комнате.

          Сервер не спасает от всех видов читов, это - миф. В Call Of Duty уже несколько лет симуляция на сервере. Как были читеры, так и остались.

          Aim Bot кстати тоже аналитикой очень-очень сложно отловить. Они не 100% попадают в голову. Игроки реально могут так грамотно стрелять.


    1. SadOcean
      18.11.2021 11:14
      +2

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

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


  1. fanatikvoice
    18.11.2021 16:13
    +1

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