DEV Community

Cover image for Biggest and common Unity security risks
GuardingPearSoftware
GuardingPearSoftware

Posted on • Originally published at guardingpearsoftware.com

Biggest and common Unity security risks

Building games in Unity is a great experience. When you are ready to release your project, security becomes an important priority. Many developers wonder how easy it is for players to cheat in their game or extract their assets. Security in games is about making attacks difficult enough that most people simply give up. We will look at common security risks in Unity and how you can protect your project.

How secure is Unity by default?

Out of the box, Unity does not offer strong protections against reverse engineering or cheating. The engine prioritizes performance and cross-platform compatibility over strict security. Your C# code compiles into assemblies that are relatively easy to read. Assets are packed into standard formats that existing tools can unpack. You need to implement your own security measures based on your game type. A single-player offline game needs different protection than a competitive multiplayer shooter.

The threat landscape in one diagram

Think of your game as four connected parts. The client is the application running on the player device. The network is the connection between the client and your servers. The backend includes your game servers and databases. The store is the platform where you sell the game and in-app purchases. Each of these parts has different vulnerabilities. Attackers can modify the client, intercept network traffic, overload the backend, or spoof store receipts.

Risk 1: Memory editing and runtime tampering

Players often use external tools to scan the game memory while it is running. If they find the exact memory address for their health or money, they can change the value. This happens because variables in memory are unencrypted by default. You can make this harder by obscuring sensitive values. For example, you can store your health as two separate numbers that you add together, or use an XOR operation to hide the real value.

// Example of a basic protected integer that hides its true value in memory using XOR encryption.
// This prevents simple memory scanners like Cheat Engine from easily finding your variables.
public class ProtectedInt 
{
    private int cryptoKey;
    private int hiddenValue;

    public ProtectedInt(int initialValue) 
    {
        // Generate a random key and scramble the initial value
        cryptoKey = UnityEngine.Random.Range(1000, 9999);
        hiddenValue = initialValue ^ cryptoKey;
    }

    public int GetValue() 
    {
        // Decrypt the value when you need to read it
        return hiddenValue ^ cryptoKey;
    }

    public void SetValue(int newValue) 
    {
        // Generate a new key and scramble the new value every time it changes
        cryptoKey = UnityEngine.Random.Range(1000, 9999);
        hiddenValue = newValue ^ cryptoKey;
    }
}
Enter fullscreen mode Exit fullscreen mode

Risk 2: Decompilation and reverse engineering

If you use the Mono scripting backend, your C# code is compiled into standard .NET DLL files. Anyone can open these files in decompilation tools and read your exact code. IL2CPP converts your C# code into C++ before compiling it into a native binary. This makes decompilation much harder, but attackers can still extract function names and structures using specialized tools. You can use code obfuscators to rename your classes and variables into meaningless strings.

Here is a small example of what your code oculd looks like before and after obfuscation.

Before obfuscation, your logic is easy to read:

// Unobfuscated code: Very easy for an attacker to read and understand the password logic.
public bool CheckPassword(string input) 
{
    string correct = "admin123";
    if (input == correct) 
    {
        UnlockGame();
        return true;
    }
    return false;
}
Enter fullscreen mode Exit fullscreen mode

After obfuscation, the same logic becomes a confusing mess. The string is encrypted, the names are changed, and the simple condition is turned into a state machine loop:

// Obfuscated code: The method name, parameters, and strings are hidden.
// The simple 'if' statement is converted into a complex state machine (control flow obfuscation).
public bool x0b1(string x0b2) 
{
    int state = 0;
    while (true) 
    {
        switch (state) 
        {
            case 0:
                string x0b3 = DecryptString(new byte[] { 0x61, 0x64, 0x6D });
                state = (x0b2 == x0b3) ? 1 : 2;
                break;
            case 1:
                x0b4();
                return true;
            case 2:
                return false;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Risk 3: Asset ripping and data extraction

Unity stores models, textures, and audio in standard asset bundles and resource files. Community tools allow anyone to extract these files from your game folder. If you want to protect exclusive art or licensed music, you cannot rely on Unity default packing. You have to encrypt your asset bundles before building your game and decrypt them in memory at runtime. This will slow down loading times, so only encrypt the most sensitive assets.

For example you could use AES encryption to encrypt your asset bundles and decrypt them in memory at runtime.

using System.IO;
using System.Security.Cryptography;
using UnityEngine;

// This example shows how to decrypt an asset bundle on the fly in memory.
// It prevents having to save the decrypted file back to the disk where a user could steal it.
public class EncryptedBundleLoader : MonoBehaviour
{
    public AssetBundle LoadEncryptedBundle(string filePath, byte[] key, byte[] iv)
    {
        // Open the encrypted file from disk
        using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            using (Aes aes = Aes.Create())
            {
                aes.Key = key;
                aes.IV = iv;

                // Create a stream that decrypts data as it is read
                using (CryptoStream cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    // Load the asset bundle directly from the decrypted memory stream
                    return AssetBundle.LoadFromStream(cryptoStream);
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Risk 4: Save files, PlayerPrefs, and local economy manipulation

Many developers use PlayerPrefs to save game progress or currency. PlayerPrefs saves data in plain text in the registry or on disk. Players can open these files and give themselves infinite money. You should always protect your local save files.

A simple option is using Base64 encoding to hide your PlayerPrefs data. This stops casual players from reading it, even though it is not true encryption.

// BAD (for security): Storing plain text allows anyone to open the file and change "Coins=10" to "Coins=9999".
// BETTER: Hiding the text using Base64 encoding. It is not true encryption, but it stops casual tampering.
public void SaveData(string plainText)
{
    // Convert the plain text to raw bytes, then to a Base64 string
    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    string encodedText = System.Convert.ToBase64String(bytes);

    PlayerPrefs.SetString("SaveData", encodedText);
}

public string LoadData()
{
    string encodedText = PlayerPrefs.GetString("SaveData", "");
    if (string.IsNullOrEmpty(encodedText)) return "";

    // Convert the Base64 string back into readable plain text
    byte[] bytes = System.Convert.FromBase64String(encodedText);
    return System.Text.Encoding.UTF8.GetString(bytes);
}
Enter fullscreen mode Exit fullscreen mode

Risk 5: Client-authoritative multiplayer and trust-the-client design

The biggest mistake in multiplayer games is letting the client make important decisions. If the client tells the server it killed an enemy, an attacker can easily forge that message. The server should always be authoritative. The client should only send inputs like a button press. The server then calculates if the shot hit and tells the client the result.

// BAD: The client decides if a player is killed and tells the server.
// A hacker can easily send this message to kill anyone.
public void OnHitEnemy_Vulnerable(int targetPlayerId) 
{
    NetworkManager.SendToServer("PlayerKilled", targetPlayerId);
}

// GOOD: The client only tells the server it fired a weapon.
// The server calculates the math to see if a hit actually happened.
public void OnFireWeapon_Secure(Vector3 aimDirection) 
{
    NetworkManager.SendToServer("PlayerFiredWeapon", aimDirection);
}
Enter fullscreen mode Exit fullscreen mode

Risk 6: Insecure networking

If your game communicates with a backend API using plain HTTP, attackers can read and modify the requests. Always use HTTPS for external API calls. For multiplayer games using UDP, make sure your networking library supports encryption. You should also validate that incoming packets match the expected format to prevent attackers from crashing your server with bad data.

// BAD: Using plain HTTP allows attackers to read or change the data mid-flight.
public IEnumerator SendScore_Vulnerable(int score) 
{
    string url = "http://mygameapi.com/submitscore?val=" + score;
    using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(url)) 
    {
        yield return request.SendWebRequest();
    }
}

// GOOD: Always use HTTPS to encrypt the communication between the client and the server.
public IEnumerator SendScore_Secure(int score) 
{
    string url = "https://mygameapi.com/submitscore?val=" + score;
    using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(url)) 
    {
        yield return request.SendWebRequest();
    }
}
Enter fullscreen mode Exit fullscreen mode

Risk 7: Exposed secrets

Never store API keys, database passwords, or third-party tokens inside your Unity project. Attackers will find them by decompiling your client. If your game needs to access a secure database, create your own web server. The game client talks to your web server, and your web server talks to the database. The secret keys only live on your web server.

// BAD: Never do this. If someone decompiles your game, they have your database password.
public class DatabaseManager : MonoBehaviour 
{
    private string myDatabasePassword = "super_secret_password_123";
    private string myStripeApiKey = "sk_live_123456789";

    // ... Direct database connection logic ...
}

// GOOD: Your Unity game only knows the URL of your own secure web server.
// The web server holds the actual passwords and API keys.
public class ApiManager : MonoBehaviour 
{
    private string backendUrl = "https://api.mygame.com/getplayerdata";

    // ... Code to request data from your secure backend ...
}
Enter fullscreen mode Exit fullscreen mode

Risk 8: Store and IAP bypass

On mobile devices, players use modified store applications to fake successful in-app purchases. The Unity client will receive a success message and grant the item without any real money changing hands. You must implement server-side receipt validation. When the store confirms a purchase, send the receipt token to your own server. Your server then checks with Apple or Google to verify the receipt is valid before granting the item.

// When the mobile store says the purchase was successful, 
// DO NOT immediately give the player the item.
public void OnPurchaseSuccessful(UnityEngine.Purchasing.Product product) 
{
    // Send the receipt data to your own secure server first.
    string receiptData = product.receipt;
    StartCoroutine(VerifyReceiptWithServer(receiptData, product.definition.id));
}

private IEnumerator VerifyReceiptWithServer(string receipt, string productId) 
{
    // Your server will check the receipt against Apple or Google.
    // Only unlock the item when your server replies with "Valid".
    // ... WebRequest logic here ...
}
Enter fullscreen mode Exit fullscreen mode

Risk 9: Piracy and cracked builds connecting to live services

Pirated copies of your game can drain your server resources. If your game requires a backend connection, you need to verify that the client is legitimate. You can use platform-specific authentication like Steamworks or Google Play Services to generate a session ticket. Your backend can verify this ticket with the platform before allowing the player to join a multiplayer match.

// Example using Steamworks.NET concepts to get an auth ticket.
public void RequestServerAccess() 
{
    byte[] ticketBlob = new byte[1024];
    uint ticketSize;

    // Get an encrypted session ticket directly from the Steam client.
    Steamworks.HAuthTicket ticketHandle = Steamworks.SteamUser.GetAuthSessionTicket(ticketBlob, 1024, out ticketSize);

    // Send 'ticketBlob' to your game server. 
    // Your server will then ask Valve if this ticket belongs to a legitimate, paying user.
    NetworkManager.SendToServer("VerifySteamTicket", ticketBlob);
}
Enter fullscreen mode Exit fullscreen mode

Platform differences: PC vs mobile vs console

PC is the most open platform and the hardest to secure. Players have full access to the file system and running memory. Android is also vulnerable because APK files are easy to decompile and modify. iOS is slightly more secure due to strict app signing, but modified devices bypass these protections. Consoles are closed ecosystems and offer the highest level of security, but you still need to secure your network traffic against packet sniffing.

Why Unity games get cracked or cheated quickly

Unity is a popular engine, which means there are many tools built specifically to modify Unity games. Attackers know exactly how Unity structures its memory and file systems. When you build a game in a custom engine, attackers have to figure out everything from scratch. With Unity, they can use existing scripts and tutorials. This shared knowledge makes standard security practices very important for your project.

Severity matrix: impact vs likelihood for your project

Risk type Likelihood Impact on single player Impact on multiplayer
Memory editing High Low High
Decompilation High Medium High
Asset ripping High Medium Low
Save manipulation High Low High
Trust-the-client Medium None Critical
Insecure networking Low None Critical
Exposed secrets Low Critical Critical
IAP bypass High High High
Piracy High Low Medium

Takeaway: Multiplayer games face critical threats from network and client manipulation, while single-player games mainly need to worry about IAP bypass and exposed secrets.

What to fix first

Here is a prioritized checklist based on the type of game you are building.

  • If you are making a single-player game without monetization, your main goal is stopping basic tampering. Encrypt your local save files so players cannot give themselves infinite resources using a simple text editor. You should also consider code obfuscation to make it harder for people to read your game logic.
  • If your game relies on in-app purchases, you must implement server-side receipt validation before doing anything else. Players frequently use fake store applications to bypass purchase checks on mobile devices. You need a secure backend to verify receipts with Apple or Google before you unlock any content.
  • For competitive multiplayer games, your highest priority is moving to a server-authoritative model. The server must dictate everything that happens in the game. You should never trust the client to tell you if a shot hit or how fast a character is moving. You also need to encrypt your network traffic to stop packet modification.
  • When your project contains valuable intellectual property, you need to protect your specific assets and proprietary code. Encrypt your sensitive asset bundles before you build the game and decrypt them in memory at runtime. You can also use IL2CPP along with commercial obfuscators to hide your custom algorithms from reverse engineering.
  • If you are running live services that require player accounts, implement strict session validation. Force players to authenticate using secure platform tokens from services like Steamworks or Google Play Services. This helps prevent pirated copies from connecting to your backend and draining your server resources.

Read more on my blog: www.guardingpearsoftware.com!

Top comments (0)