Skip to main content

Write-up for Reverse Master CSCV2025

·2330 words·11 mins

Hello everyone🫡! Today I will write a little bit about The Reverse Master challenge in CSCV2025. The question:

image

When we install the file we will have an apk file like this:

image
Use jadx-gui to decompile this file,then open Source code/com/ctf.challenge and you will see Main Activity like this:
image
Open that and you will see the code below:

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

image

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

image
Let open this file in ghidra and find the Java_com_ctf_challenge_MainActivity_checkSecondHalf function:

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:

image
So let go to FUN_0011ad68 to see what inside 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😘!