Introduction


Games are a great way to kill the time and most of you probably already played video games. Some of you have already been tempted to cheat in video game because somehow, they find it funnier like this. There is a lot of kind of games over internet. Some of them are even playable under your browser. To speak about the most famous technologies used right now, we have Flash, Html5 and Unity3D. Today, we’ll be talking about scoring games. More exactly, we’ll be talking about scoring game made with the game engine Unity 3D.

The use of Unity3D is growing with the years because of its convenience to make and deploy games across all platforms. Game programming is no longer reserved to special organization with high resources. Games can be made easily by anyone who has a computer and know basics of programming.

Although I like to see new games, I've realized most of them were poorly developed and were containing big flows even when they were made by big companies.

Why it works


If you already tried to make an online game, you were probably facing issues like deciding who will have the main authority, the client, the server, both? As a matter of fact, we keep saying to people to never trust a client and all the game logic should be calculated server side but Hey! Servers can cost a lot and all of games don’t necessarily need to have a big client-server communication. If you’re an indie developer, you make your game on your free time and you don’t have too much time to spare on the security part of your game because you probably trust the tools you’re using or you don't even realize what can be the consequences.

People making a simple scoring game like the popular “flappy bird” or “doodle jump” will only need to send the final score to an API and it seems overkill to make an emulator for your game if a single page can handle the score, right? Well, that’s what most of developers think and that’s the reason most of games playable in browsers are basing the game authority in the client.

Theory and method of exploitation


Now we know most of games are client based, what if we could get that client and extract it? Extracting a game will give us all its resources (textures, animation, scripts, etc.). Since we’re aiming to cheat our score, we will concentrate on extracting the scripts. Considering the client has authority, it has to contain the endpoint and methods to send the score to an API which will update the scoreboard.

No more talking, let’s see what we can do!

Practice and methodology step-by-step


For this example, I will apply this tutorial on a real game from a website with 30k+ active users. Since this this has not been fixed yet, I will replace sensitive data by fake ones but all the process and ideas are stayed intact.

Let’s find a web player game made with unity on the internet. We've found a scoring game named “Rookie Jump” over the internet, graphics looks cool but something is wrong. When we look at the scoreboard, two peoples are over 1 million points and others player from the top 100 can't pass the cap of 100 000..

Is that game “hack-able”? I would say, yes !

The website is asking me to create an account in order to save my progress and score on the scoreboard. Let’s do so... Ok, the account is made now let’s open your browser developer tools (F12) and look for a file with an extension of “.unity3d”. We’ve found the file WebPlayer.unity3d

getplayer.jpg

Let’s download the player on our computer. This is the main entry point of the game and all the data are supposed to be stored in this file. So far, opening it with notepad or trying to extract it with a normal editor will lead nowhere, everything looks compressed. As I’m lazy, I’ll just google if there is any extractor for unity3d file and bingo. Here we come with a tool named "Unity3D obfuscator", this tool allows us to unpack any web player files generated by unity. Here is what the GUI looks like:

softextractor.jpg

Quite explicit, right?

After extracting the game in a folder, we obtain something like this

extractedfiles.jpg

OK, that’s good but we’re still having binaries and nothing is human readable, shitty software, it didn't extracted anything! Well, it actually made a great part of the work since all we have to do now is to reverse the dll. For this tutorial, I’ll be using dotPeek because it’s free and convenient.

All dll in that folder can be reversed but in our case, we’re looking to change the score and that is happening in Assembly-CSharp.dll because other dll are just containing the Unity3D library. Let’s drag & drop the dll into dotPeek and see what we have:

reverseddll_global.jpg

Wow, all the game is just there in a big god object. Let’s have a look at the methods shown. In this kind of game, the moment the score is set should be at the time the player die, right? In unity (and also general gaming development), we like to split a game into different categories:
  1. Initialization (setting the window size, configuration etc)
  2. Events (all kind of events like input from keyboard, controller or even server events)
  3. Update (calculate the state of the games according the events received)
  4. Draw ( draw to your screen the results of the Update)

Since we’re looking for the method that managing when the player dies, let’s have a look in the Update...
Code:
 private void Update()
  {
    float num1 = (float) this.playerTrans.get_position().y;
    if ((double) num1 > (double) this.nextPlatformCheck)
      this.PlatformMaintenaince();
    this.testCredits();
    this.testCreditsPirates();
    float num2 = (float) ((Component) this).get_transform().get_position().y;
    float num3 = Mathf.Lerp(num2, num1, Time.get_deltaTime() * 10f);
    if (this.playerTrans.get_position().y > (double) num2)
      ((Component) this).get_transform().set_position(new Vector3((float) ((Component) this).get_transform().get_position().x, num3, (float) ((Component) this).get_transform().get_position().z));
    else if ((double) num1 < (double) num2 - 9.0)
      this.EndRound();
    if (GameGUI.level == 100)
      this.EndRound();
    if ((double) this.multiplicateurScore * (double) num1 <= (double) GameGUI.score)
      return;
    GameGUI.score = this.multiplicateurScore * (int) num1;
  }



We can see an EndRound() method which seems like to be called when the player die or reach the maximum level. Let’s see what the method EndRound() does
Code:
private void EndRound()
  {
    if (this.waitJoueur)
      return;
    this.waitJoueur = true;
    if (this.numPartie == this.nombreDePartieParJoueur)
    {
      this.numPartie = 0;
      PlayerPrefs.SetInt("PartieNumero", this.numPartie);
    }
    if (this.numPartie != 0)
    {
      this.GameOver();
    }
    else
    {
      GameGUI.SP.CheckHighscore();
      this.soumettre(GameGUI.SP.getBestScore());
      GameControl.gameState = GameState.end;
      GameControl.distancePlateformMax = 3.5f;
      Time.set_timeScale(0.0f);
    }
  }




In this method, we can see two interesting calls
Code:
GameGUI.SP.CheckHighscore(); 



And
Code:
this.soumettre(GameGUI.SP.getBestScore());



The second one seems like to take the best score in parameters, that’s probably what we’re looking for. One way to know => check this method.
Code:
  public void soumettre(int newHighScore)
  {
    GameControl.HighScore = newHighScore;
    if (GameControl.playerID != string.Empty)
    {
      string key1 = "421xor" + (GameControl.HighScore * 42).ToString() + "21" + GameControl.playerID;
      object[] objArray = new object[6];
      int index1 = 0;
      string str1 = "http://www.mysupergame.com/games/rookie_jump_1_0/setScore.php?gameID=1&playerID=";
      objArray[index1] = (object) str1;
      int index2 = 1;
      string str2 = GameControl.playerID;
      objArray[index2] = (object) str2;
      int index3 = 2;
      string str3 = "&score=";
      objArray[index3] = (object) str3;
      int index4 = 3;
      // ISSUE: variable of a boxed type
      __Boxed<int> local = (ValueType) GameControl.HighScore;
      objArray[index4] = (object) local;
      int index5 = 4;
      string str4 = "&clef=";
      objArray[index5] = (object) str4;
      int index6 = 5;
      string key2 = this.getKey(key1);
      objArray[index6] = (object) key2;
      getPHP.setURL(string.Concat(objArray), "envoi");
    }
    else
      getPHP.setURL("http://www. mysupergame.com/games/rookie_jump_1_0/scores.php", "score");
  }



Almost there


Wow, seems like we found it, there is a plain text url redirecting to the game API which manage the scoreboard. One thing there, it seems like the developers have tried to secure the call to the API by implementing a key system. After all, we’ll have to think a little on how to generate the url to send to the server with our score…

First of all, we can see a property GameControl.playerID. Do you remember it has been asking me to register on the website before playing this game? That’s id might probably be the same as my user id. Let’s try with the id I've found under my profile page. Back to the code, we can see a call to a method getKey which is generating an md5hash of some aggregated values. Let’s be lazy one more time, we decide to copy those methods into a new C# project. We’re mocking the setUrl method to actually display it under our console and here is what we got:
Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace testrookiejuump
{
    class Program
    {

        public static string getKey(string key)
        {
            byte[] hash = ((System.Security.Cryptography.HashAlgorithm)System.Security.Cryptography.MD5.Create()).ComputeHash(Encoding.ASCII.GetBytes(key));
            StringBuilder stringBuilder = new StringBuilder();
            for (int index = 0; index < hash.Length; ++index)
                stringBuilder.Append(hash[index].ToString("X2"));
            return stringBuilder.ToString();
        }


        public static void soumettre(int newHighScore)
        {
            var GameControl = new { HighScore = newHighScore, playerID = "4242" };

            if (GameControl.playerID != string.Empty)
            {
                string key1 = "421xor" + (GameControl.HighScore * 42).ToString() + "21" + GameControl.playerID;
                object[] objArray = new object[6];
                int index1 = 0;
                string str1 = "http://www. mysupergame.com/games/rookie_jump_1_0/setScore.php?gameID=1&playerID=";
                objArray[index1] = (object)str1;
                int index2 = 1;
                string str2 = GameControl.playerID;
                objArray[index2] = (object)str2;
                int index3 = 2;
                string str3 = "&score=";
                objArray[index3] = (object)str3;
                int index4 = 3;
                // ISSUE: variable of a boxed type
                int local = GameControl.HighScore;
                objArray[index4] = (object)local;
                int index5 = 4;
                string str4 = "&clef=";
                objArray[index5] = (object)str4;
                int index6 = 5;
                string key2 = getKey(key1);
                objArray[index6] = (object)key2;
                setURL(string.Concat(objArray), "envoi");
            }
            else
                setURL("http://www. mysupergame.com/games/rookie_jump_1_0/scores.php", "score");
        }



        public static void setURL(string URL, string mode)
        {
            Console.WriteLine(URL + " | " + mode);
        }

        static void Main(string[] args)
        {
            soumettre(1000000);
        }
    }
}



Execute copy paste the generated url into a browser and voila! Our score is set on the scoreboard, now sitting on first rank!

Conclusion


I hope some game developers who want a real challenging game will stop publishing scoring games with that poor design. will read me and understand why they need to secure their games.

As you may have notice, this flaw in Unity3D (which is not really related to unity3d because it’s actually present in any application client side)can goes much further than simply sending score to an API and exploitation of this technique can be highly devastating. It’s the responsibility of the developers to ensure clients are just used as an input/draw machine and not containing any game authority. In that case, the client shouldn't be able to send the score to the server.

Bonus: Workaround


One way to fix that game would have been to copy the game logic server side. The client should be only able to send which key are pressed to the server and the server should check every action to ensure they are legitimate. To avoid lagging issue due to the massive sending of packets, we can let the client being able to calculate the next position of the player but we must have a good synchronization with the server. The big problem with this is the need of a server that can host your game emulator, it's expensive and much harder to put in place, for those reason we'll probably always be able to perform this kind of attack.