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.