HacktivityCon CTF Mobile Writeup
03 August 2020
By Jacob Pimental
Last week was HacktivityCon, running from Wednesday to Friday. While I was not able to find the time to watch the talks, I did manage to participate in the CTF and complete most of the mobile Reverse Engineering challenges.
MobileOne
The first challenge was fairly simple, a basic application that displayed an image of MobilOne motor oil.
My first instinct was to unpack the apk file given to us using apktool. You can do this with the command:
apktool d mobile_one.apk
After that, I spent some time looking through the source code in JADX-GUI to find a hardcoded flag or something that would point me in the right direction. Nope, all the application did was create an activity that would display the image of motor oil. Out of desperation, I went to the resource section and grep’d for the string “flag” and got the answer:
A lot easier than I originally thought. On to the next one.
Pinnochio
The second challenge was a bit more interesting. We are presented with a form that asks us to input a pin. If we put in a random number we will get a screen that says “Invalid pin”. Grepping for the pin in the resources doesn’t work this time either, so we will have to look at the source code for this one.
After unpacking the apk with apktool and reading the AndroidManifest.xml file, we know that the main activity of the application is at com.congon4tor.pinocchio.MainActivity
, which sets an onClickListener that will transfer our pin to the FlagActivity when the submit button is pressed. The FlagActivity appears to be creating a hashmap with our pin and sending it as a JSON string over to http://jh2i.com:50029
. I decided to verify this using mitmproxy as well.
The response for this was “Invalid pin”
Since the pin is only 4 characters long we could easily brute force that with a python script. I used multithreading to speed up the process because I am impatient.
import requests
import json
from concurrent.futures import ThreadPoolExecutor
url = 'http://jh2i.com:50029/'
d = {}
headers = {'User-Agent':'Dalvik/2.1.0 (Linux; U; Android 9; Android SDK '\
'built for x86 Build/PSR1.180720.012)',
'Content-Type':'application/json'}
def send_request(i):
data = {'pin': f'{i:04}'}
r = requests.post(url, data=json.dumps(data), headers=headers)
if not r.text == 'Invalid pin':
print(i)
print(r.text)
pins = [i for i in range(9999)]
with ThreadPoolExecutor() as executor:
r = executor.map(send_request, pins)
After a few minutes, we got the pin and our flag.
Just Not Interesting
The last challenge that I will go over was a lot more interesting than the title suggests. The application asks for a username and password and will print Invalid Credentials when you input the incorrect combo.
Going to the MainActivity in the source code, which you can find the same way we did for Pinnochio, the first thing that stuck out to me were the two native functions that were defined, checkUsername
and checkPassword
. The “native” keyword tells us that these functions are not going to be written in Java, but are instead using a native library written in ARM, x86, or another architecture. At the bottom of the MainActivty class, we can see the call to System.LoadLibrary
which will load the native library “native-lib”.
The native library code for this is located at lib/
After loading our shared library into Cutter, we can see the functions sym.Java_com_example_justnotinteresting_MainActivity_checkPassword
and sym.Java_com_example_justnotinteresting_MainActivity_checkUsername
which defines the native functions we saw in the apk. Starting with the checkUsername function you can see it makes a call it 0x6bf, pops the return address into ebx, and adds it by 0x1911. This is used as a base to reference other variables. For example, we can see further in the code that we subtract that base by 0x17a8 and use that value as a parameter to strcmp. If we look at the value of [0x6bf + 0x1911 – 0x17a8] (0x828) we can see the string “admin”, which must be the username for the application. Another interesting aspect of Native functions is their use of JNIEnv, which is an array of pointers to JNI functions. It is normally the first argument for a native function and is used to call other native functions. In the checkUsername function we see a call to [ecx + 0x2a4], ecx being the location of the JNIEnv. There is a handy chart that will tell you what each offset points to, this specific one is GetStringUTFChars. So this is converting our input into an array of UTF-encoded bytes.
Looking at the checkPassword function we can see it calculates the value to that base offset again by calling 0x711 and adding 0x18bf to it. The function then gets the string “NOTFLAG(the_fLag_ISN’T_here!!!!)” by subtracting the base by 0x17a2. It then grabs another variable at the offset 0x1781, which appears to be an array of bytes, and XORs our input against that. It then compares to see if the XOR’d data is equal to “NOTFLAG(the_fLag_ISN’T_here!!!!)” using strncmp and will return if they are equal. It is easy enough to find the correct input using another simple Python script:
key = [40,35,53,33,55,44,38,
81,22,13,58,62,57,32,
8,19,43,37,54,17,78,
58,43,13,23,23,22,85,
72,79,70,84] # Byte array at offset 0x1781
check = "NOTFLAG(the_fLag_ISN'T_here!!!!)"
for c, k in zip(check, key):
print(chr(ord(c) ^ k), end='')
This will print out the flag “flag{maybe_a_little_interesting}”.
Conclusion
I did not have enough time to look at the final mobile challenge that was released on the second day of the CTF. These challenges were good practice in mobile reverse engineering and I learned some things from them. A resource I found useful when going through these challenges was Maddie Stone’s Android App Reverse Engineering 101 course, specifically the chapter on native libraries and JNI functions. I recommend checking out that resource if you haven’t already. If you have any questions on how I solved the challenges or would like to share how you solved them, feel free to reach out to me on my Twitter or LinkedIn.
Thanks for reading and happy reversing!