Ph0wn2024 Writeup

Race Roller Application reversing

The Ph0wn 2024 CTF was a hacking challenge specialized in smart-devices and low-level softwares.
This event took place on November 30th in the south of France.

It is a very original CTF because we had very original challenges such as hacking EV3 Lego robots or Pico PCB.

In this article we are going to review a Write-Up of an Android Challenge called Race Roller, by taking a look at different ways to solve it.

Discovery

Let’s first launch the app on a device.

Make sure you have an emulator, if not, the easiest way to get one is by installing Android Studio.

Then use adb install raceRoller.apk to install the app on our emulator.
Then we can open our app to understand its features

At first glance, this seems to be a simple app which has only one feature: Generating random colors on 5×4 square of cars.

Recognition

In order to understand how our app works, let’s decompile it in Jadx !

Let’s open our MainActivity class, and precisely the OnCreate() method, which is the first function called when launching our Activity.

As we can see, it is pretty simple to understand its behavior: When clicking on the button, it calls OnCreate$lambda3(), which will be the main method to manage the cars generation.

Let’s dive into it:

To sum up, this function generates 20 random numbers and assign them one by one on a car, with each integer value corresponding to a color.

Win condition

After reading the description of the challenge, it is implied that the win condition is to get all cars green.

Our assumption can be validated easily be looking at the end of OnCreate$lambda3()

 

As we can see, if any car’s color id is different then 5, we break out of the function. If not, it will generate a small popup window using Toast (Pop-up Windows) with the decrypted flag by calling decryptFlag() !

Obviously, the color id 5 should be green following the information given for this challenge. Therefore to get the flag, we must get all cars to color id 5 to win !

By doing simple maths, we can observe that there are 7²⁰ different possibilities and a probability of 1/7²⁰ to get all green cars just by playing it.
We can therefore forget the idea of spamming the button with a script to win.
There are actually many ways to solve this challenge, we are going to show you the ways to retrieve the flag !

Solution 1: Patching the Random Function

The aim is to find the method that creates random numbers and change its return value to five in order to generate only green cars.

The randomRaceValue() is the one called when generating cars. It simply returns an integer between 1 and 7 returned by a call to the Random Class. Then, we just need to change it so that it does a return 5;

But how do we change this function knowing it is already installed and running on our emulator ?

The solution is to create another version similar to the original one but with this small change and install it on our emulator ! This is called binary patching, which is a very common way to solve simple Reverse Engineering problems like CrackMe challenges.

A good thing to know is that on Android, we cannot modify directly the Java code, we must change the decompiled code using an Intermediate Language called Smali.

Here are the steps to patch our APK:

  1. make sure you have the following packages installed: apktool apksigner zipalign keytool
    Most of them are already installed by default with Android Studio, but their path are not in your $PATH. They might be available at $HOME/Android/Sdk/build-tools
  2. Let’s start by decompiling our APK with apktool d raceRoller.apk
  3. Now the function we want to modify is inside the Companion Class.
    The smali files of our custom functions (chall.ph0wn.raceroller) are located in:
    smali/chall/ph0wn/raceroller (one sub-directory per dots!)
    Let’s see what files are available inside this directory.
    Remember that function we want to modify is inside the Companion Class.
    The file corresponding to it is MainActivity$Companion.smali:

 

4. Next step is to locate the Smali code responsible for random generation and modify it by changing the return lines of our method.
To do so, we can open the IDE of our choice ( Vim >>> )and search for randomRaceValue.

This is the original Smali code of our function. Now to understand what changes must be made, it is necessary to make a quick reminder on Smali syntax:

  • .method—>.end method: define the start and end of a function.
  • const/4 <name>, <value>: create a constant
  • invoke-virtual {<args>}: Call a function
  • move-result <variable>: store the returned value of the last function called inside a variable.

Now that we simplified the reading, we can identify that the following lines are useless for us, because we just need to return 5:

 

Let’s just remove these lines and add the following:

Save it and got back to you root directory.

				
					const/4 p0, 0x5 #create a constant that stores 5
return p0 #return the constant
#Those two lines are identical to "return 5" in Java !!
				
			
  1. Build a new APK with the modifications: apktool b raceRoller/
  2. Next step is to sign the APK. First, we need to create a cryptographic key using keytool:

 

				
					keytool -genkey -v -keystore ex.keystore -alias research_key -keyalg RSA -keysize 2048 -validity 10000
				
			

7. Now it’s time for us to sign the APK with apksigner but we need to align the APK to 4 bytes for newer versions using zipalign.

				
					zipalign -v 4 raceRoller/dist/raceRoller.apk ./patched.apk # align to 4 bytes
apksigner sign --ks ./ex.keystore ./patched.apk #sign our new APK 
				
			

8. Finally we just need to uninstall the previous app and install our new one:

				
					adb uninstall chall.ph0wn.raceroller
adb install ./patched.apk
				
			

9. Now launch the app:

As you can see, all the cars are green and thus we get the flag !!

However, the flag is the sentence “Frida is so cool” in l33t speaking, so I guess this was not the intended way but we solved it on live with the patching methodology.

Let’s see how we could use Frida to flag this challenge!!

 

Solution 2: Frida

The goal remains the same with frida: Change the return value of randomRaceValue() to 5 to get all cars to green color and retrieve the flag.

Frida is a dynamic instrumentation framework that allows us to hook functions and modify their usage.

We can then simply hook randomRaceValue() ’s implementation and change its behavior so that it returns 5 instead of a random number. It’s pretty much like Solution 1 but dynamically.

  1. Make sure you have frida installed (pip intall fria-tools).
  2. Then upload the latest version of frida-server on your emulator (make sure you have the same version of frida on both emulator and host machine):

 

				
					#Choose the Frida version to install
FRIDA_VERSION= wget https://github.com/frida/frida/releases/download/16.5.7/frida-server-$FRIDA_VERSION-android-x86_64.xz
xz -d frida-server-$FRIDA_VERSION-android-x86_64.xz
adb push frida-server-$FRIDA_VERSION-android-x86_64  /data/loca/tmp
adb shell "chmod +x /data/local/tmp/frida-server-$FRIDA_VERSION-android-x86_64"
adb shell "/data/local/tmp/frida-server-$FRIDA_VERSION-android-x86_64"
				
			

3. Last thing to do is to create our frida script:

				
					#runs inside a Dalvik VM 
# executes our code inside the same context as the app is run inside
Java.perform(() =&gt; 
{
	#Retrieving Companion Class from MainActivity
  var Companion = Java.use("chall.ph0wn.raceroller.MainActivity$Companion");
  #Getting randomRaceValue()'s implementation to overwrite it
  Companion.randomRaceValue.implementation = function() 
    {
	    #Return 5 to spawn only green cars
      return 5;
    }
});
				
			
  1. Now just launch the script with the following command: frida -U -f chall.ph0wn.raceroller -l solve.js
  2. Click on the button and solve the challenge !

Solution 3: Run Java code on custom app

This last solution is the longest one but it can be very useful in some other cases.

The idea is to reimplement the Companion inside a custom APK.

Why try hard to reverse a cryptographic algorithm when we can just execute the decryptFlag method? 😎

  1. Let’s start by opening Android Studio and create a new Project in Java with No Activity.
  2. Create a MainActivity and copy the Companion class from jadx inside it
  3. Add the following imports

				
					import java.util.ArrayList;

import kotlin.jvm.internal.Intrinsics;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import kotlin.Metadata;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.internal.DefaultConstructorMarker;
import kotlin.jvm.internal.Intrinsics;
import kotlin.random.Random;
import kotlin.text.Charsets;
import kotlin.text.StringsKt;
				
			
  1. Change decryptFlag() and generateKey() to static
  2. Add the encrypted flag:

				
					private final String encryptedFlag = "rRX0o5VF6Rlz6aHlL+qH9jUtobYXmVcVAfq72Z4nOGA=";
				
			

6. Call decryptFlag() inside MainActivity’s OnCreate() and log the result:

				
					String flag = Companion.decryptFlag(encryptedFlag, Companion.generateKey());
Log.d("FLAG", flag);
				
			

7. Run the app and open your Logcat to retrieve the flag!

This solution is interesting because it completely bypasses the goal of the challenge: we do not even need to win the game to get the flag.

We could of course reverse the algorithm and create a decrypt function, but it would not be very useful to describe it in this post. However, the challmaker made a decryptFlag.py script inside its write up. It is available here for those of you who are interested.

In addition, you can check the official write up at this link.

A big thanks to @sehno for creating this challenge and to all the Ph0wn 2024 staff for this amazing edition !!

Patrick Ventuzelo / @Pat_Ventuzelo

Thybalt Marschutz / @thybalt-marschutz

About Us

Founded in 2021 and headquartered in Paris, FuzzingLabs is a cybersecurity startup specializing in vulnerability research, fuzzing, and blockchain security. We combine cutting-edge research with hands-on expertise to secure some of the most critical components in the blockchain ecosystem.

Contact us for an audit or long term partnership!

Get Your Free Security Quote!

Let’s work together to ensure your peace of mind.

Keep in touch with us !

email

contact@fuzzinglabs.com

X (Twitter)

@FuzzingLabs

Github

FuzzingLabs

LinkedIn

FuzzingLabs

email

contact@fuzzinglabs.com

X (Twitter)

@FuzzingLabs

Github

FuzzingLabs

LinkedIn

FuzzingLabs