DroidPlugin代码分析(二) Hook机制

接上篇,这篇来看一下Droid Pluginhook机制。Droid Plugin的官方文档提到了下面三点:

  • 动态代理实现函数hook
  • Binder代理绕过部分系统服务限制
  • IO重定向

我们一项一项地来看。

一、动态代理实现函数hook

这部分实现主要在hook/proxy/hook/handle里。先上一张类图:


首先定义了一个基类Hook,这是一个抽象类,外部可以通过setEnable()方法来使能或者关闭该hook。同时它还声明了和install相关的方法,子类可以覆盖这些方法完成相应的初始化。

ProxyHook继承自Hook,同时还实现了InvocationHandler接口。它有一个setOldObj()方法,用来保存将被代理的原始对象。另外,既然实现了InvocationHandler接口,必然要实现其invoke()方法:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            return method.invoke(mOldObj, args);
        } 
        ... ...
    }

可以看到,这里没有直接在invoke()方法里进行任何处理,而是先通过mHookHandles获取了一个HookedMethodHandler对象。

mHookHandles是一个BaseHookHandle对象,内部包含了一个Map,可以根据API名映射到对应对应的HookedMethodHandler对象。这个Map由其子类IXXXHookHandle在初始化的时候进行填充。

紧接着调用HookedMethodHandlerdoHookInner()方法:

    public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
        try {
            ... ...
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
            afterInvoke(receiver, method, args, invokeResult);
        .... ...
    }

这里其实就跟上一篇的例子一样,在调用实际方法之前,先调用一个beforeInvoke()方法进行一些处理,然后在实际方法调用结束后,再调用afterInvoke()方法进行另外一些处理。需要注意的是,如果beforeInvoke()返回true,那么实际要调用的方法根本不会被执行,直接变被skip掉了。

HookedMethodHandler的子类主要就是覆盖beforeInvoke()afterInvoke()这两个方法,通过修改传入参数达到“瞒上”的目的,通过修改返回值达到“欺下”的目的。但是翻看代码会发现,这些子类一般不直接继承HookedMethodHandler,而是继承自一个叫做ReplaceCallingPackageHookedMethodHandler的类。这个类覆盖了beforeInvoke()方法:

    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            if (args != null && args.length > 0) {
                for (int index = 0; index < args.length; index++) {
                    if (args[index] != null && (args[index] instanceof String)) {
                        String str = ((String) args[index]);
                        if (isPackagePlugin(str)) {
                            args[index] = mHostContext.getPackageName();
                        }
                    }
                }
            }
        }
        return super.beforeInvoke(receiver, method, args);
    }

可以看到,如果发现是插件程序的包名的话,会统一替换成宿主(host)的包名。为什么需要这样一层转换呢?原来当我们的app调用一些系统API的时候,都会到AppOpsService那边进行鉴权,AppOpsService会判断当前的uid和包名是不是匹配,如果不匹配就会抛一个“Bad call”的SecurityException(具体参见getOpsRawLocked()方法)。我们启动插件的时候,uid是宿主apk,包名是插件apk,显然是不匹配的。因此经过这层转换以后,我们就可以“欺骗”系统,让其以为是宿主apk调过来的。当然,这样做也是有副作用的,宿主apk必须把所有插件apk需要的权限全都申请上,因为系统只会去检查宿主apk。所以你查看AndroidManifest.xml的时候会发现几乎app能申请的所有权限都被申请了。。。

到此,hook的原理就搞清楚了。以IActivityManager为例:

  • IActivityManagerHook:“劫持”所有IActivityManagerAPI
  • IActivityManagerHookHandle:安装所有被“劫持”的API的处理对象,加入到Map
  • IActivityManagerHookHandle.startActivity:这是一个内部类,专门处理startActivity()方法。以此类推,还有startActivityAsUser类、startActivityAsCall类等等,每个API方法都对应一个处理类。

最后一个问题:这些hook是如何被安装到系统上的?其实就是用了上一篇提到的方法,利用反射替换掉静态单例对象。举个例子,我们看一下IActivityManagerHookonInstall()方法:

    public void onInstall(ClassLoader classLoader) throws Throwable {
        Class cls = ActivityManagerNativeCompat.Class();
        Object obj = FieldUtils.readStaticField(cls, "gDefault");
        if (obj == null) {
            ActivityManagerNativeCompat.getDefault();
            obj = FieldUtils.readStaticField(cls, "gDefault");
        }

        if (IActivityManagerCompat.isIActivityManager(obj)) {
            setOldObj(obj);
            Class<?> objClass = mOldObj.getClass();
            List<Class<?>> interfaces = Utils.getAllInterfaces(objClass);
            Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
            Object proxiedActivityManager = MyProxy.newProxyInstance(objClass.getClassLoader(), ifs, this);
            FieldUtils.writeStaticField(cls, "gDefault", proxiedActivityManager);
            Log.i(TAG, "Install ActivityManager Hook 1 old=%s,new=%s", mOldObj, proxiedActivityManager);
        }
        ... ...
    }

很明显,首先获取全局的IActivityManager对象gDefault,然后通过Proxy.newProxyInstance()创建一个代理对象,最后用这个代理对象替换掉之前的gDefaultgDefault变成了mOldObj)。 IActivityManagerHook是这个代理对象的InvocationHandler,因此所有的API调用都会走到它的invoke()方法中来。这样所有路径就都打通了。


二、Binder代理绕过部分系统服务限制

这部分实现主要在hook/binder/hook/handle里。

Binder代理其实基本和之前差不多,稍微有些差别。还是先上一张类图:



这里出现了3个新面孔,我们先看MyServiceManager,这个类包含了3Map

  • mOriginServiceCache:这里存储的是原始的service cache。每个ActivityThreadbindApplication()的时候,会从ServiceManager那边获得一个service cache,每次要和某个service通信时,会先检查这个cache里有没有代理对象,如果有的话就直接用,不需要再和ServiceManager进行一次binder交互了。
  • mProxiedServiceCache:这里存储的就是service cache的代理对象了,因为我们要“劫持”这些binder调用,所以必须把service cache也替换成我们的代理对象,每次调用都会走进ServiceManagerCacheBinderHook对象的invoke()方法。
  • mProxiedObjCache:这里存储的是所有的service代理对象,那原始的service对象放在哪里呢?在BinderHookmOldObj里。

2个类ServiceManagerCacheBinderHook主要就是来替换掉service cache对象的。看一下ServiceManagerCacheBinderHookonInstall()方法:

    protected void onInstall(ClassLoader classLoader) throws Throwable {
        Object sCacheObj = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sCache");
        if (sCacheObj instanceof Map) {
            Map sCache = (Map) sCacheObj;
            Object Obj = sCache.get(mServiceName);
            if (Obj != null && false) {
                throw new RuntimeException("Can not install binder hook for " + mServiceName);
            } else {
                sCache.remove(mServiceName);
                IBinder mServiceIBinder = ServiceManagerCompat.getService(mServiceName);
                if (mServiceIBinder != null) {
                    MyServiceManager.addOriginService(mServiceName, mServiceIBinder);
                    Class clazz = mServiceIBinder.getClass();
                    List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                    Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                    IBinder mProxyServiceIBinder = (IBinder) MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
                    sCache.put(mServiceName, mProxyServiceIBinder);
                    MyServiceManager.addProxiedServiceCache(mServiceName, mProxyServiceIBinder);
                }
            }
        }
    }

首先把原始的service cache存起来,然后生成一个代理对象并通过反射替换掉cache中的对象,最后把这个代理对象也存起来。下次如果要真正和service进行通信,通过getOriginService()把原始的service cache拿出来用就行了。

3个类ServiceManagerBinderHook继承自ProxyHook,主要是用来hookgetService()checkService()这两个API。如果这两个API被调用,并且在mProxiedObjCache发现有对应的代理对象,则直接返回这个代理对象,参见它里面的ServiceManagerHookafterInvoke()方法(setFakedResult()会导致API的返回值被替换成proxiedObj):

            protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
                int index = 0;
                if (args != null && args.length > index && args[index] instanceof String) {
                    String servicename = ((String) args[index]);
                    Object proxiedObj = MyServiceManager.getProxiedObj(servicename);
                    if (proxiedObj != null) {
                        setFakedResult(proxiedObj);
                    }
                }
                Log.e("ServiceManagerBinderHook", "%s(%s)=%s", method.getName(), Arrays.toString(args), invokeResult);
                super.afterInvoke(receiver, method, args, invokeResult);
            }

看完这3个新面孔,我们来看一下BinderHook。首先BinderHook增加了一个新方法:

public abstract String getServiceName();

所有子类必须实现该方法,这个我们就可以知道需要hook哪个service

然后对比一下ProxyHookBinderHook,发现BinderHook自己已经实现了onInstall()方法,这样它的子类就不必实现该方法了。看一下它的onInstall()方法:

    protected void onInstall(ClassLoader classLoader) throws Throwable {
        new ServiceManagerCacheBinderHook(mHostContext, getServiceName()).onInstall(classLoader);
        mOldObj = getOldObj();
        Class<?> clazz = mOldObj.getClass();
        List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
        Object proxiedObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
        MyServiceManager.addProxiedObj(getServiceName(), proxiedObj);
    }

先调用ServiceManagerCacheBinderHookonInstall()方法更新一下service cache,然后生成一个新的代理对象放到mProxiedObjCache里。这样下次不管是从cache里取,还是直接通过binder调用,就都会返回我们的代理对象。

至此,Binder代理就分析完了。


三、IO重定向

IO重定向”,听起来是不是很高大上?其实所谓的“重定向”不过就是替换一下要访问的路径。插件程序的所有数据都是放在宿主程序的目录下的,方便统一管理。因此,当插件访问“/data/data/插件包名/xxx”时,需要把路径替换成“/data/data/插件宿主包名/Plugin/插件包名/data/插件包名/xxx”。

具体是实现在LibCoreHookHandle里,libcore主要是一些系统调用的实现(如open(), remove(), mkdir()等等),因此需要在进行系统调用之前把路径替换掉。看一下里面的BaseLibCore类的beforeInvoke()方法:

private abstract static class BaseLibCore extends HookedMethodHandler {
        protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
            int index = 0;
            replace(args, index);
            return super.beforeInvoke(receiver, method, args);
        }
}

这里调用了一个replace()方法:

        protected void replace(Object[] args, int index) {
            if (args != null && args.length > index && args[index] instanceof String) {
                String path = (String) args[index];
                String newPath = tryReplacePath(path);
                if (newPath != null) {
                    args[index] = newPath;
                }
            }
        }

遍历所有的参数,依次调用tryReplacePath()方法:

        private String tryReplacePath(String tarDir) {
            if (tarDir != null && tarDir.length() > mDataDir.length() && !TextUtils.equals(tarDir, mDataDir) && tarDir.startsWith(mDataDir)) {
                if (!tarDir.startsWith(mHostDataDir) && !TextUtils.equals(tarDir, mHostDataDir)) {
                    String pkg = tarDir.substring(mDataDir.length() + 1);
                    int index = pkg.indexOf("/");
                    if (index > 0) {
                        pkg = pkg.substring(0, index);
                    }
                    if (!TextUtils.equals(pkg, mHostPkg)) {
                        tarDir = tarDir.replace(pkg, String.format("%s/Plugin/%s/data/%s", mHostPkg, pkg, pkg));
                        return tarDir;
                    }
                }
            }
            return null;
        }

这里完成了路径的替换工作,也就完成了“IO重定向”。

四、InstrumentationHook

最后再提一个特殊类型的hookInstrumentationHook。这个hook的目的主要是监控activity的生命周期,拦截一些关键的回调函数。实现也比较简单,直接替换掉全局静态变量mInstrumentation,没有对应的hook handle对象。参见它的onInstall()方法:

    protected void onInstall(ClassLoader classLoader) throws Throwable {

        Object target = ActivityThreadCompat.currentActivityThread();
        Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();

         /*替换ActivityThread.mInstrumentation,拦截组件调度消息*/
        Field mInstrumentationField = FieldUtils.getField(ActivityThreadClass, "mInstrumentation");
        Instrumentation mInstrumentation = (Instrumentation) FieldUtils.readField(mInstrumentationField, target);
        if (!PluginInstrumentation.class.isInstance(mInstrumentation)) {
            PluginInstrumentation pit = new PluginInstrumentation(mHostContext, mInstrumentation);
            pit.setEnable(isEnable());
            mPluginInstrumentations.add(pit);
            FieldUtils.writeField(mInstrumentationField, target, pit);
            Log.i(TAG, "Install Instrumentation Hook old=%s,new=%s", mInstrumentationField, pit);
        } else {
            Log.i(TAG, "Instrumentation has installed,skip");
        }
    }

可以看到,这里把mInstrumentation替换成了一个PluginInstrumentation对象,而mInstrumentation本身成为了PluginInstrumentation对象的一个成员变量mTarget

这个PluginInstrumentation是继承自Instrumentation的,覆盖了父类了下面这些方法。这些方法里会增加一些额外的处理,最终通过mTarget完成系统服务的调用。

  • onActivityCreated()
  • onActivityOnNewIntent()
  • onActivityDestory()
  • callActivityOnCreate()
  • callActivityOnDestroy()
  • callActivityOnNewIntent()
  • callApplicationOnCreate()

除此以外,还有一个PluginCallbackHook,这个hook和后面的占坑部分密切相关,我们留到后面再做分析。

到这里Hook机制部分基本就分析完了,下一篇分析占坑部分。

猜你喜欢

转载自blog.csdn.net/turkeycock/article/details/50959825
今日推荐