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.
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.
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.
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 !
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:
apktool apksigner zipalign keytool
$HOME/Android/Sdk/build-tools
apktool d raceRoller.apk
chall.ph0wn.raceroller
) are located in:smali/chall/ph0wn/raceroller
(one sub-directory per dots!)
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:
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 !!
apktool b raceRoller/
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!!
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.
#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(() =>
{
#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;
}
});
frida -U -f chall.ph0wn.raceroller -l solve.js
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? 😎
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;
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
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!
Cookie | Duration | Description |
---|---|---|
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |