android 插件化机制之AMS&PMS

——本文转载自  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机制可以进行字节码编织。

    原文作者:Achillisjack
    原文地址: https://blog.csdn.net/u012439416/article/details/70665923
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞