Защита от читеров на примерах для 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 и др).

В заключении

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

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

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

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

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

Вы добавляете защиту в ваши игры?

15.15% Локальную5
33.33% Локально + проверка на сервере11
51.52% Нет17

Проголосовали 33 пользователя. Воздержались 24 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/589899/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *