sp1d3r

Slaydroid

Another hard, RC4, Rev, Android Challenge #

Although the CTF had more android challenges, I chose to write a writeup for this challenge.

Originally, this was a “hard” challenge which I managed to solve in less than 20 minutes, thanks to my instrumentation knowledge (and frida too).

Description #

The CTF had an android challenge already with pure Java code, and this time, it had some machine code/libraries.

You would want to look at the below functions:

public native void damn(String str);
public native void k2(String str);
public native void kim(String str);
public native void nim(String str);

// LoadLibrary
static { System.loadLibrary("sl4ydroid"); }

The above functions are meant to generate parts of the flag and the inclusion of a native library is meant to make the challenge harder.

Anyway, let’s decompile the above functions. Don’t choose to decompile the x86 libraries, they are a trap! Use the ARM libraries to understand and apply. So, I used Ghidra for the job.

Looking at the disassembly of each function, we get:

Damn
void Java_com_backdoor_sl4ydroid_MainActivity_damn
               (_JNIEnv *param_1,_jobject *param_2,_jstring *param_3)

{
    byte bVar1;
    _jclass *p_Var2;
    char *pcVar3;
    // ...
    p_Var10 = (_jobject *)_JNIEnv::NewStringUTF(param_1,pcVar5);
    _JNIEnv::CallVoidMethod((_jobject *)param_1,(_jmethodID *)param_2,uVar4,p_Var7);
    _JNIEnv::DeleteLocalRef(param_1,p_Var7);
    std::__ndk1::basic_string<>::~basic_string(abStack_60);
    std::__ndk1::basic_string<>::~basic_string(abStack_48);
    std::__ndk1::basic_string<>::~basic_string(abStack_30);
    lVar6 = tpidr_el0;
    if (*(long *)(lVar6 + 0x28) == local_18) {
        return;
    }
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
}
K2
void Java_com_backdoor_sl4ydroid_MainActivity_k2
               (_JNIEnv *param_1,_jobject *param_2,_jstring *param_3)

{
    byte bVar1;
    byte bVar2;
    ulong uVar3;
    _jclass *p_Var4;
    char *pcVar5;
    // ...
    pcVar5 = (char *)FUN_00120e00(abStack_48);
    p_Var10 = (_jobject *)_JNIEnv::NewStringUTF(param_1,pcVar5);
    _JNIEnv::CallVoidMethod((_jobject *)param_1,(_jmethodID *)param_2,uVar6,p_Var10);
    _JNIEnv::DeleteLocalRef(param_1,p_Var10);
    std::__ndk1::basic_string<>::~basic_string(abStack_48);
    std::__ndk1::basic_string<>::~basic_string(abStack_30);
    lVar7 = tpidr_el0;
    if (*(long *)(lVar7 + 0x28) == local_18) {
        return;
    }
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
}
Kim
void Java_com_backdoor_sl4ydroid_MainActivity_kim
               (_JNIEnv *param_1,_jobject *param_2,_jstring *param_3)

{
    long lVar1;
    _jclass *p_Var2;
    char *pcVar3;
    undefined8 uVar4;
    ulong uVar5;
    // ...
    pcVar3 = (char *)FUN_00120e00(abStack_48);
    p_Var7 = (_jobject *)_JNIEnv::NewStringUTF(param_1,pcVar3);
    _JNIEnv::CallVoidMethod((_jobject *)param_1,(_jmethodID *)param_2,uVar4,p_Var7);
    _JNIEnv::DeleteLocalRef(param_1,p_Var7);
    std::__ndk1::basic_string<>::~basic_string(abStack_48);
    std::__ndk1::basic_string<>::~basic_string(abStack_30);
    lVar1 = tpidr_el0;
    if (*(long *)(lVar1 + 0x28) == local_18) {
        return;
    }
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
}
Nim
void Java_com_backdoor_sl4ydroid_MainActivity_nim
               (_JNIEnv *param_1,_jobject *param_2,_jstring *param_3)

{
    byte bVar1;
    byte bVar2;
    ulong uVar3;
    _jclass *p_Var4;
    // ...
        pcVar5 = (char *)FUN_00120e00(abStack_58);
    p_Var10 = (_jobject *)_JNIEnv::NewStringUTF(param_1,pcVar5);
    _JNIEnv::CallVoidMethod((_jobject *)param_1,(_jmethodID *)param_2,uVar6,p_Var10);
    _JNIEnv::DeleteLocalRef(param_1,p_Var10);
    std::__ndk1::basic_string<>::~basic_string(abStack_58);
    std::__ndk1::basic_string<>::~basic_string(abStack_40);
    lVar7 = tpidr_el0;
    if (*(long *)(lVar7 + 0x28) == local_28) {
        return;
    }
                    // WARNING: Subroutine does not return
    __stack_chk_fail();
}

If someone would observe the above functions carefully, they would notice the following code in every function.

p_Var10 = (_jobject *)_JNIEnv::NewStringUTF(param_1,pcVar5);

Now, my first assumption (and last) was that the code tries to do something at its core and constructs a string for the Java world using _JNIEnv::NewStringUTF. This is MY time to use frida to extract the flag.

First I patched this apk using objection.

pip install objection
objection patchapk -s sl4ydroid.apk

I grabbed a cable and installed this patched APK on my phone (using ADB).

Next, I began to write a frida script:

const base = ptr(Process.findModuleByName('libsl4ydroid.so').base)
console.log(`[+] base: ${base}`)

Interceptor.attach(base.add(0x20ee0), {  // Offset
    onEnter: (args) => {
        console.log(`[NewString] ${args[1].readUtf8String()}`)
    }
})

About the offset, remember when I avoided not to choose the x86 binaries. Because android would run the ARM version of the library and we would have a different offset.

And that’s it. Now launching the application with frida on the PC would give me the following flag.

flag{RizZZ! Rc4_R3v3r51Ngg_RrR!:}

Now I know that RC4 is used to encrypt the flag in the binary :P

Final thoughts #

If an application is using a specific data structure extensively (for example: std::string), hooking some overloads and monitoring the pointer from the constructor would leak some vital data of that application.

comments powered by Disqus