Skip to main content
  1. Writeups/

PingCTF 2022 - Ping Game

5 mins· 0 · 0 ·
misc PingCTF 2022
Table of Contents
PingCTF 2022 - This article is part of a series.
Part 2: This Article
Ping Game>

Ping Game #

Reconnaissance>

Reconnaissance #

Since this is a Unity game, the first thing to do is to check the presence of the game’s data DLLs (these DLLs can be compressed/compiled into a format that is more complicated to read and modify).

To check this, go to the <Game>/<Game>_Data/Managed/ folder.

For the Ping game, we can see that these DLL files are present, and it’s a very good thing.

dnSpy>

dnSpy #

We are going to use the tool dnSpy to decompile the DLLs files, modify them and recompile them afterwards.

The interface of dnSpy looks like this:

You can open the DLL files in File->Open and by selecting all the DLLs in the folder directly.

Reading the code>

Reading the code #

In Unity, the Assembly-CSharp.dll file contains all the game custom code, and the other DLL files contain the game engine code. If you unroll the file in dnSpy, you will find all the internal classes of the game:

A random class, GameManager, which seems to be important, is now giving away all its secrets.

We find the methods, but also the attributes of the GameManager class. We will be able to see how the system of points, life, game over works, as well as the upload of data towards the server of the game which can be interesting.

Analysis of the GameManager>

Analysis of the GameManager #

Notable variables>

Notable variables #

Nom
lives
points
_serverUrl
Notable features>

Notable features #

// GameManager
// Token: 0x0600000B RID: 11 RVA: 0x0000245C File Offset: 0x0000065C
private void Start()
{
	this.rSeeds.Add(GameManager.HashString(Random.Range(1, 250000).ToString(), ""));
	this.rSeeds.Add(GameManager.HashString(Random.Range(1, 250000).ToString(), ""));
	this.r1 = new RandomNumbers(this.rSeeds[1]);
	this.r2 = new RandomNumbers(this.rSeeds[0]);
	this._serverUrl = PlayerPrefs.GetString("serverUrl");
	Application.targetFrameRate = PlayerPrefs.GetInt("fps");
	this.UseCameraShake = (PlayerPrefs.GetString("graphics") == "high");
	string @string = PlayerPrefs.GetString("audio_music");
	if (@string == "full")
	{
		this.AudioSource.volume = 0.6f;
		return;
	}
	if (@string == "quiet")
	{
		this.AudioSource.volume = 0.2f;
		return;
	}
	this.AudioSource.volume = 0f;
}
// GameManager
// Token: 0x06000013 RID: 19 RVA: 0x0000287F File Offset: 0x00000A7F
private IEnumerator Upload()
{
	string @string = PlayerPrefs.GetString("nick", "");
	string value = PlayerPrefs.GetInt("jersey_number", 1).ToString();
	WWWForm wwwform = new WWWForm();
	wwwform.AddField("points", this.points);
	wwwform.AddField("nick", @string);
	wwwform.AddField("jerseyNumber", value);
	wwwform.AddField("list", string.Join<int>("", this.ballsHit.ToArray()));
	wwwform.AddField("list2", string.Join<int>("", this.ballsMissed.ToArray()));
	string text = this.PrepareSecretCode();
	string text2 = "";
	for (int i = 0; i < text.Length; i++)
	{
		text2 = text2 + text[i].ToString() + this.rSeeds[1][i].ToString() + this.rSeeds[0][i].ToString();
	}
	wwwform.AddField("ss", text2);
	using (UnityWebRequest www = UnityWebRequest.Post(this._serverUrl + "/send_score", wwwform))
	{
		yield return www.SendWebRequest();
		if (www.result != UnityWebRequest.Result.Success)
		{
			Debug.Log(www.error);
			this.UiController.SetConnectionStatus(UiController.ConnectionStatus.Bad);
		}
		else
		{
			Debug.Log("Form upload complete!");
			Debug.Log(www.downloadHandler.text);
			GameManager.JsonResponseFull jsonResponseFull = JsonUtility.FromJson<GameManager.JsonResponseFull>(www.downloadHandler.text);
			if (jsonResponseFull.success)
			{
				this.UiController.SetConnectionStatus(UiController.ConnectionStatus.Good);
				if (jsonResponseFull.flagGranted)
				{
					PlayerPrefs.SetString("flag", jsonResponseFull.flag);
					PlayerPrefs.Save();
					SceneManager.LoadScene("Scenes/Flag");
				}
			}
			else
			{
				this.UiController.SetConnectionStatus(UiController.ConnectionStatus.Bad);
			}
		}
	}
	UnityWebRequest www = null;
	yield break;
	yield break;
}
public void OnBallMissed()
{
	if (this.IsGameOn)
	{
		this.lives--;
	}
	if (this.lives == 0 && this.IsGameOn)
	{
		this.GameOver();
	}
}
Choice of a direction>

Choice of a direction #

Several possibilities are available to us to succeed the challenge (to make more than 2022 points):

More discreet method but sometimes longer to implement:

  • Modify the behavior of the game to facilitate the victory.

Not very discreet but usually quite fast method:

  • Forge a request to the server ourselves.

We choose the first way because in our case, we think it was easier as the request need a list of all balls touched and missed.

Modifying the behavior of the game>

Modifying the behavior of the game #

In our case our final solution was to modify the behavior of the game, the objective was that the balls go constantly straight and towards the racket to not have to move the mouse and just accumulate the score.

The behavior of the balls is handled in the BallsGenerator and BallController classes.

BallsGenerator modification>

BallsGenerator modification #

// BallsGenerator
// Token: 0x06000009 RID: 9 RVA: 0x000023CC File Offset: 0x000005CC
private void GenerateBall()
{
	if (!this.isOpen)
	{
		return;
	}
	int index = 0;
	float num = Random.Range(0f, 1f);
	if (num <= 0.2f)
	{
		index = 1;
	}
	else if (num <= 0.35f)
	{
		index = 2;
	}
	else if (num <= 0.45f)
	{
		index = 3;
	}
	else if (num <= 0.5f)
	{
		index = 4;
	}
	Object.Instantiate<GameObject>(this.ballPrefabs[index], new Vector3(0f, 4.12f, 7.53f), Quaternion.identity);
}

In this function, the game generates a random type of ball, for more stability on our side, we will remove this part and make it always generate the ball of index 4 which gives 5 points.

// BallsGenerator
// Token: 0x06000009 RID: 9 RVA: 0x000023CC File Offset: 0x000005CC
private void GenerateBall()
{
    if (!this.isOpen)
	{
		return;
	}

    Object.Instantiate<GameObject>(this.ballPrefabs[4], new Vector3(0f, 4.12f, 7.53f), Quaternion.identity);
}
Modifying the BallController>

Modifying the BallController #

To make the balls always go straight, we will modify the BallController and change the gravity action on the ball as well as the random direction.

// BallController
// Token: 0x06000003 RID: 3 RVA: 0x0000205C File Offset: 0x0000025C
public void AddForce()
{
	float x = Random.Range(-0.3f, 0.3f);
	float z = -1f * Random.Range(1f, 1.3f);
	base.GetComponent<Rigidbody>().AddForce(new Vector3(x, 0f, z), ForceMode.Impulse);
}

First, remove the x variable that just sets a random direction on the x (horizontal) axis to the ball.

// BallController
// Token: 0x06000003 RID: 3 RVA: 0x0000205C File Offset: 0x0000025C
public void AddForce()
{
	float z = -1f * Random.Range(1f, 1.3f);
	base.GetComponent<Rigidbody>().AddForce(new Vector3(0f, 0f, z), ForceMode.Impulse);
}

Now the ball is constantly moving forward, however a problem arises once several balls are hit. The new balls that spawn hit the table once before they hit the racket, and they end up hitting each other before they hit the racket, which again creates random movements. To fix this we need to make them throw harder so they don’t hit the table anymore.

// BallController
// Token: 0x06000003 RID: 3 RVA: 0x0000205C File Offset: 0x0000025C
public void AddForce()
{
	base.GetComponent<Rigidbody>().AddForce(new Vector3(0f, 0f, -2f), ForceMode.Impulse);
}
Victory>

Victory #

Now all you have to do is keep the racket straight, wait until you pass 2022 points and then voluntarily drop 12 balls to win.



PingCTF 2022 - This article is part of a series.
Part 2: This Article