This post will be on a CTF challenge I have completed in February but I did not have the time to write about it until now. My friend and I took part in the CTF but the other challenges we solved were website site related and we could not access them anymore. Hence, I will only be writing the write-up I still have access to. Without further ado, let’s get into it.
The Login challenge is an Android application reverse engineering challenge. Therefore, you are required to have some knowledge of the following items before you can understand or knows how to solve the challenge:
- Android Application Package (APK) internal files
- Signing process
- Android development
If you are new to Android reverse engineering, you can read this post here.
Before we begin, we will need the following tools:
Set up the emulator and run the app
Setting up the emulator is important as we would like to see what is in the application when we execute it. For those using AVD from Android Studio, you can launch it from Android Studio or directly from the terminal (See Fig. 1a and 1b). Note that the Login challenge app requires API 30 hence make sure you have the correct emulator launched.
> emulator -list-avds Pixel_2_API_24 Pixel_2_XL_API_16 Pixel_2_XL_API_19 Pixel_4_XL_API_28 Pixel_4_XL_API_30 > emulator -avd Pixel_4_XL_API_30
Fig 1b. Commands to set up the emulator
After setting up the emulator, we would like to install the app and run it to see how is the app’s interface. This can be done using Android Debug Bridge (adb). Adb is readily available if you have installed Android Studio on your computer. Make sure the path to it is set in your environmental variable to AppData\Local\Android\Sdk\platform-tools. Next, you can install it using the following command:
> adb install login.apk
After installing, you can launch it which will show you just like the image on Fig. 1c which is a login page.
This shows us that the app consists of a login page. The first thing that came to my mind was to find the login credentials as the flag might be produced after we managed to login. The string comparison should either be in the same activity as the Login activity or in a normal Java class instantiated by the Login activity if the developer of this app follows software engineering architecture as such MVC or MVP.
Search for Login activity
Since we know that the first page is a login activity, we can now disassemble the Login APK file to obtain all the files in it using APKTool. The command to disassemble the APK can be seen below.
> apktool d login.apk
A folder called “login” will be created. Inside the folder, open the file AndroidManifest.xml. Search for the launcher class which contains the intent filters “android.intent.action.MAIN” and “android.intent.category.LAUNCHER“. The launcher class can be seen in the red box while the intent filters we were looking for are in the blue box in Fig 2.
We can then search for the launcher class in the smali folder which should be in the following path:
Search for login credential checks
As we open up LoginActivity.smali, true enough, we can see that it uses a software engineering design pattern, MVVM, as there is a ViewModel that manages the logic of the User Interface (UI). This can been seen in Fig 3a below.
Therefore, when the login button is clicked on the UI of the app, there should be a method in the class that handles the logic to check the credentials. True enough, I was able to find the login() in LoginViewModel class. It can be seen taking two inputs which are both strings. Those two inputs are probably the strings of username and password. Inside that method, we can see the username and password are further passed into another login() in LoginRepository class in Fig 3b.
As we look into login() in LoginRepository, we can see that it calls login() again in another class, LoginDataSource, see Fig 3c.
Inside login() in LoginDataSource class, it can be seen that the username and password are being compared as seen in Fig 3d. The red box in Fig 3d shows that the username was previous some number in string and undergoes hex-to-string changes before being compared to user-inputted username and the TRUE or FALSE result is returned to register p1. If the result is TRUE, the jump should be taken to go to cond_0 section as TRUE is equaled to value 1 which is non-zero. Else, a string that notifies the user of an incorrect username will be printed on Log.d() as shown in the blue box in Fig 3d.
Let’s say the branch was taken as the username is true, we will arrive at cond_0 section. In this section, we can see that the first 4 characters of the password were obtained via substring() in the green box in Fig 3e. The red box then obtains part of the actual password and then compares it with those first 4 characters of the user-inputted password as shown in the red box of Fig 3e. Similarly, the result of equals() of string comparison is placed in register v2 before deciding if the branch is to be taken. If the first 4 characters are incorrect, it will go to the blue box, else it will go to section cond_1.
In section cond_1, the remaining password is being compared with the other stored password. This can be seen in the red box in Fig 3f. If the remaining password is incorrect, it will be printed via Log.d() shown in the blue box in Fig 3f.
If we look at the remaining login() which is at section cond_2, we will be able to see that the flag will be printed via logcat. This can be seen via the blue box and red box in Fig 3g.
Preparation to obtain the password
The username does not require us to prepare anything as we know that the username is in hex, “5573657231333337”. This can be seen in Fig 3d. Using some hex to string convert from any website, we will be able to obtain the username, “User1337”.
As we know that the password is being split into two comparisons, we can use logcat to obtain both of the passwords by making medications to the smali code. To obtain the first 4 characters of the password and the remaining password, the newly added smali code by me can be seen on Fig 3h and 3i respectively.
Compile the modified APK
The modified APK can be compiled using the following command in the terminal:
> apktool b login -o result.apk
An APK called result.apk will be created. After creating the file, we must sign the APK again as the certification information stored in the INF-META file no longer match the current modified APK. This can be done using Jarsigner. Before we sign the modified APK, we must create a keystore first. This can be done using keytool. Keytool can be found in your Java JDK folder in the bin folder. You can create a key using the following command:
> keytool -genkey -noprompt -alias alias_name -keyalg RSA -keysize 2048 -validity 10000 -keystore my-release-key.keystore -dname \"CN=test, OU=test, O=test, L=test,S=test, C=SG\" -storepass testing
After generating a keystore, we can then sign it using Jarsigner. This can done using the following command:
> jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore -storepass testing result.apk alias_name
After signing using version 1, we have to sign using version 2 as Login APK requires it. This applies to those apps that have higher API levels. This can be done easily by following the steps in this video using the tool in the video’s description.
Obtain the password
After signing the modified APK using APKsigner v2, we can now install the modified APK using adb. Remember to uninstall the previous Login app from your emulator first before installing the modified APK. After successful installation, we can now launch logcat using adb. Remember that the tag name for Log.d() is “ctflevel1”? This can be seen in the constructor method in LoginDataSource class as shown in Fig 4a.
Since we know what is the tag name will be used when we print out the store passwords and the flag, we can use the following command:
> adb logcat -s "ctflevel1"
This will cause adb to listen for logcat and filter for us only Log.d() message with tag name that is “ctflevel1”.
Once adb is listening to the logcat command, we can open up the modified Login app. Input the username, User1337, and any characters for the password but must be more than a length of 4. Click on the “SIGN IN” button. Results of the first 4 characters of the password should be printed out. We will be able to see the that first 4 characters should be “L1v3”.
Once again. input the password starting with “L1v3” while with random characters at the end. The remaining password will be printed on logcat as shown in Fig 4b.
You will notice that the flag has been printed out. However, the tricky part of this CTF is it will check if the app has been modified and printed out an incorrect flag. Therefore, reinstall the original Login APK and you will obtain the right flag. You do not need to off your adb that is listening to logcat while reinstalling the original Login APK.
After reinstalling and input the correct login details:
You will be able to obtain the flag: 71BCADE1B51D529AD5C9D23657662901A4BE6EB7296C76FECEE1E892A2D8AF3E
I hope this post has been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂