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
-
Stub
-
The Stub class is generated by the Android build system from the
.aidlfile on the service side. -
It implements the actual service interface (
IInterface) and extends theBinderclass. -
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.
-
-
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 underlyingIBinder. -
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:
| Step | Description |
|---|---|
| 1 | Client calls method on Proxy object (e.g., isMagiskPresent()). |
| 2 | Proxy marshals method parameters into a Parcel and sends a transaction via the Binder driver to the remote process. |
| 3 | The Stub’s onTransact() method in the service process receives the Parcel. |
| 4 | Stub unmarshals the Parcel data, identifies which method to call, and invokes the actual implementation. |
| 5 | The service method executes and returns the result. |
| 6 | Stub marshals the result into another Parcel and sends it back via Binder to the Proxy. |
| 7 | Proxy 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 forisMagiskPresent()arrives (identified by the code), it calls the local method implementation. -
Method calls on
Proxyresult in IPC traffic to the remoteStub. -
If the client and service are in the same process,
asInterfaceoptimizes 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
Binderlevel. -
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 Target | Process | Purpose | Effect |
|---|---|---|---|
| Proxy (Client) | Client | Intercept outgoing client method calls | Spoof or modify data before it travels across IPC |
| Stub (Service) | Service | Intercept service-side method implementations | Modify actual service behavior seen by all clients |
| Binder.onTransact() | Any process | Intercept IPC at the Binder transaction level | Universal 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