Here is my write up for OWASP MSTG UnCrackable App – Android Level 1. The challenges are located here, and the objective for Android Level 1 is:
A secret string is hidden somewhere in this app. Find a way to extract it.
You can download the APK from here, start up an emulator, and install it using ADB:
emulator -avd KitKat -writable-system -tcpdump KitKat_traffic.pcap adb install UnCrackable-Level1.apk
If you don’t have emulator, adb, or Android Studio, check out my post that covers setting things up.
After opening the installed app, I quickly learned that it checks to see if the device is rooted, and exits if it is:
I close that emulator, switch to a non-rooted emulated device and install it there:
emulator -avd Nougat -tcpdump Nougat_traffic.pcap adb install UnCrackable-Level1.apk
The app asks me to enter a secret string. I went ahead and tried letmein. It didn’t work:
Doesn’t look like the app does much. I closed the emulator and decided to check the pcap file to see if there was anything of interest happening on the network while I was interacting with the app. There was not. Now, on to static analysis.
The APK is an archive that contains everything required to run the app. If you unzip an APK, you will have the following directory/file structure:
The OWASP MSTG describes the resources:
- xml: contains the definition of the app’s package name, target and min API version, app configuration, components, user-granted permissions, etc.
- META-INF: contains the app’s metadata
- MF: stores hashes of the app resources
- RSA: the app’s certificate(s)
- SF: list of resources and the SHA-1 digest of the corresponding lines in the MANIFEST.MF file
- dex: classes compiled in the DEX file format, the Dalvik virtual machine/Android Runtime can process. DEX is Java bytecode for the Dalvik Virtual Machine. It is optimized for small devices
- lib: directory containing 3rd party libraries that are part of the APK.
- res: directory containing resources that haven’t been compiled into resources.arsc
- arsc: file containing precompiled resources, such as XML files for the layout
Unfortunately, if you try to unzip the files and examine them, you’ll find some of them unreadable, as they are binary encoded:
To extract and decode the files, one option is to use apktool:
apktool d UnCrackable-Level1.apk -o apktool_out
The resources will be extracted to apktool_out and will now be readable:
From here we can review the various files to find information about the app as mentioned in the earlier descriptions of the files.
People have created various tools to review the files and get the information, one of which is Androwarn.
Androwarn is a static code analyzer for primarily meant for checking if an app is malicious, but it also can be useful to get a high-level look at the app. Androwarn is written in Python and can be installed with “pip install androwarn”.
I use it to generate an HTML report:
androwarn -i UnCrackable-Level1.apk -v 3 -r html
I like Androwarn because it’s easy. There isn’t a lot to this app, so the most interesting thing is probably the Classes List section of the report, which shows the classes used by the app:
Quickly reviewing some of the classes, and knowing that the objective involves finding a secret string, I take note of the use of base64 and the crypto packages.
Moving on from that, I like to run strings on the classes.dex file. From the strings output, a few catch my eye:
,5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc= 8d127684cbc37c17616d806cf50473cc AES error: AES/ECB/PKCS7Padding Nope... Root detected! Success! That's not it. Try again. This is the correct secret. 3This is unacceptable. The app is now going to exit.
These strings may give you an educated guess of what the program does. From running the app, we’ve seen the strings “Root detected!” along with “This is unacceptable. The app is now going to exit.”, and “Nope” with “That’s not it. Try again.”. It probably safe to assume that “Success” will accompany “This is the correct secret.”. You could also hypothesize that the application uses AES ECB encryption with PKCS7 padding. You can even go as far as to do some brief analysis of the strings, the first of which is likely base64 encoded and the second looks like hex. Here is what I did in Python:
>>> import base64 >>> s1 = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=" >>> s2 = "8d127684cbc37c17616d806cf50473cc" >>> dec1 = base64.b64decode(s1) >>> dec1 b'\xe5Bb\x15\xcb[\x9a\x06\xc3\xa0\xb5\xe6\xa4\xbdv\x9aI\xe8\xf0t\xf8.\xff\x1d\x95\xab|\x17\x14v\x18\xe7' >>> len(dec1) 32 >>> dec2 = bytes.fromhex(s2) >>> dec2 b'\x8d\x12v\x84\xcb\xc3|\x17am\x80l\xf5\x04s\xcc' >>> len(dec2) 16
Neither of these decoded to any meaningful values, but the lengths of the decoded values are clues. To validate the hypothesis I decompiled the classes.dex to get an idea of the source.
I created a new directory and use jadx to decompile the app:
mkdir UnCrackable-Level1 jadx -d UnCrackable-Level1 UnCrackable-Level1.apk
Jadx also has a GUI tool which is nice, but using a full blown IDE like Android Studio allows me to easily search for references, as well as rename and refactor in the case of obfuscated code.
I opened decompiled project in Android Studio (File > New > Import Project.. > Go to the UnCrackable-Level1 folder containing the decompiled code > Click Ok) to view the reconstructed source and started with reviewing the Main activity:
At the bottom there is a method that performs an evaluation on an object and then sets a string depending on whether the object. Here is the evaluation:
You can see that the code that evaluates the object is in a.a, which we will want to look at. In Android Studio you can go directly to the code by selecting the a.a with the cursor, right-click, and select Go To > Declaration (or highlight and Ctrl + b):
This method has the hex and base64 encoded strings we saw in the strings command output. It takes a string (str) as an argument, sets another string variable (str2) is set to a static value, which is converted to bytes (via the b() method) and passed to sg.vantagepoint.a.a.a along with a base64 encoded value, which is captured in the bArr variable. The str argument is compared with the string within the bArr using str.equals, returning true if it matches or false if it doesn’t. Highlight the sg.vantagepint.a.a.a and press Ctrl + b to view what that code is doing:
This method shows that two byte are the input, bArr and bArr2. The first, bArr, is used as the key in new SecretKeySpec(bArr, “AES/ECB/PKCS7Padding”); which also tells us that AES ECB is the cipher mode in use. The cipher gets created using the key, and a byte is returned using the cipher to decrypt bArr2: return instance.doFinal(bArr2).
So, this brings us back to the Main Activity:
If the input someone types into the app matches the decrypted value, then a.a(obj) will be True, and the secret is solved. If the input doesn’t match the decrypted value, then the other message is displayed. Since the value and the key are both there, we can just go ahead and decrypt the value. Here’s what this looks like in Python:
>>> import base64 >>> secret = base64.b64decode(“5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=”) >>> key = bytes.fromhex(“8d127684cbc37c17616d806cf50473cc”) >>> from Crypto.Cipher import AES >>> cipher = AES.new(key, AES.MODE_ECB) >>> plaintext = cipher.decrypt(secret) >>> plaintext b'I want to believe\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
Without the padding, the secret decrypts to “I want to believe”. Let’s see if that works:
And sure enough, that’s the secret, so objective complete! In this post I’ll walk through bypassing the rooted device protection mechanism.