ØxOPOSɆC Baby Xmas Challenge 2019 | Android Rev
The APK for the original challenge can be downloaded here.
The author's write-up can be downloaded here.
We're given an Android Package (xmas_ctfzadas.apk
) along with the following instructions:
Baby pwn tu-tu-tu-tu!
- Get the first two flags ({oposec}XPTO) locally;
- For the brave ones, find a way to get the third flag
- Yup "This_is_the_flag" is your friend but not the final solution.
Unzipping the APK
An APK is actually just a ZIP file, by unzipping it we get to see the compressed files:
~ unzip app.apk
~ ls -la
...
-rw-r--r--@ 1 inesmartins staff 2444 Nov 22 2019 AndroidManifest.xml
drwxr-xr-x@ 5 inesmartins staff 160 Jun 13 20:59 META-INF
-rw-r--r--@ 1 inesmartins staff 178784 Nov 22 2019 classes.dex
-rw-r--r--@ 1 inesmartins staff 2062612 Nov 22 2019 classes2.dex
drwxr-xr-x@ 3 inesmartins staff 96 Jun 13 20:59 lib
drwxr-xr-x@ 16 inesmartins staff 512 Jun 13 20:59 res
-rw-r--r--@ 1 inesmartins staff 225072 Nov 22 2019 resources.arsc
So, as expected we see AndroidManifest.xml
, some .dex
files (the source code), the resources, and also one Java Native Interface (JNI) library that contains some interesting strings:
~ strings lib/armeabi-v7a/libopoctf-jni.so
...
Java_pt_oposec_ctfzadas_CTFObject_firstFlag
Java_pt_oposec_ctfzadas_CTFObject_secondFlag
Java_pt_oposec_ctfzadas_CTFObject_thirdFlag
Java_pt_oposec_ctfzadas_MainActivity_stringFromJNI
envzadas
jump_here
jump_not_here
...
tres_out
...
This_is_not_the_flag
This_is_the_flag
...
Static analysis with Jadx
To be able to use jadx
we need to transform the .dex
files into a single .jar
file, which we can achieve using the dex2jar script:
~ sh dex2jar-2.0/d2j-dex2jar.sh -f xmas_ctfzadas.apk
The pt.oposec.ctfzadas
package contains 4 classes:
BuildConfig.class
andR.class
are automatically generated files: the first lists the project configs and the second identifies all the project assets;- this leaves us with only 2 interesting files:
MainActivity.class
andCTFObject.class
.
MainActivity.class
As we can see below, on app start the MainActivity loads the opoctf-jni
library, then creates a Webview that has access to an instance of the CTFObject
class called ctfObj
. The webview's URL depends on the Intent's open_sesame
query string.
CTFObject.class
This class is quite interesting:
- There is a function called
um
that returns the output of thefirstFlag
JNI function:
public class CTFObject {
...
public native String firstFlag();
@JavascriptInterface
public String um() {
return firstFlag();
}
...
}
2. Similarly, there's a function called dois
that calls the secondFlag
JNI function with a string param. Note that there's a 5-number PIN mentioned on this function.
public class CTFObject {
...
public native String secondFlag(String paramString);
@JavascriptInterface
public String dois(String paramString) {
Log.d("OPOSEC", "Pin have 5 numbers (String)\nString starts with \"{oposec}\"");
Log.d(secondFlag(paramString), secondFlag(paramString));
return secondFlag(paramString);
}
...
}
3. Finally, there's a tres
function that calls the thirdFlag
function from the JNI library with a byte array that's parsed from a hex string:
public class CTFObject {
...
public static byte[] hexStringToByteArray(String paramString) {
int j = paramString.length();
byte[] arrayOfByte = new byte[j / 2 + 1];
for (int i = 0; i < j; i += 2)
arrayOfByte[i / 2] = (byte)((Character.digit(paramString.charAt(i), 16) << 4) + Character.digit(paramString.charAt(i + 1), 16));
arrayOfByte[arrayOfByte.length - 1] = Integer.valueOf(0).byteValue();
return arrayOfByte;
}
public native String thirdFlag(byte[] paramArrayOfbyte);
@JavascriptInterface
public String tres(String paramString) {
Log.d("aaa-", paramString);
byte[] arrayOfByte = hexStringToByteArray(paramString);
Log.d("aaa:", String.valueOf(arrayOfByte));
return thirdFlag(arrayOfByte);
}
...
}
Note that functions um
, dois
and tres
are accessible on the MainActivity's webview since they're marked with the JavascriptInterface notation.
Re-Creating the Android project
After installing Android Studio we create a new Android project and then:
- add the
CTFObject
class tosrc/main/java/pt.oposec.ctfzadas
(I ended up changing the Java code to Kotlin, since I'm more familiar with it); - add the
libopoctf-jni.so
library under/src/main/jniLibs/armeabi-v7a
(this path is extremely important).
Getting the first flag
The first flag is very simple, all we need is an instance of CTFObject
and then we can simply print the result of calling either um
or the firstFlag
function directly, as shown below.
package pt.oposec.ctfzadas
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private var ctfObject = CTFObject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
print(ctfObject.firstFlag())
}
}
Getting the second flag
From the CTFObject
class we know we need to find a 5-digit PIN code, which we can easily brute force. Knowing that the flag has the word oposec
we simply run through all the options until we get the one that prints out the flag:
package pt.oposec.ctfzadas
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private var ctfObject = CTFObject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
for (i in 0..9) {
for (j in 0..9) {
for (k in 0..9) {
for (l in 0..9) {
for (m in 0..9) {
val pin = "$i$j$k$l$m"
val res = ctfObject.secondFlag(pin)
if (res.contains("oposec")) {
print("Pin is: " + pin)
print("Flag is: " + res)
}
}
}
}
}
}
print("Could not get flag")
}
```