Team SIK is organizing a CTF based hacking event, to participate in which every hacker needs to reverse engineer four android apps (well, at least one of them) of varying difficulty levels, ranging from trivial to hard. Each app includes a secret password hidden inside, which is to be found in order to complete that level. The apps are available at https://goo.gl/vMj6b6.
This post discusses some of the ways that one could go about reverse engineering these apps in order to find the hidden password. I used dex2jar, apktool and JD-GUI and jdb for decompiling and analysing the apps, and Genymotion as the emulator. But, any other tools for these purposes should work as well.
Solving the trivial challenge is as simple as running the app in Genymotion and entering an incorrect password. Run logcat to watch the logs as you enter a password. The app prints out the password in the logs when verifying the password.
Another way to find the password is to unzip the APK file, use dex2jar to convert classes.dex to jar file, and studying the code for class a. The password can be found hardcoded in the code in base64 encoded form.
This can be decoded on the command line using base64 command line tool.
The second app takes a bit more effort than the first one to reverse engineer. The classes.dex file can be converted to jar file and opened in JD-GUI for easier analysis. The app verifies the password by first running the user input through an encryption algorithm. The algorithm works by first swapping out the two nibbles of every byte and then XORing the result with 0x42. It then matches this encrypted value to an encrypted value that is hardcoded in the app in base64 encoded form (BFF1NwF1YRTl).
Here is the encryption algorithm.
Since, we have the base64 encoded encrypted password, we can write a small script that decrypts the password by first base64 decoding it and running it through a decryption algorithm corresponding to the above alogrithm, i.e. first XORing the bytes with 0x42 and then swapping the nibbles. The python script below does just that.
Run the above script with the base64 encoded encrypted password, and you have the correct password.
The intermediate level tries to conceal its runtime behaviour by using reflection to call methods. Class names and method names are mildly encrypted.
Here is the code used for decryption, before calling methods using reflection.
To decrypt the class names and method names, we can study the above method. However, an easier alternative is to tamper the above method to make it print the decrypted class and method names before it returns them. To do this, we edit the smali code for the Verifier class and insert code to log the decrypted names.
Rebuild and install the modified APK, and watch logcat when as we enter a password.
With the help of above info, the decompiled code becomes a little more easier to understand. The app retrieves the password from the JNI library libpassword.so which exports a method Java_org_teamsik_apps_hackingchallenge_Verifier_getPassword, and then compares it with password submitted by the user. We can try to reverse engineer the above JNI method, but there is an easier alternative available. We can tamper with the code that retrieves the password and make it log it. Here is the method that does this.
In the above code, password is retrieved from the JNI library by the call Object localObject = localMethod1.invoke(null, arrayOfObject);. We will edit the corresponding smali code to make it log the localObject after it makes this call.
Rebuild and install the modified APK, and watch logcat as we enter a password to see the dumped password.
The final challenge takes a number of measures to ensure that the password is never decrypted, and to conceal the runtime behaviour of the app. The password is secured using AES. Most of the method calls are made using reflection, to conceal the behavior of the app. The class names and method names are decrypted at runtime and then called, similar to the way it was done in the intermediate level. The hard level goes a step further by verifying if the app has been tampered with. This makes it difficult to run the modified version of the app, as we did in the previous level. Therefore, the analysis must be done is phases.
In the first phase, we must gain a better insight of the runtime behavior of the app. The class and method names are encrypted using AES, and are decrypted by the following method of d class.
One way to retreive the decrypted class and method names is by tampering the above method, just like we did in the intermediate level. We tamper it to make it log the decrypted values just before it returns them to the caller method. To do this, we decomple the APK using apktool, and modify the smali code of the above method to inject logging code.
This tampering would be detected by the app, but it should still log the decrypted class and method names when we enter a password.
The knowledge gained from the above log, when combined with the decompiled code in JD-GUI, makes it somewhat easier to figure out the runtime behaviour of the app. The app retrieves the public key of the signing certificate at runtime, and uses it as one of the parameters to the encryption algorithm that is used to encrypt the user input using AES. This encrypted value is then compared with a encrypted value hardcoded in the app in base64 encoded form. The encryption used here is a password based encryption, where password is derived from the public key of the signing certificate. The salt and the IV used in the AES encryption are also hardcoded in the app in base64 encoded form. The snippet below shows the code responsible for performing the encryption on the user input.
As tampering with the app would change the signing certificate and the public key, and hence the password for the AES encryption, we must use a runtime attack on the unmodified app to retrieve the password. The app does not use any anti-debugging techniques, which makes it easier to attach a debugger at runtime. So let’s install the original APK and run it. We must enter a password once to make sure all the relevant classes are loaded. Once the app is running and the password is entered, we can attach jdb to the running app.
As seen in the above decompiled code, the password is used as the first argument to PBEKeySpec constructor. We can put a breakpoint on this constructor in order to dump the password.
Now that we have the AES encryption password, we can write a small decryption code that decrypts the hardcoded encrypted password for the app, using this dumped encryption password and the hardcoded salt and IV. Since I feel lazy after all the above hard work, I am just gonna rip off the decompiled code from JD-GUI to do this, with some modifications of course.
Let’s compile and run the above code, and if all is well, it should print out the decrypted password.