Pages

Friday, November 8, 2013

Custom System Service using XposedBridge

For those of you who don't know XposedBridge, you should really have a look at it. By enabling you to hook any Class and Method within Android, system classes as well as normal application classes, this is just as powerful a tool as a root shell session. This however operates within the Java VM instead of hacking your way in from a shell.

After working with XposedBridge for awhile, I started stumbling across a few limitations. The Xposed Framework might be able to hook any method in any class, but it can of cause only add a hook before and after execution of the original method. This means that you cannot change anything within a method, but instead either replace it, change the arguments being parsed to it or change the result from it. In most cases this will be enough, however since Android parses a lot of things between multiple processes in JVM and Native code (which you cannot hook), there are circumstances where this will not be enough. For an example, if you decide to add a hook to PhoneWindowManager's interceptKeyBeforeQueueing or interceptKeyBeforeDispatching methods to change the incoming key code or some policy flags, that hook will not be enough. The two original methods will be executed with these new values, but after they have executed, the Native Event Handler will parse the original values to the application dispatchers, and your hook changes will not affect that part. This means that you would also have to add a hook to the KeyEvent's dispatch method. But, since that one will run in different processes than your first hook(s), you cannot share data between them, not even if you add all hooks to one single class instance or use a static class.

In some cases, you can use Broadcasts to send data from one process to another. But the problem with Broadcasts is that you cannot be sure about when the receiver will be invoked or when it is done processing your data. Also, sending a Broadcast or adding a Receiver requires access to a Context, and when working with Xposed you will not always have one of those. The best option in these cases is adding a custom System Service that you can use to share data between processes. The positive thing about System Services, unlike normal Application Services, is that they are available always, from the beginning of System Boot and until the System is shut down. Also they are much easier to connect to. Normally you would not be able to create such a service, but since we are working with XposedBridge, you can actually add whatever you'd like since you can operate as being part of Android.

The first thing that we will need, is an AIDL interface file. You cannot have a System Service without this. In your project, create a new package named android.os and in that package create a new file named ICustomService.aidl that looks like the below example.

android.os.ICustomService.aidl:
package android.os;
/** {@hide} */
interface IXAService {

}

We will also need a class in our regular project package that will be used to hook the service into Android. Create a class named CustomServiceHook.java somewhere in your project package.

CustomServiceHook.java:
public final class CustomServiceHook implements IXposedHookZygoteInit {
 @Override
 public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
  CustomService.inject();
 }
}

Since you should already be familiar with XposedBridge, you should already know how to add this hook to be invoked by Xposed during boot.

The CustomService class that we call in our hook above is our System Service Class. The inject() method is a static method from where we will inject the Service into Android so that it can be used by our module. Create a new package named com.android.server and create a new class in that package named CustomService.java.

com.android.server.CustomService.java:
public class CustomService extends ICustomService.Stub {
 public static void inject() {

 }
}

This is our basic classes. Now it's time to extend CustomService.java to actually make it work. All of Android's original services are created and registered from com.android.server.SystemServer.java. However, all of the creation and registration is done from within a Thread. The problem here is that this Thread keeps the System Context as a normal variable, so we have no way to access it outside the Run() method. And since we cannot hook our way into the method but only add a hook before or after, this method and class is of no use to us.

However, the first thing that this method does, is invoke the main() method of com.android.server.am.ActivityManagerService in order to get the System Context. So if we add a hook after that method, the hook will be invoked in the beginning of the SystemServer thread, and we will also have access to the System Context which will be in the result from the main() method that we added our hook to. Let's change our CustomService.java file and add this hook to it.

com.android.server.CustomService.java:
public class CustomService extends ICustomService.Stub {

 private Context mContext;

 private static CustomService oInstance;

 public static void inject() {
  final Class ActivityManagerServiceClazz = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", null);
  
  XposedBridge.hookAllMethods(
   ActivityManagerServiceClazz, 
   "main", 
   new XC_MethodHook() {
    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
     Context context = (Context) param.getResult();
     
     oInstance = new CustomService(context);
     
     XposedHelpers.callMethod(
       XposedTools.findClass("android.os.ServiceManager"), 
       "addService", 
       new Class[]{String.class, IBinder.class}, 
       "custom.service", 
       oInstance
     );
    }
   }
  );
 }

 public XAService(Context context) {
  mContext = context;
 }
}

The service will now be created and registered with Android during boot, but we are not quite done yet. At the time this service is created, no other services are available. So if you want to do some initializing, the constructor is not the place to do it. What we need is yet another hook that will be invoked once the system is ready. The ActivityManagerService class also has a good place for this. It has a systemReady() method that is invoked once all of the services has been created and is up and running. Let's add this to our CustomService class.

com.android.server.CustomService.java:
public class CustomService extends ICustomService.Stub {

 private Context mContext;

 private static CustomService oInstance;

 public static void inject() {
  final Class ActivityManagerServiceClazz = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", null);
  
  XposedBridge.hookAllMethods(
   ActivityManagerServiceClazz, 
   "main", 
   new XC_MethodHook() {
    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
     Context context = (Context) param.getResult();
     
     oInstance = new CustomService(context);
     
     XposedHelpers.callMethod(
       XposedTools.findClass("android.os.ServiceManager"), 
       "addService", 
       new Class[]{String.class, IBinder.class}, 
       "custom.service", 
       oInstance
     );
    }
   }
  );

  XposedBridge.hookAllMethods(
   ActivityManagerServiceClazz, 
   "systemReady", 
   new XC_MethodHook() {
    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
     oInstance.systemReady();
    }
   }
  );
 }

 public XAService(Context context) {
  mContext = context;
 }

 private void systemReady() {
  // Make your initialization here
 }
}

Now you are done. Your new service will now be loaded into Android's ServiceManager during boot via our first hook to the ActivityServiceManager.main() method and it will be initialized once all services is ready via our second hook to ActivityServiceManager.systemReady().

After the initialization, you can access the binder using ServiceManager.getService( name ) just like with any other System Service provided by Android.

All that is left is to implement whatever options you would like this service to provide and start using it in your module.

Example of usage:
public class SomeClass {
 ICustomService mService;
 
 public void someMethod() {
  if (mService == null) {
   mService = ICustomService.Stub.asInterface(
     ServiceManager.getService("custom.service")
   );
  }
  
  mService.someServiceMethod();
 }
}

2 comments:

  1. ;o You did what I wanted.

    ReplyDelete
  2. Thank you for your work. I tried a lot to find out why it wasn't working for me, until I found the problem. I had to use:

    XposedHelpers.callStaticMethod(
    Class.forName("android.os.ServiceManager"),
    "addService",
    new Class[]{ String.class, IBinder.class },
    "custom.service",
    oInstance
    );

    The callStaticMethod did the trick. Lucky to find it in the XposedHelpers code on github and try it out.

    ReplyDelete