Unity Obfuscation Methods and Anti-Hack Protection
You finally released the game that you've been working so hard on, and maybe even added a leaderboard to add challenge to the game. But days go by and you notice some players popping up on top of the scoreboard with unrealistically high scores. Your first thought is of course they're hacking, but how do they do that?
The answer is, they're most likely using a program to inject their own values into the memory, with the most popular of such programs being Cheat Engine. Now, in single-player games, hacking doesn't really matter that much, but it becomes a problem when it's a multiplayer game where the other players are involved.
In this post, I will be showing how to make your game more secure against such attacks, which in turn will improve the experience for non-hacking players.
NOTE: This article only briefly covers the most common attacks and basic protection against them. If you need more out-of-the-box solution feel free to check this Asset Store Package.
When it comes to hacking with Cheat Engine there are 2 most common attacks: Speed Hacking and Value Scanning.
Speed Hack
Being the easiest to execute (only requires 2 clicks), the Speed Hack is usually the first choice for novice users.
Speed hack works by speeding up the game's update rate, making everything faster, thus giving hackers an edge over the players who play at the normal speed.
Fortunately, there is a way to detect this hack in Unity. Check the script below:
NOTE: As of today, this method no longer works, therefore, detecting speed hack has become much more difficult in single-player games. Multiplayer games, however, are still able to do it by relying on server-side checks to detect any mismatch in the player-server time and take the appropriate action (kicking/banning the player, etc.).
SC_SpeedhackDetector.cs
using UnityEngine;
using System;
public class SC_SpeedhackDetector : MonoBehaviour
{
//Speed hack protection
public int timeDiff = 0;
int previousTime = 0;
int realTime = 0;
float gameTime = 0;
bool detected = false;
// Use this for initialization
void Start()
{
previousTime = DateTime.Now.Second;
gameTime = 1;
}
// Update is called once per frame
void FixedUpdate()
{
if (previousTime != DateTime.Now.Second)
{
realTime++;
previousTime = DateTime.Now.Second;
timeDiff = (int)gameTime - realTime;
if (timeDiff > 7)
{
if (!detected)
{
detected = true;
SpeedhackDetected();
}
}
else
{
detected = false;
}
}
gameTime += Time.deltaTime;
}
void SpeedhackDetected()
{
//Speedhack was detected, do something here (kick player from the game etc.)
print("Speedhack detected.");
}
}
The script above compares the in-game time with a computer's (system) time. Normally both times are updated at the same rate (assuming the Time.timeScale is set to 1), but when the SpeedHack is activated, it accelerates the in-game update frequency, making the in-game time accumulate faster.
Once the difference between both times becomes too great (in this case 7 seconds, but you can choose any value, just make sure it's not too small to avoid false positives) the script calls the SpeedhackDetected() method which signalizes the presence of SpeedHack.
To use the script make sure it's attached to any Object in the Scene.
Value Scanning
Value scanning is a process of finding relevant values in the game's allocated memory and overwriting them with different values. Most commonly used to increase player Health, Weapon Ammo, or any value that would give a hacker an unfair advantage in the game.
Technically speaking, every value in the game can be overwritten/changed, but does it mean all of them are needed to be protected? Not necessarily. Generally, novice hackers only target the values that are displayed on the screen and are known what they are used for (For example player health, ammo, etc.). So most of the time only "exposed" values need to be protected.
For example on the screenshot above, every value on the screen is a potential target for hacking.
So the question is, how to protect the important values against a Value Scanning attack? The answer is Obfuscation.
Obfuscation is the action of making something obscure, unclear, or unintelligible.
There are many ways to obfuscate a variable, but I will be using a method that I call Randomizer. The random value is generated at the start, then the real value is subtracted from it (subsequently hiding it), then when needed, the hidden value is subtracted from a generated random value, with a difference being the original number. The key is to have a value that is displayed on the screen to have completely different value from the variable, leading hackers in a completely wrong way when scanning.
- Create a new script, call it 'SC_Obf', and paste the code below inside it:
SC_Obf.cs
using UnityEngine;
public class SC_Obf : MonoBehaviour
{
static float random = -1;
public static void Initialize()
{
if(random == -1)
{
random = Random.Range(10000, 99999);
}
}
public static float Obfuscate(float originalValue)
{
return random - originalValue;
}
public static float Deobfuscate(float obfuscatedValue)
{
return random - obfuscatedValue;
}
}
The script above will be used to generate a random number and 2 simple methods to obfuscate and deobfuscate the values.
- Now let's move to a regular example of a script without any obfuscation:
using UnityEngine;
public class SC_Test : MonoBehaviour
{
public float health = 100;
public int ammo = 30;
public void Damage(float points)
{
health -= points;
}
void OnGUI()
{
GUI.Label(new Rect(5, 5, 150, 25), health + " HP");
GUI.Label(new Rect(5, 30, 150, 25), ammo + " Ammo");
}
}
The script above contains 2 simple variables: health (float) and ammo (int). Both variables are displayed on the screen:
This way of doing things is simple and convenient in terms of maintenance, but hackers will easily be able to scan the values and overwrite them using Cheat Engine or similar software.
- Here is the same script, but using obfuscation methods from the 'SC_Obf.cs':
using UnityEngine;
public class SC_Test : MonoBehaviour
{
public float health;
public int ammo;
void Awake()
{
SC_Obf.Initialize();
health = SC_Obf.Obfuscate(100);
ammo = (int)SC_Obf.Obfuscate(30);
}
public void Damage(float points)
{
health = SC_Obf.Obfuscate(SC_Obf.Deobfuscate(health) - points);
}
void OnGUI()
{
GUI.Label(new Rect(5, 5, 150, 25), SC_Obf.Deobfuscate(health) + " HP");
GUI.Label(new Rect(5, 30, 150, 25), SC_Obf.Deobfuscate(ammo) + " Ammo");
}
}
Instead of initializing health and ammo variables directly, we initialize them at the start in void Awake() (Make sure to call SC_Obf.Initialize() before assigning the values using SC_Obf.Obfuscate(value)).
Then when displaying the values, we deobfuscate them on the fly by calling SC_Obf.Deobfuscate(value) thus displaying the real values.
The hacker would try to search for 100 and 30 but would not be able to find them because the real values are completely different.
To manipulate the obfuscated values (ex. subtracting health) we first deobfuscate the value then subtract the needed value then obfuscate the final result back.
For a more advanced solution feel free to check this Asset Store Package.