——本文转载自 Android插件化原理解析——Hook机制之AMS&PMS 这一系列的文章实在是写的好!
1, 概述
ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道:
1.startActivity最终调用了AMS的startActivity系列方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;
2.startService,bindService最终调用到AMS的startService和bindService方法;
3.动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)
4.getContentResolver最终从AMS的getContentProvider获取到ContentProvider.
而PMS则完成了诸如权限校捡(checkPermission,checkUidPermission),Apk meta信息获取(getApplicationInfo等),
四大组件信息获取(query系列方法)等重要功能。AMS和PMS就是以Binder方式提供给应用程序使用的系统服务,
理论上也可以采用这种方式Hook掉它。但是由于这两者使用得如此频繁,Framework给他了一些“特别优待”,
这也给了相对于Binder Hook更加稳定可靠的hook方式。
阅读本文之前,可以先clone一份understand-plugin-framework,参考此项目的ams-pms-hook 模块。本编文章的源码基于android 6.0.
2, AMS获取过程
使用startActivity有两种形式:
1.直接调用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag。
2.调用被Activity类重载过的startActivity方法,通常在的Activity中直接调用这个方法就是这种形式;
2.1 startActivity
ContextWrapper的startActivity方法如下,
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
最终使用了ContextImpl里面的方法,代码如下:
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
代码相当简单;知道了两件事:
1.其一,知道了在Service等非Activity的Context里面启动Activity为什么需要添加FLAG_ACTIVITY_NEW_TASK;
2.其二,真正的startActivity使用了Instrumentation类的execStartActivity方法;继续跟踪:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// ... 省略无关代码
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
// ----------------look here!!!!!!!!!!!!!!!!!!!
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
到这里发现真正调用的是ActivityManagerNative的startActivity方法;
2.2 Activity.startActivity
Activity类的startActivity方法相比Context而言直观了很多;这个startActivity通过若干次调用
辗转到达startActivityForResult这个方法,在这个方法内部有如下代码:
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
可以看到,其实通过Activity和ContextImpl类启动Activity并无本质不同,
他都通过Instrumentation
这个辅助类调用到了ActivityManagerNative
的方法。
3.Hook AMS
其实startActivity最终通过ActivityManagerNative这个方法远程调用了AMS的startActivity方法。
那么这个ActivityManagerNative是什么呢?
ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;
每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。
继续看ActivityManagerNative的getDefault()方法做了什么:
static public IActivityManager getDefault() {
return gDefault.get();
}
gDefault这个静态变量的定义如下:
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity
IActivityManager am = asInterface(
return am;
}
};
由于整个Framework与AMS打交道是如此频繁,framework使用了一个单例把这个AMS的代理对象保存了起来;
这样只要需要与AMS进行IPC调用,获取这个单例即可。这是AMS这个系统服务与其他普通服务的不同之处,
也是不通过Binder Hook的原因——只需要简单地Hook掉这个单例即可。
这里还有一点小麻烦:Android不同版本之间对于如何保存这个单例的代理对象是不同的;
Android 2.x系统直接使用了一个简单的静态变量存储,Android4.x以上抽象出了一个Singleton类;
以6.0的代码为例说明如何Hook掉AMS,
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
// 获取 gDefault 这个字段, 想办法替换它
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// 6.0的gDefault是一个 android.util.Singleton对象; 取出这个单例里面的字段
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
Object rawIActivityManager = mInstanceField.get(gDefault);
// 创建一个这个对象的代理对象, 然后替换这个字段, 让的代理对象帮忙干活
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerInterface },
new IActivityManagerHandler(rawIActivityManager));
mInstanceField.set(gDefault, proxy);
4. PMS获取过程
PMS的获取也是通过Context完成的,具体就是getPackageManager这个方法;
姑且当作已经知道了Context的实现在ContextImpl类里面,直奔ContextImpl类的getPackageManager方法:
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
可以看到,这里干了两件事:
1.真正的PMS的代理对象在ActivityThread类里面
2.ContextImpl通过ApplicationPackageManager对它还进行了一层包装
继续查看ActivityThread类的getPackageManager方法,源码如下:
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
sPackageManager = IPackageManager.Stub.asInterface(b);
return sPackageManager;
}
可以看到,和AMS一样,PMS的Binder代理对象也是一个全局变量存放在一个静态字段中;可以如法炮制,Hook掉PMS。
现在的目的很明切,如果需要Hook PMS有两个地方需要Hook掉:
1.ActivityThread的静态字段sPackageManager
2.通过Context类的getPackageManager方法获取到的ApplicationPackageManager对象里面的mPM字段。
5. Hook PMS
现在使用代理Hook应该是轻车熟路了吧,通过上面的分析,Hook两个地方;代码信手拈来:
// 获取全局的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 获取ActivityThread里面原始的 sPackageManager
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object sPackageManager = sPackageManagerField.get(currentActivityThread);
// 准备好代理对象, 用来替换原始的对象
Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
new Class<?>[] { iPackageManagerInterface },
new HookHandler(sPackageManager));
// 1. 替换掉ActivityThread里面的 sPackageManager 字段
sPackageManagerField.set(currentActivityThread, proxy);
// 2. 替换 ApplicationPackageManager里面的 mPM对象
PackageManager pm = context.getPackageManager();
Field mPmField = pm.getClass().getDeclaredField("mPM");
mPmField.setAccessible(true);
mPmField.set(pm, proxy);
Context的实现类里面没有使用静态全局变量来保存PMS的代理对象,
而是每拥有一个Context的实例就持有了一个PMS代理对象的引用;
所以这里有个很蛋疼的事情,那就是如果想要完全Hook住PMS,需要精确控制整个进程内部创建的Context对象;
所幸,插件框架中,插件的Activity,Service,ContentProvider,Broadcast等所有使用到Context的地方,
都是由框架控制创建的;因此要小心翼翼地替换掉所有这些对象持有的PMS代理对象。
前面也提到过,静态变量和单例都是良好的Hook点,这里很好地反证了这句话:
想要Hook掉一个实例变量该是多么麻烦! 其实Hook并不是一项神秘的技术;
一个干净,透明的框架少不了AOP,而AOP也少不了Hook。
所讲解的Hook仅仅使用反射和动态代理技术,更加强大的Hook机制可以进行字节码编织。