Native Android Hurts My Eyes

For the following challenge, we are given a .PNG file.

This quickly gives you a hint that we may be dealing with an APK file. After all this challenge is the Mobile track. So, let us go find this APK. I took a bit longer route with this one but I just wanted to be sure. If we load the png file inside Profiler it asks to choose a format.

I chose to parse this file with the PNG format. We look at the format tab and we see the typical PNG chunks: IHDR, IDAT & IEND. However, one chunk stands out:

The ‘paNw’ chunk surely must be special. If we select that chunk, we can see the hex dump of it.

Seems we found what appears to be an APK file. Couple of ways we can go from here. The easiest for me was rename the file with an .zip extension and let 7zip do the work for me. The other way is we can select all the bytes from the profiler window -> right-click -> Copy -> Copy To new file. Personally, I like the second option because it gives me a packaged APK file that I can then load to our analysis tools.

We can start by examining the AndroidManifest.xml file. This one is simple with an interesting discrepancy. If you notice the activity name: com.panw.ctf.EntryActivity. Now if we go and look for this in our smali code we can’t find it. In this case the Android OS will attempt to find the activity and not find it. What it does then, is it will look at the base Application name. Therefore we the start of this application’s code is under: com.panw.ctf.MyApplication

We can begin analyzing some code. The code is pretty simple which raises suspicion. The onCreate() function calls a function and if that returns True it will call killProcess(). However, if its false it loads a native library called ‘loader’.

If we look at the function b.a() we see that is calling other functions inside the same class all returning Booleans. Quick solution is modify the smali code to return False.

Before we do that let’s explore the code a bit more. We see strings in byte arrays:

= generic

= emu_files

= 1555521

Examining the code a bit more we see the ‘emu_files’ is being loaded from the assets folder. In the unzip directory of the apk we can locate this file and open it. Is a list of files all pertaining to emulator files. If you look at the code is looking for these filenames within the device; thus checking for any emulators. The other string ‘generic’ is used to test again the Device name. Lastly the string ‘1555521’ is used to test against the Phone’s line number. We can conclude that the app is checking for emulators or sandboxes. To bypass these checks we are simply going to return false in the initial function. We modify line 307 in the following file: smali/com/panw/ctf/common/b.smali:

from: const/4 v0, 0x1

to: const/4 v0, 0x0

Before we rebuild the APK let’s look at the native library being loaded. Most of the time native libraries will be found under the lib directory. If the author compiles the application with support for multiple architectures, then each will receive its respected directory. Let’s load the armeabi and x86 architecture libraries into IDA. I will be providing both armeabi and x86 function addresses, respectively. Doing some initial analysis, we can quickly gather that the strings are encoded. Let’s decode these so maybe we can get a better understanding of what is going on. We can see that the function sub_13D8 is used to decode these strings:

The function is just decoding the strings by adding four to each character, this is known as a shift cipher. Decoding a few strings we get the following:

/data/data/com.panw.ctf/.hide

/data/data/com.panw.ctf/.hide/.payload

proc/%d/status

TracerPid

getPackageManager

signatures

com/panw/ctf/common/Loader

G0PAl0aLtOPaNw&%

com.panw.ctf.Util

We can draw up several conclusions based on some of these strings. I spent some time decoding more strings and analyzing the code. The string: ‘G0PAl0aLtOPaNw&%’, led me down some rabbit holes but it gave me a bit more understanding of the code. With no major breakthrough, I when back to the main function which is the JNI_Onload. I did notice in the main function the use of getpid() and kill(). Finding the xrefs and usage of these led me to three places:

sub_1368/sub_8B0 – This function is in the .init_array section. At first I didn’t know much about this section so doing some searching in Google we find that the .init_array is an array of function addresses that must be called in-order to perform initialization.

This initialization function calls sub_1380/sub_8F0. Depending on the return value it either calls getpid() & kill() or skips it.

I did spend some time analyzing the sub_1380/sub_8F0, but I simply plan to match the binary.

sub_1B64/sub_1610 – This second function also has the combo of getpid()_ & kill(). Is called from sub_1B54/sub_15D0, which a simple loop of calling sub_1B64/sub_1610 and sleeping for 1 second. Using IDA’s xref we see that sub_1B64/sub_1610 is being used in the JNI_OnLoad funtion. Notably is creating a thread using sub_1B64/sub_1610 as the start address. Again, didn’t spend too much time trying to understand sub_1B64/sub_1610 I simply plan to NOP’d its usage inside thread function sub_1B54/sub_15D0.

JNI_Onload – Lastly the main function also has this combo of getpid() & kill(). Examining the code before it calls it we see a strcmp and a call to sub_1C80/sub_17B0. As with previous functions, we spend minimal time understanding this function and we are just going to patch the code.

Keep in mind I decided to path the x86 library. I chose this because the Android emulator runs faster using the x86 architecture. I chose to simply NOP the calls to getpid() & kill()

As I continued to analyze the code I banged my head a while trying to understand it. With no major breakthrough, I decided to go back to the function that used the ‘G0PAl0aLtOPaNw&%’ string; function sub_168C/sub_DE0. We see three strings being decoded here:

  • com/panw/ctf/common/Loader

  • load

  • Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V

Now we see a call ‘BLX R2’ or in the x86: ‘call dword ptr [eax+18h]’. After some searching and asking friends how can we find out what function is the code calling, they pointed me to this wonderful resource. We know that R0 holds a pointer to the JNIEnv. In the code, we see is dereferencing that pointer at index 0x18. Using the spreadsheet from the link above we can see that 0x18 hold the function address to FindClass.

As we move down the code we see more calls like these, for example at address 0016FC/00EBD we see a call to GetStaticMethodID. At address 00177E/00F78 we a call to CallStaticVoidMethod. If you haven’t already notice these functions are often used in Java for code reflection. If we go back to decompiled APK we see that the application has a class: com/panw/ctf/common/Loader and it happens to have a method called load().

If we look inside class a() we see the use of Cipher and CipherInputStream. In addition to the base64 encoded string: “AES”

We can assume that the application is decrypting something using AES. However, we need find the arguments, and being that is decrypting something I bet the key to decrypt is: G0PAl0aLtOPaNw&%.

If we go back to the JNI_OnLoad function there is function called at address: 0x1516/0x0BC7 (sub_15E0/sub_D00). Exploring this function we see calls to fopen(), strtol(), fputc() along with a string “wb+”. We can conclude that this function is opening a file with the writing/update in binary mode. Which then enters a loop to convert values from string to long and writing them to the file opened.

Is clear that what is being written is within this file. I initially attempted to write a python script to decrypt the contents but it didn’t work. Searching on google for CipherInputStream decrypt, we find sample code we can use. With a bit of modifications to the sample code I wrote a quick decryption tool. I exported the encrypted blob which if you export the .rodata section and just delete the other data it should be easier than finding the right offsets. Remember we need to convert these ascii-hex values and write them to a new file to decrypt. Quick python script to do this, after exporting the data.

f = open('/path/to/encryptedBlob', 'rb')

f1 = f.read()

o = open('/path/to/blob1', 'wb')

blob = bytearray.fromhex(f1)

o.write(blob)

o.close()

f.close()

We then use our java program to decrypt it.

java -jar MyProg.jar “G0PAl0aLtOPaNw&%” ../blob1.

If it all works out, you should get what appears to be another APK.

Digging around the new decrypted APK we see a lot of goodies but none gives us the right flag. At this point we have done all the work statically.

Let’s rebuild the APK with the patches and let’s run the application. If you need help repacking the APK check out the blog post for EzDroid. As I rebuilt the APK I am going to remove all the native libraries except for the x86. Quick note on using apktool to rebuild the application. If you attempt to repacked you get an error:

AndroidManifest.xml:4: error: No resource identifier found for attribute ‘roundIcon’ in package ‘android’

This attribute was introduced in the 8.0, API level 25. I simply deleted: ‘android:roundIcon=”@mipmap/ic_launcher_round”>’ on line

  1. Once we packed it to an APK, we sign it with our key, run zipaling and lastly install it. For this challenge, since we know there is going to be file activity I like to use fsmon. I don’t think it has been updated in a while and I wish the option to back up files worked ;). I startup the application using logcat and fsmon to log events. Since all changes to the app worked, we are presented with:

Let’s try inputting a string:

Ok this is helpful and we can continue to reverse the application. Since we decrypted a second APK, let’s go back to that and analyze its code. We see the following code structure:

If we examine the class ‘EntryActivity’ we see code to retrieve text form a button and doing some checks. We can conclude that the input box we get is being presented by this second decrypted APK. (Note: I have renamed some functions)

The rename function ‘checkPass’ checks for the password to start with ‘PAN’(the string is Base64 Encoded) and the length to be 12.

If that check passes it then calls a native function, isFlagCorrect().

If we go to the class where the function ‘checkPass’ is in, we see a lot of base64 encoded strings. Decoding them reveals interesting hints to complete this challenge.

If you happened to used fsmon you can review the log and see the file names being created. After you analyze the class you can see that the first call to the Class e is to load the resources. Then right before calling the native function isFlagCorrect() it calls another function which loads the native library.

Inside the method that loads the system library, it decrypts the libraries using the same encryption from the previous APK just different password. We can extract the encrypted files: checker & checkerx. Using the java program we modified earlier.

java -jar MyProg.jar “Pal0aLtoN3TwOrks” ../checker

(Note: the file will be saved as ‘newDexFile’, just renamed accordingly)

As we begin to analyze the native library checker we see the same string encoding, we see there is a function in the .init section and we see there is some reflection. Let’s break it down!

Starting with the initialization function: sub_1394/sub_890. This function uses a different string encoding function. The first string gets decoded to: /data/data/com.panw.ctf/.key. The function calls two functions again checking for any virtualization or sandbox. We obviously want these functions to return 0 so that the right value is written to the ‘.key’ file; in this case we want the ‘.key’ file to hold ‘((‘.

For the sake of time and space I will quickly go over the rest of the code as the important part will come later. The main function ‘Java_com_panw_ctf_Util_isFlagCorrect’, first checks for the flag to have certain ranges for each value.

It then takes the inputted flag and encodes it, followed by calculating the sha256 checksum.

Using reflection, it calls the java method Util.getFlashHash(String arg2). What this method does it simply decodes the QR code from the flag.png which is: “3;:j>99j2??38=9n3o:i?:;9»«9<28i»j>;hoi<>;om>=2njiiojm9hi:;23=”

The code has one last sandbox detection before it retrieves the value saved to the ‘.key’ file from the initialization function. Using that value it XORs the value from the QR code which gives us what appears to be sha256 value. To sum it all up we need our inputted flag to match the decoded sha256 value. Using the knowledge, we gathered from the function that checks the inputted flag we can build a script to brute force this. Don’t forget the inputted string flag is encoded and then the sha56 sum is calculated. Below is a sample python script, I am sure there is improvements for speed on this and looking forward to seeing everyone’s else solution.

#!/usr/bin/env python

import sys

import hashlib

import itertools

def encodePass(mypass):

    t = ""

    for p in mypass:

        t = t + chr((ord(p) - 4) ^ 23)

    return t

def bruteforce(correctHash):

    prefix = 'PAN{'

    postfix = '}'

    count = 0

    for upper1, upper2, digit1, digit2, lower1, lower2,         special in
        itertools.product(

        range(65, 91),

        range(65, 91),

        range(48, 58),

        range(48, 58),

        range(97, 123),

        range(97, 123),

        range(32, 48)):

        count += 1

        passwd = prefix + chr(upper1) + chr(digit1) \\

            + chr(digit2) + chr(lower1) \\

            + chr(upper2) + chr(lower2) \\

            + chr(special) + postfix

        encodeP = encodePass(passwd)

        hashEncodeP = hashlib.sha256(encodeP).hexdigest()

        if hashEncodeP == correctHash:

        return encodeP, passwd

        return 0, 0

def main():

    encodeP, passwd = bruteforce(

    '801a522a9448362e8d1b410255772793b55a50cdb750df569eabbdaf2cb10986')

    if encodeP:

        print encodeP

        print passwd

        return 1

    else:

        print "Not Found"

        return 0

if __name__ == '__main__':

    sys.exit(main())

Using the script above and patiently waiting about 10minutes you should get the flag: PAN{M03iLe&}