My Blog, written in golang!

使用Xposed添加自定义系统服务

发表于 2016/08/30 19:17  |  分类于 xposed

Xposed 是 Android 平台上一个著名的框架。基于这个框架,我们可以在不需要 root 的情况下修改(hook)任何系统和 App 的类和方法,正如作者介绍的那样 modify your ROM - without modifying any APK (developers) or flashing (users)!

Xposed 可以 hook 任何类的任何方法,但是仅限于在方法执行前和执行后加入钩子(hook),而并不能修改方法原有的代码。这在大多是情况是够用的,但是当涉及到多进程时就不行了。举个例子,你 hook 了微信,获取到了微信昵称,想将昵称显示在 QQ 中,如果你想简单的通过一个变量来传值是行不通的,因为微信和 QQ 运行在不同的进程中,在 QQ 进程中获取到的还是变量的初始值。

广播(Broadcast)是解决该问题的一种方法。但是广播的缺点是:1.不能确定什么时候能够收到2.创建广播和广播接收者都需要用到 Context ,而在 hook 的类里并不总是有 Context

最好的方法就是添加一个自定义的系统服务来进行进程间的数据共享。系统服务的优点有:1.服务从开机就启动了,并且一直存活到关机2.可以简单的通过ServiceManager.getService()来调用

在正常情况下是不能添加这样的系统服务的,但是我们可以借助 Xposed 来实现。实现进程间通信的服务需要用到 AIDL(Android Interface Definition Language),我们的服务也不例外。

首先,创建 android.os.ICustomService.aidl

package android.os;

/** {@hide} */
interface ICustomService {
	// implemention
}

以及它的实现类 CustomService.java

public class CustomService extends ICustomService.Stub {
	public static void register(final ClassLoader classLoader) {
		// implemention
	}
}

然后,创建一个 Xposed 模块用于注册服务

public class XposedMod implements IXposedHookLoadPackage {
	@Override
	public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable {
		if ("android".equals(loadPackageParam.packageName)) {
			CustomService.register(loadPackageParam.classLoader);
		}
	}
}

至于 register 方法的具体实现,也即如何向 Android 系统注册我们的服务,则是用到了 android.os.ServiceManageraddService 方法,在不同 Android 版本中的实现略有区别。addService 方法使用 private 修饰,所以需要使用反射来调用。这里,我们使用 Xposed 调用。

Android 的系统服务都是在 com.android.server.SystemServer 中注册的。在 5.0 之前,SystemServer 的 SystemContext 是由 ActivityManagerServicemain 方法返回的,而之后是由 createSystemContext 方法生成,并最终传递给了 ActivityManagerService构造方法(通过 ActivityManagerService.Lifecycle 和 SystemServiceManager)。

我们需要 SystemContext 来对 CustomService 进行一些初始化,所以分别 hook ActivityManagerService 的 main 方法和构造方法来获取 SystemContext,并注册 CustomService。具体实现如下:

public static void register(final ClassLoader classLoader) {
	Class<?> ActivityManagerService = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", classLoader);
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
		XposedBridge.hookAllConstructors(ActivityManagerService, new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(MethodHookParam param) throws Throwable {
				register(classLoader, (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"));
			}
		});
	} else {
		XposedBridge.hookAllMethods(ActivityManagerService, "main", new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(MethodHookParam param) throws Throwable {
				register(classLoader, (Context) param.getResult());
			}
		});
	}
}

public static void register(final ClassLoader classLoader, Context context) {
    mCustomService = new CustomService(context);

	Class<?> ServiceManager = XposedHelpers.findClass("android.os.ServiceManager", classLoader);
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
		XposedHelpers.callStaticMethod(
				ServiceManager,
				"addService",
				"custom.service",
				mCustomService,
				true
		);
	} else {
		XposedHelpers.callStaticMethod(
				ServiceManager,
				"addService",
				"custom.service",
				mCustomService
		);
	}
}

需要注意的一个地方是 5.0 以后的版本中,因为 selinux 的原因,服务名称需要加 user. 前缀,否则会抛出 java.lang.SecurityException 错误。

因为 CustomService 注册时,其他服务并没有初始化完成,所以需要找到其他的 hook 入口来完成 CustomService 的最终初始化,ActivityManagerService 的 systemReady 方法会在其他所有服务初始化完毕后调用,正是我们需要的。

XposedBridge.hookAllMethods(ActivityManagerService, "systemReady", new XC_MethodHook() {
	@Override
	protected void afterHookedMethod(MethodHookParam param) throws Throwable {
		mCustomService.systemReady();
	}
});

至此,CustomService 已经被注册到 Android 系统中,成为了一个系统服务。我们可以在其他类中调用它了:

public class SomeClass {
	ICustomService mService;

	public void someMethod() {
		if (mService == null) {
			mService = ICustomService.Stub.asInterface(
				ServiceManager.getService("custom.service")
			);
		}

		mService.someServiceMethod();
	}
}

最后,附上 CustomService 的完整代码:

public class CustomService extends ICustomService.Stub {
	private static final String SERVICE_NAME = "custom.service";

	private static Context mContext;

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

	public static ICustomService getClient() {
		if (mClient == null) {
			try {
				Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
				Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
				mClient = ICustomService.Stub.asInterface((IBinder) getService.invoke(null, getServiceName()));
			} catch (Throwable t) {
				mClient = null;
			}
		}

		return mClient;
	}

	public static String getServiceName() {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? "user." + SERVICE_NAME : SERVICE_NAME;
	}

	public static void register(final ClassLoader classLoader) {
		Class<?> ActivityManagerService = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", classLoader);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			XposedBridge.hookAllConstructors(ActivityManagerService, new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					register(classLoader, (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"));
				}
			});
		} else {
			XposedBridge.hookAllMethods(ActivityManagerService, "main", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					register(classLoader, (Context) param.getResult());
				}
			});
		}

		XposedBridge.hookAllMethods(ActivityManagerService, "systemReady", new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(MethodHookParam param) throws Throwable {
				mCustomService.systemReady();
			}
		});
	}

	private static void register(final ClassLoader classLoader, Context context) {
		mCustomService = new CustomService(context);

		Class<?> ServiceManager = XposedHelpers.findClass("android.os.ServiceManager", classLoader);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
			XposedHelpers.callStaticMethod(
					ServiceManager,
					"addService",
					getServiceName(),
					mCustomService,
					true
			);
		} else {
			XposedHelpers.callStaticMethod(
					ServiceManager,
					"addService",
					getServiceName(),
					mCustomService
			);
		}
	}

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

参考资料: