Back to blog
Aug 08, 2025
6 min read

Hooking AIDL IPC with Frida

Hook AIDL IPC in Android by targeting Proxy (client), Stub (service), or Binder.onTransact for full control over inter-process communication using Frida

What is AIDL ?

AIDL (Android Interface Definition Language) is a mechanism in Android to enable calling methods across different processes. Since processes cannot share memory directly, Android uses the Binder IPC framework behind the scenes to communicate between client and service.

The Role of Proxy and Stub in AIDL

  1. Stub

    • The Stub class is generated by the Android build system from the .aidl file on the service side.

    • It implements the actual service interface (IInterface) and extends the Binder class.

    • The Stub receives incoming IPC calls from the client, unmarshals (deserializes) the data sent over IPC, and dispatches the calls to the local service implementation.

    • In code, the Stub is an abstract class you extend to implement your service methods.

  2. Proxy

    • The Proxy class is also generated automatically and exists on the client side.

    • It is a local object that the client code uses to call the remote service methods.

    • When you call a method on Proxy, it:

      • Creates a Parcel.

      • Writes the input arguments into it.

      • Calls transact() on the underlying IBinder.

      • Waits for the remote result and reads the reply Parcel.

    • Proxy looks like a local implementation of the interface, but every call is an IPC transaction.


Communication Flow Between Proxy and Stub

When the client calls a method on the Proxy, the following happens:

StepDescription
1Client calls method on Proxy object (e.g., isMagiskPresent()).
2Proxy marshals method parameters into a Parcel and sends a transaction via the Binder driver to the remote process.
3The Stub’s onTransact() method in the service process receives the Parcel.
4Stub unmarshals the Parcel data, identifies which method to call, and invokes the actual implementation.
5The service method executes and returns the result.
6Stub marshals the result into another Parcel and sends it back via Binder to the Proxy.
7Proxy unmarshals the result Parcel and returns the result to the client code that called the method.

This IPC is transparent to the client; it looks like a local call via the Proxy.


Simple Example of Proxy and Stub Communication

Suppose you have an AIDL interface IExampleService.aidl:

interface IExampleService {
    boolean isMagiskPresent();
}

Android generates a Java interface with nested Proxy and Stub classes behind the scenes:

  • IExampleService.Stub (service side)

  • IExampleService.Stub.Proxy (client side)

Proxy (Client-Side)

IExampleService service = IExampleService.Stub.asInterface(serviceBinder);
boolean magiskPresent = service.isMagiskPresent();  // This calls Proxy method
    private static class Proxy implements IExampleService {
        private IBinder mRemote;

        Proxy(IBinder remote) {
            mRemote = remote;
        }

        @Override
        public boolean isMagiskPresent() throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            boolean result;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_isMagiskPresent, data, reply, 0);
                reply.readException();
                result = (reply.readInt() != 0);
            } finally {
                reply.recycle();
                data.recycle();
            }
            return result;
        }
    }

Behind this call, the Proxy writes data into IPC parcel and sends it to the service.

Stub (Service Side)

public abstract static class Stub extends Binder implements IExampleService {
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        if (code == TRANSACTION_isMagiskPresent) {
            data.enforceInterface(DESCRIPTOR);
            boolean result = this.isMagiskPresent();
            reply.writeNoException();
            reply.writeInt(result ? 1 : 0);
            return true;
        }
        return super.onTransact(code, data, reply, flags); 
    }

    public static IExampleService asInterface(IBinder obj) {
        if (obj == null) return null;
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof IExampleService) {
            return (IExampleService) iin;
        }
        return new Proxy(obj);
    }

}


Key Details from this Flow

  • Proxy.isMagiskPresent() creates data parcels, sends transactions, and reads replies, making IPC transparent to the caller.

  • Stub.onTransact() listens for IPC transactions. When a transaction for isMagiskPresent() arrives (identified by the code), it calls the local method implementation.

  • Method calls on Proxy result in IPC traffic to the remote Stub.

  • If the client and service are in the same process, asInterface optimizes by returning the local interface directly, avoiding IPC overhead.


How to Hook with Frida

When to Hook Proxy?

  • Hooks calls before they go over IPC.

  • Useful if the service runs in a different process.

  • Intercepts and spoofs the result on the client side, no need to attach to service.

Proxy Hook Snippet:

Java.perform(function () {
    var Proxy = Java.use("com.example.IExampleService$Stub$Proxy");
    Proxy.isMagiskPresent.implementation = function () {
        console.log("[+] Hooked Proxy.isMagiskPresent()");
        return false;  // Spoof result to client as no Magisk
    };
});

When to Hook Stub?

  • Hooks the actual implementation in the service process.

  • Requires attaching to the service process.

  • Spoofs the response for all clients, service-wide.

Stub Hook Snippet:

Java.perform(function () {
    var Stub = Java.use("com.example.IExampleService$Stub");
    Stub.isMagiskPresent.implementation = function () {
        console.log("[+] Hooked Stub.isMagiskPresent()");
        return false;  // Always say no Magisk on service side
    };
});

Hooking Binder.onTransact() (Low-Level Fallback)

  • Raw IPC interception at Binder level.

  • Doesn’t rely on AIDL-generated class names.

  • Works even if class names are obfuscated or removed.

Java.perform(function () {
    var Binder = Java.use('android.os.Binder');
    Binder.onTransact.implementation = function (code, data, reply, flags) {
        if (code === 1) {  // Transaction code for isMagiskPresent call
            console.log("[+] Intercepted isMagiskPresent transaction");
            reply.writeNoException();
            reply.writeInt(0);  // false = no Magisk
            return true;        // Bypass original implementation
        }
        return this.onTransact(code, data, reply, flags);
    };
});


When to Hook Proxy vs Stub in Frida

Hook TargetProcessPurposeEffect
Proxy (Client)ClientIntercept outgoing client method callsSpoof or modify data before it travels across IPC
Stub (Service)ServiceIntercept service-side method implementationsModify actual service behavior seen by all clients
Binder.onTransact()Any processIntercept IPC at the Binder transaction levelUniversal fallback for all IPC calls, bypassing Proxy and Stub

Diagrammatic Overview

Client Process                            Service Process
---------------                          -----------------
  Client code
     |
     v
+------------+     IPC transaction      +-----------------+
| Proxy Impl | -----------------------> | Stub onTransact |
| (marshals) |                          | (unmarshals)    |
+------------+                          +-----------------+
     |                                            |
     v                                            v
Return result <-------------------------------- Call service implementation