Protection from cheaters by examples for Unity

Hello everyone! Ilya is with you again and we continue the series of articles on developing games on Unity. Today we will analyze the process of protecting your games using examples. I will explain it based on our open library created for Pixel Incubator – a community in which we teach how to make games and more.

Beginning of work

So it all starts with the library. Naturally, in the course of the article we will analyze how anti-cheat elements work, but for now I suggest just taking a ready-made and free simple example:

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

This library includes:

  • Several types of cheat detectors (Speed ​​Hack, Wall Hack, Teleport Hack, Time Hack, Assembly Injection, Memory Hack);

  • Protected types that encrypt their values;

  • Classes for secure storage of data in Player Prefs or files;

  • Encryption and hashing libraries (AES, RSA, SHA, MD5, Base64, xxHash);

  • Library for getting network and local time;

  • Utility libraries;

  • An example scene of working with the anti-cheat UI;

  • Unified management interface;

More layers of protection are also planned to be added soon. All of them can be dynamically connected / disconnected.

Cheat detectors work in automatic mode with minimal settings. Next, we’ll look at how they identify cheaters and protect your games.

It is worth saying that whatever anti-cheat you have, ideally you shouldn’t store and process any data on the client side. If possible, do all critical data manipulation on the server.

Connecting cheat detectors:

Any detector can be initialized as follows:

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

You can also connect all detectors at once:

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

Some of them may contain parameters. For example:

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

Cheat detectors

These classes allow you to intercept possible ways of foul play and notify the code about it using events.

In our library, we have provided an example in which when such events are triggered, a UI is displayed with suspicion of cheating:

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/");
        }
    }
}

If you develop this idea and combine it with the server side, you can send cheat reports to moderators who will check the honesty / dishonesty of the game. But ideally, it is better to store all data on the server and check it there.

And now, a little theoretical part.

Detector Speed ​​Hack

Speedhack – in essence, a cheat that speeds up the playing time, due to which the player begins to move quickly. In order to eradicate this – we compare the elapsed time inside the Unity game loop through Time.deltaTime and time elapsed in the system

If this time does not match (with a certain error and with the allowed number of gaps), we issue a cheating event.

It is worth noting that if you programmatically change the TimeScale, then the anti-cheat may work falsely, but for such cases, you can enter a certain coefficient for changing the time, or temporarily disable the detector.

Wall Hack Detector

Wall hack – roughly speaking, walking through walls. It can also be included in NoClip hacks. Some (or all) colliders are disabled for objects. To protect against this, we create RB or Character Controller service objects, with which we constantly check the ability to walk through the wall using the wall service object.

Thus, using service objects (fake wall and fake players), we check the functionality of collisions in the game.

Time Hack Detector

An additional method of protection, in which the presence of a hack for rewinding time (forward or backward) for fast farming of resources tied to time is checked. This is especially common in various kinds of idlers, farms, etc.

You can check the time locally or via the Internet. Both methods are implemented in our library.

What is their meaning?

We take the time from the Internet and local time, and after a certain period of time we check the difference between the local time and the time from the Internet.

If after 10 seconds, we get a difference in Internet time of 10 seconds, then when rewinding the time on the phone, we get a difference of 10 seconds + a certain period of time for which we rewound.

Thus, we can calculate the rewind of time and exclude it, creating an additional layer of protection.

Assembly Injection Detector

Everything is quite simple here – we set a whitelist of libraries that can be included in the final build of our game, and if they do not match the list when the game is running, then either its code has been changed (or the code of .dll libraries), or who -to infiltrated our game.

Again, for more robustness, remember to obfuscate your code and also use IL2CPP instead of Mono.

Memory change detector (Memory Hack)

This detector works in conjunction with protected types. Protected types store the real value and its encrypted hash. If the real value changes from outside, then its hash remains unchanged, which means that the memory access was made from outside (for example, changed through the Cheat Engine).

This way we can safely work with our data using protected types to store values.

Teleport Hack

This method helps to partially get rid of both some types of speed hacks and teleport hacks. Its essence is simple – every certain period of time (for example, every 10 seconds) we check the distance between the player’s current position and his new position. If this position has changed more than the allowable one, the player has teleported.

Here you can additionally use protected types to encrypt vectors.

Protected types

The point is simple – we store several values ​​in them. Real and encrypted. If they do not match, then someone changed them from outside. And we need to check regularly for these changes, which in our case does the memory detector.

Both basic types (like float, int, string, etc.) and some custom ones (Vector2, Vector3, Color, Quaternion, etc.) can be protected.

In custody

Protecting your game is undoubtedly important. And in order to achieve this as much as possible, you need to use several layers of protection. However, always weigh the benefits and harms, because protection is an additional burden on devices, and sometimes an inconvenience for the end user.

The second conclusion, although I wrote it in the introduction, will still repeat myself. Use the client only to display your data. Implement business logic and data manipulation on servers, of course taking into account the benefit / harm ratio.

Thanks for reading the article. I hope it was useful to you, as well as the library that I attached above.

Good luck with your projects. And of course, I will be glad to discuss with you.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *