CyCTF 2024 Finals - Android Challenges
Introduction
CyCTF is maintained by the CyShield and the 2024 edition happened on 20/11/2024.
As usual, this was a great CTF with some really nice Android challenges. I’ve managed to solve 2 Android challenges Armor
and Andoroido
, and I thought since they were such fun challenges they deserved a writeup.
Armor
Starting by taking a quick look at the app on my emulator, It asks for a Pin Code
, and If provided incorrectly, the app will crash.
The next step is to open the APK in a decompiler and start to analyze how the application works. It seems that the Pin Code
is being passed to the function named CP
on line 27
, this function is defined inside the native library, and despite the name of the library is encrypted you can notice that there is only one native library shipped with the APK (libarmor.so
) so no need to guess which one is it here.
To analyze the code of the CP
function, I dropped the library in IDA and after looking at the code, it appears to be pretty straightforward. The code copies the provided Pin Code
to the local variable v4
on line 9, it then copies it to v7
on line 12, and finally it is passed to sub_6A7EC
on line 13 where it basically compares it with the string 534925
.
Finally, trying this Pin Code
in the application will reveal a Toast message which eventually turns out to be the encrypted flag in base64 encoded format.
After getting the flag, I started digging deeper into the code and noticed that inside the Prompt
class lies the code for the toast message that printed the encrypted flag. After that, the application uses the DexClassLoader class to load a certain class and invoke a function from within that class. You can also notice on line 64
that the location from which it’ll load the code is inside the app’s cache directory. Furthermore, it prints something in the logs so it is worth checking that out as well.
The logs of the application point to a dex file inside the cache directory which is consistent with what is on line 64
.
I grabbed a copy of that dex file and noticed that it is a class named Utils which is used for encryption and decryption as well. You can notice that it uses AES with CBC mode. It uses a IV shown on line 55
and the key is defined with a null
value at the very beginning of the class. Finally, notice how all the class code is readable except for the code of the constructor.
To understand why jadx fails to decompile the constructor you’ll have to look at the small code. The code starts by checking the length of the parameter sent to the constructor and makes sure it is 32 bytes in length. The issue starts from line 52
where you can see an unconditional jump to label goto_e3
.
If you scroll down to goto_e3
, you can see the pattern from line 313 to 317 is basically an infinite loop that hindered jadx from decompiling the constructor properly.
I removed the jump on line 52
and removed the infinite loop pattern as well and rebuilt the dex file again and this time you can see the code and the bytes of the encryption key.
Finally, after compiling all the parts of the key, you can decrypt the flag with a simple CyberChef formula.
Andoroido
Andoroido is my personal favorite in all of the challenges since it is about Android Internals which is a topic I’ve been very interested in lately.
After inspecting the application in jadx, it is very clear that the main activity (which is exported by default) takes an extra called url
and if the url variable is set, the app calls a function named downloadDex
.
The downloadDex
function simply downloads whatever the url is pointing to and saves it in the applications Files
directory under the name payload.dex
.
Next, downloadDex
calls a new function named loadDex
which again uses the DexClassLoader class to invoke a function named execute
inside a class named com.clone.payload.Main
. Finally, loadDex
checks the package name and if it is equal to l337_P0wer
It’ll print the flag.
This last part is basically the entire challenge because the getPackageName
function will always return com.cyctf.andoroido
.
To move the easier stuff out of the way, below is the code needed to communicate with Andoroido
and sends it the url which points to the malicious dex file. Just a pretty straightforward Intent with an Extra, nothing fancy.
The final part is about modifying the package name and for that, let me quickly summarize a few key pieces of information needed to perform this trick.
ActivityThread
: It is a critical Android internal class that manages the main thread of an application.-
mBoundApplication
: It is a field that contains anAppBindData
instance, which holds information about the current app. LoadedApk
: This class contains themPackageName
field. Modifying this field will change the package name and ensure changes will propagate to runtime checks that reference it. for example checks like thegetPackageName()
function used in the challenge.
So, to put it all together, I simply need to modify the value of mBoundApplication.info.mPackageName
. Since this code will be loaded from a Dex
file using the keyword this
turned out to be risky. this
in this context refers to the class in which execute
is defined, not the actual Application instance or its related class loader. To avoid this, you can see on lines 8 and 9 I’m using The ActivityThread
singleton to retrieve the currentActivityThread()
method. This is essential for modifying the internal app’s data.
Before compiling this application, one final modification needs to be made. I needed to set the multiDexEnabled
field to false
in the gradle configurations to avoid dividing the code into multiple dex files.
Finally, compile the code, extract the Dex file, host it on your server, and open the exploit application from earlier and you’ll be treated with the flag.