Hello everyone🫡! Today I will write a little bit about The Reverse Master challenge in CSCV2025.
The question:
When we install the file we will have an apk file like this:

package com.ctf.challenge;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import o.CountDownTimerC0176z3;
import o.F2;
import o.F3;
import o.RunnableC0154w;
import o.V4;
/* loaded from: classes.dex */
public final class MainActivity extends AppCompatActivity {
public static final /* synthetic */ int b = 0;
public final byte[] a = {66, 51, 122, 33, 86};
static {
try {
System.loadLibrary("native-lib");
} catch (UnsatisfiedLinkError e) {
Log.e("CTF", "❌ Native lib failed: " + e.getMessage());
}
}
public final native boolean checkSecondHalf(String str);
public final native String getHint();
public final void h(LinearLayout linearLayout, String str, String str2) {
View viewInflate = getLayoutInflater().inflate(android.R.layout.simple_list_item_2, (ViewGroup) linearLayout, false);
TextView textView = (TextView) viewInflate.findViewById(android.R.id.text1);
TextView textView2 = (TextView) viewInflate.findViewById(android.R.id.text2);
textView.setText("⚠️ ".concat(str));
textView.setTextColor(Color.parseColor("#FF5252"));
textView.setTextSize(16.0f);
textView.setTypeface(null, 1);
textView2.setText(str2);
textView2.setTextColor(Color.parseColor("#BDBDBD"));
textView2.setTextSize(13.0f);
linearLayout.addView(viewInflate);
}
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public final void onCreate(Bundle bundle) throws IOException {
boolean z;
String line;
super.onCreate(bundle);
setContentView(R.layout.activity_main);
boolean z2 = false;
boolean z3 = (getApplicationInfo().flags & 2) != 0;
String[] strArr = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su"};
int i = 0;
while (true) {
if (i >= 9) {
try {
Runtime.getRuntime().exec("su");
break;
} catch (Exception unused) {
z = false;
}
} else if (new File(strArr[i]).exists()) {
break;
} else {
i++;
}
}
z = true;
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("ps").getInputStream()));
do {
line = bufferedReader.readLine();
if (line == null) {
break;
} else if (!V4.u(line, "frida") && !V4.u(line, "gum-js-loop")) {
}
} while (!V4.u(line, "gmain"));
z2 = true;
bufferedReader.close();
} catch (Exception unused2) {
}
int i2 = 27042;
while (true) {
if (i2 >= 27053) {
break;
}
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", i2), 100);
socket.close();
z2 = true;
break;
} catch (Exception unused3) {
i2++;
}
}
if (!z3 && !z && !z2) {
Thread thread = new Thread(new RunnableC0154w(8, this));
thread.setDaemon(true);
thread.start();
final TextInputLayout textInputLayout = (TextInputLayout) findViewById(R.id.flagInputLayout);
final TextInputEditText textInputEditText = (TextInputEditText) findViewById(R.id.flagInput);
Button button = (Button) findViewById(R.id.checkButton);
Button button2 = (Button) findViewById(R.id.hintButton);
button.setOnClickListener(new View.OnClickListener() { // from class: o.y3
/* JADX WARN: Removed duplicated region for block: B:4:0x001a */
@Override // android.view.View.OnClickListener
/*
Code decompiled incorrectly, please refer to instructions dump.
To view partially-correct code enable 'Show inconsistent code' option in preferences
*/
public final void onClick(android.view.View r13) {
/*
r12 = this;
r13 = 16
r0 = 1
int r1 = com.ctf.challenge.MainActivity.b
com.google.android.material.textfield.TextInputEditText r1 = r1
android.text.Editable r1 = r1.getText()
java.lang.String r1 = java.lang.String.valueOf(r1)
com.ctf.challenge.MainActivity r2 = r2
java.lang.String r3 = "CSCV2025{"
boolean r3 = r1.startsWith(r3)
r4 = 0
if (r3 != 0) goto L1c
L1a:
r13 = r4
goto L6e
L1c:
java.lang.String r3 = "}"
boolean r3 = r1.endsWith(r3)
if (r3 != 0) goto L25
goto L1a
L25:
int r3 = r1.length()
int r3 = r3 - r0
r5 = 9
java.lang.String r1 = r1.substring(r5, r3)
java.lang.String r3 = "substring(...)"
o.F2.e(r1, r3)
java.lang.String r5 = r1.substring(r4, r13)
o.F2.e(r5, r3)
byte[] r6 = new byte[r13]
r6 = {x0090: FILL_ARRAY_DATA , data: [122, 86, 27, 22, 53, 35, 80, 77, 24, 98, 122, 7, 72, 21, 98, 114} // fill-array
byte[] r7 = new byte[r13]
r8 = r4
L44:
if (r8 >= r13) goto L55
r9 = r6[r8]
byte[] r10 = r2.a
int r11 = r10.length
int r11 = r8 % r11
r10 = r10[r11]
r9 = r9 ^ r10
byte r9 = (byte) r9
r7[r8] = r9
int r8 = r8 + r0
goto L44
L55:
java.lang.String r6 = new java.lang.String
java.nio.charset.Charset r8 = o.X.a
r6.<init>(r7, r8)
boolean r5 = r5.equals(r6)
if (r5 != 0) goto L63
goto L1a
L63:
java.lang.String r13 = r1.substring(r13)
o.F2.e(r13, r3)
boolean r13 = r2.checkSecondHalf(r13)
L6e:
com.google.android.material.textfield.TextInputLayout r1 = r3
if (r13 == 0) goto L80
java.lang.String r13 = "🎉 Correct! Flag is valid!"
android.widget.Toast r13 = android.widget.Toast.makeText(r2, r13, r0)
r13.show()
r13 = 0
r1.setError(r13)
return
L80:
java.lang.String r13 = "❌ Wrong flag! Try again!"
android.widget.Toast r13 = android.widget.Toast.makeText(r2, r13, r4)
r13.show()
java.lang.String r13 = "Invalid flag"
r1.setError(r13)
return
*/
throw new UnsupportedOperationException("Method not decompiled: o.ViewOnClickListenerC0170y3.onClick(android.view.View):void");
}
});
button2.setOnClickListener(new F3(3, this));
return;
}
setContentView(R.layout.security_warning);
TextView textView = (TextView) findViewById(R.id.countdownText);
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.issuesList);
if (z3) {
F2.c(linearLayout);
h(linearLayout, "Debug Mode Detected", "Application is running in debuggable mode");
}
if (z) {
F2.c(linearLayout);
h(linearLayout, "Root Access Detected", "Device has been rooted or jailbroken");
}
if (z2) {
F2.c(linearLayout);
h(linearLayout, "Frida Framework Detected", "Dynamic instrumentation tool is running");
}
new CountDownTimerC0176z3(textView, this).start();
}
public final native void startFridaMonitoring();
}
You can find the first half of the flag with this XOR code from python:
byte[] encrypted = {
122, 86, 27, 22, 53, 35, 80, 77,
24, 98, 122, 7, 72, 21, 98, 114
};
byte[] key = {66, 51, 122, 33, 86};
for (int i = 0; i < 16; i++) {
decrypted[i] = encrypted[i] ^ key[i % key.length];
}
String expected = new String(decrypted, Charset.UTF_8);
return input.equals(expected);
The output is:
8ea7cac794842440
To find out the second half of the flag, we’ll disassemble and analyze the native code to reverse the logic inside checkSecondHalf from libnative-lib.so Let use apktool command in terminal to extract libnative-lib.so from the apk file:
apktool d -f reverse-master.apk -o reverse-master_src

A new folder named reverse-master_src will appear in the same path you use Terminal. Check that out and find libnative-lib.so in the lib folder
bool Java_com_ctf_challenge_MainActivity_checkSecondHalf
(long *param_1,undefined8 param_2,undefined8 param_3)
{
int iVar1;
int iVar2;
int iVar3;
ulong uVar4;
char *__s;
size_t sVar5;
uVar4 = FUN_00119ca0();
if ((uVar4 & 1) != 0) {
__android_log_print(4,"Lib-Native","Debugger detected in native code!");
return false;
}
__s = (char *)(**(code **)(*param_1 + 0x548))(param_1,param_3,0);
if (__s != (char *)0x0) {
sVar5 = strlen(__s);
iVar1 = rand();
iVar1 = iVar1 % 0x32 + 1;
iVar2 = rand();
iVar2 = iVar2 % 0x32 + 1;
if (iVar1 * iVar1 + iVar2 * iVar2 == (iVar2 + iVar1) * (iVar2 + iVar1) + iVar1 * iVar2 * -2 + 1)
{
FUN_0011ac60(__s);
FUN_0011accc();
}
iVar1 = rand();
iVar1 = iVar1 % 100;
iVar2 = rand();
iVar2 = iVar2 % 100;
if ((iVar2 + iVar1) * (iVar2 + iVar1) < iVar1 * iVar1 + iVar2 * iVar2) {
iVar3 = 0;
}
else {
iVar3 = FUN_0011ad68(__s,sVar5 & 0xffffffff);
iVar1 = rand();
iVar1 = iVar1 % 0x32 + 1;
iVar2 = rand();
iVar2 = iVar2 % 0x32 + 1;
if (iVar1 * iVar1 + iVar2 * iVar2 ==
(iVar2 + iVar1) * (iVar2 + iVar1) + iVar1 * iVar2 * -2 + 1) {
FUN_0011b5e8(__s);
FUN_0011b658();
}
}
(**(code **)(*param_1 + 0x550))(param_1,param_3,__s);
if (iVar3 != 0) {
iVar1 = rand();
iVar1 = iVar1 % 100;
iVar2 = rand();
iVar2 = iVar2 % 100;
if (iVar1 * iVar1 + iVar2 * iVar2 <= (iVar2 + iVar1) * (iVar2 + iVar1)) {
return true;
}
}
iVar1 = rand();
iVar1 = iVar1 % 0x32 + 1;
iVar2 = rand();
iVar2 = iVar2 % 0x32 + 1;
return iVar1 * iVar1 + iVar2 * iVar2 ==
(iVar2 + iVar1) * (iVar2 + iVar1) + iVar1 * iVar2 * -2 + 1;
}
rand();
rand();
return false;
}
Let chatGPT analyze this:
undefined4 FUN_0011ad68(byte *param_1,int param_2)
{
byte bVar1;
byte bVar2;
byte bVar3;
byte bVar4;
byte bVar5;
undefined1 auVar6 [16];
undefined8 uVar7;
uint5 uVar8;
int iVar9;
int iVar10;
byte *pbVar11;
uint5 *puVar12;
uint uVar13;
byte bVar14;
byte bVar15;
byte bVar16;
byte bVar17;
byte bVar18;
undefined4 local_90;
pbVar11 = (byte *)calloc(0x10,1);
uVar13 = 0x1a2b;
bVar18 = 0;
local_90 = 0;
do {
if (uVar13 < 0x7a8b) {
if (uVar13 == 0x1a2b) {
iVar9 = rand();
iVar9 = iVar9 % 100;
iVar10 = rand();
iVar10 = iVar10 % 100;
uVar13 = 0xbecf;
if (iVar9 * iVar9 + iVar10 * iVar10 <= (iVar10 + iVar9) * (iVar10 + iVar9)) {
uVar13 = 0x3c4d;
}
}
else if (uVar13 == 0x3c4d) {
if (param_2 != 0x10) goto LAB_0011b004;
iVar9 = rand();
iVar9 = iVar9 % 0x32 + 1;
iVar10 = rand();
iVar10 = iVar10 % 0x32 + 1;
uVar13 = 0xbecf;
if (iVar9 * iVar9 + iVar10 * iVar10 !=
(iVar10 + iVar9) * (iVar10 + iVar9) + iVar9 * iVar10 * -2 + 1) {
uVar13 = 0x5e6f;
}
}
else {
if (uVar13 != 0x5e6f) goto LAB_0011b538;
puVar12 = (uint5 *)calloc(5,1);
if (puVar12 == (uint5 *)0x0) {
bVar14 = 0;
bVar18 = 0;
bVar16 = 0;
bVar17 = 0;
bVar15 = 0;
}
else {
bVar14 = 99;
bVar15 = 0x7d;
bVar17 = 0xe2;
bVar16 = 0x14;
bVar18 = 0xb8;
*(undefined4 *)puVar12 = 0xb814e27d;
*(byte *)((long)puVar12 + 4) = 99;
}
bVar1 = *(byte *)((long)puVar12 + 1);
bVar2 = (byte)*puVar12;
bVar3 = *(byte *)((long)puVar12 + 2);
bVar4 = *(byte *)((long)puVar12 + 3);
bVar5 = *(byte *)((long)puVar12 + 4);
uVar8 = *puVar12;
pbVar11[1] = (bVar17 ^ 0x6c) - 10 ^ bVar1;
*pbVar11 = (bVar15 ^ 0x2f) - 7 ^ bVar2;
pbVar11[2] = ((bVar16 | 1) ^ 0x95) - 0xd ^ bVar3 ^ 2;
pbVar11[0xd] = (bVar18 ^ 8) - 0x2e ^ bVar4 ^ 0xd;
pbVar11[4] = (bVar14 ^ 0x74) - 0x13 ^ bVar5 ^ 4;
pbVar11[0xf] = (bVar15 ^ 7) - 0x34 ^ bVar2 ^ 0xf;
pbVar11[0x10] = 0;
pbVar11[3] = ((bVar18 | 2) ^ 0x21) - 0x10 ^ bVar4;
pbVar11[0xe] = (bVar14 ^ 0x5a) - 0x31 ^ bVar5 ^ 0xe;
auVar6._5_3_ = 0;
auVar6._0_5_ = uVar8;
auVar6[8] = bVar2;
auVar6[9] = bVar1;
auVar6[10] = bVar3;
auVar6[0xb] = bVar4;
auVar6[0xc] = bVar5;
auVar6._13_3_ = 0;
uVar7 = a64_TBL(ZEXT816(0),auVar6,0x201000403020100);
bVar18 = ((bVar18 | 7) ^ 0x4d) - 0x1f ^ 8 ^ (byte)((ulong)uVar7 >> 0x18);
*(ulong *)(pbVar11 + 5) =
CONCAT17(((bVar16 | 0xb) ^ 0x53) - 0x2b ^ 0xc ^ (byte)((ulong)uVar7 >> 0x38),
CONCAT16((bVar17 ^ 0xe2) - 0x28 ^ 0xb ^ (byte)((ulong)uVar7 >> 0x30),
CONCAT15((bVar15 ^ 0x17) - 0x25 ^ 10 ^ (byte)((ulong)uVar7 >> 0x28) ,
CONCAT14(((bVar14 | 8) ^ 0x45) - 0x22 ^ 9 ^
(byte)((ulong)uVar7 >> 0x20),
CONCAT13(bVar18,CONCAT12((bVar16 ^ 0x28) - 0x1c ^ 7
^ (byte)((ulong)uVar7 >>
0x10),
CONCAT11(((bVar17 | 5) ^
0x47) - 0x19 ^ 6
^ (byte)((ulong)
uVar7 >> 8),
(bVar15 ^ 0x4c) - 0x16 ^ 5 ^ (byte)uVar7)))))));
iVar9 = rand();
iVar9 = iVar9 % 100;
iVar10 = rand();
iVar10 = iVar10 % 100;
uVar13 = 0xbecf;
if (iVar9 * iVar9 + iVar10 * iVar10 <= (iVar10 + iVar9) * (iVar10 + iVar9)) {
uVar13 = 0x7a8b;
}
}
}
else if (uVar13 < 0xbecf) {
if (uVar13 == 0x7a8b) {
if (((((((((((((((*pbVar11 == *param_1 && pbVar11[1] == param_1[1]) &&
pbVar11[2] == param_1[2]) && pbVar11[3] == param_1[3]) &&
pbVar11[4] == param_1[4]) && pbVar11[5] == param_1[5]) &&
pbVar11[6] == param_1[6]) && pbVar11[7] == param_1[7]) && bVar18 == param_1[8])
&& pbVar11[9] == param_1[9]) && pbVar11[10] == param_1[10]) &&
pbVar11[0xb] == param_1[0xb]) && pbVar11[0xc] == param_1[0xc]) &&
pbVar11[0xd] == param_1[0xd]) && pbVar11[0xe] == param_1[0xe]) &&
pbVar11[0xf] == param_1[0xf]) {
iVar9 = rand();
iVar9 = iVar9 % 100;
iVar10 = rand();
iVar10 = iVar10 % 100;
if (iVar9 * iVar9 + iVar10 * iVar10 <= (iVar10 + iVar9) * (iVar10 + iVar9)) {
uVar13 = 0x9cad;
goto LAB_0011b008;
}
}
LAB_0011b004:
uVar13 = 0xbecf;
}
else {
if (uVar13 != 0x9cad) goto LAB_0011b538;
local_90 = 1;
LAB_0011b0f4:
uVar13 = 0xd1e2;
}
}
else {
if (uVar13 == 0xbecf) {
local_90 = 0;
goto LAB_0011b0f4;
}
if (uVar13 == 0xd1e2) {
return local_90;
}
LAB_0011b538:
iVar9 = rand();
iVar9 = iVar9 % 100;
iVar10 = rand();
iVar10 = iVar10 % 100;
if (iVar9 * iVar9 + iVar10 * iVar10 <= (iVar10 + iVar9) * (iVar10 + iVar9)) {
uVar13 = 0xbecf;
}
}
LAB_0011b008:
rand();
} while( true );
}
So i give this to chatGPT and i have a python code solve the second half like this:
# solve_second_half.py
def u8(x): return x & 0xFF
def compute_second_half():
# puVar12 = 0xb8 14 e2 7d + 0x63 (little-endian 0xb814e27d then 0x63)
b2 = 0x7d
b1 = 0xe2
b3 = 0x14
b4 = 0xb8
b5 = 0x63
# duplicates in code
b14 = 0x63
b15 = 0x7d
b17 = 0xe2
b16 = 0x14
b18 = 0xb8
pb = [0]*16
pb[1] = u8(((b17 ^ 0x6C) - 10) ^ b1)
pb[0] = u8(((b15 ^ 0x2F) - 7) ^ b2)
pb[2] = u8((((b16 | 1) ^ 0x95) - 13) ^ b3 ^ 2)
pb[13] = u8((((b18 ^ 8) - 46) ^ b4) ^ 13)
pb[4] = u8((((b14 ^ 0x74) - 19) ^ b5) ^ 4)
pb[15] = u8((((b15 ^ 7) - 52) ^ b2) ^ 15)
pb[3] = u8((((b18 | 2) ^ 0x21) - 16) ^ b4)
pb[14] = u8((((b14 ^ 0x5A) - 49) ^ b5) ^ 14)
# Build auVar6 (first 5 bytes are [b2,b1,b3,b4,b5])
au = [0]*16
au[0],au[1],au[2],au[3],au[4] = b2,b1,b3,b4,b5
# a64_TBL(..., mask=0x0201000403020100) → select bytes [0,1,2,3,4,0,1,2]
sel = [au[i] for i in (0,1,2,3,4,0,1,2)]
pick = lambda sh: sel[(sh//8)]
# update b18 and fill pb[5..12]
b18 = u8(((b18 | 7) ^ 0x4D) - 0x1F ^ 8 ^ pick(0x18))
X1 = u8((b15 ^ 0x4C) - 0x16 ^ 5 ^ pick(0x00))
X2 = u8(((b17 | 5) ^ 0x47) - 0x19 ^ 6 ^ pick(0x08))
X3 = u8((b16 ^ 0x28) - 0x1C ^ 7 ^ pick(0x10))
X4 = u8(((b14 | 8) ^ 0x45) - 0x22 ^ 9 ^ pick(0x20))
X5 = u8((b15 ^ 0x17) - 0x25 ^ 10 ^ pick(0x28))
X6 = u8((b17 ^ 0xE2) - 0x28 ^ 11 ^ pick(0x30))
X7 = u8(((b16 | 0xB) ^ 0x53) - 0x2B ^ 12 ^ pick(0x38))
pb[5],pb[6],pb[7],pb[8],pb[9],pb[10],pb[11],pb[12] = X1,X2,X3,b18,X4,X5,X6,X7
return bytes(pb)
if __name__ == "__main__":
second = compute_second_half()
print(second.hex()) # 6fe3ccc3cf2197e4
print(second.decode()) # 6fe3ccc3cf2197e4
The output is:
6fe3ccc3cf2197e4
So the flag is CSCV2025{8ea7cac7948424406fe3ccc3cf2197e4} Thanks for watching this post😘!
