接上篇,这篇来看一下Droid Plugin的hook机制。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在初始化的时候进行填充。
紧接着调用HookedMethodHandler的doHookInner()方法:
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:“劫持”所有IActivityManager的API
- IActivityManagerHookHandle:安装所有被“劫持”的API的处理对象,加入到Map中
- IActivityManagerHookHandle.startActivity:这是一个内部类,专门处理startActivity()方法。以此类推,还有startActivityAsUser类、startActivityAsCall类等等,每个API方法都对应一个处理类。
最后一个问题:这些hook是如何被安装到系统上的?其实就是用了上一篇提到的方法,利用反射替换掉静态单例对象。举个例子,我们看一下IActivityManagerHook的onInstall()方法:
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()创建一个代理对象,最后用这个代理对象替换掉之前的gDefault(gDefault变成了mOldObj)。 IActivityManagerHook是这个代理对象的InvocationHandler,因此所有的API调用都会走到它的invoke()方法中来。这样所有路径就都打通了。
二、Binder代理绕过部分系统服务限制
这部分实现主要在hook/binder/和hook/handle里。
Binder代理其实基本和之前差不多,稍微有些差别。还是先上一张类图:
这里出现了3个新面孔,我们先看MyServiceManager,这个类包含了3个Map:
- mOriginServiceCache:这里存储的是原始的service cache。每个ActivityThread在bindApplication()的时候,会从ServiceManager那边获得一个service cache,每次要和某个service通信时,会先检查这个cache里有没有代理对象,如果有的话就直接用,不需要再和ServiceManager进行一次binder交互了。
- mProxiedServiceCache:这里存储的就是service cache的代理对象了,因为我们要“劫持”这些binder调用,所以必须把service cache也替换成我们的代理对象,每次调用都会走进ServiceManagerCacheBinderHook对象的invoke()方法。
- mProxiedObjCache:这里存储的是所有的service代理对象,那原始的service对象放在哪里呢?在BinderHook的mOldObj里。
第2个类ServiceManagerCacheBinderHook主要就是来替换掉service cache对象的。看一下ServiceManagerCacheBinderHook的onInstall()方法:
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,主要是用来hook住getService()和checkService()这两个API。如果这两个API被调用,并且在mProxiedObjCache发现有对应的代理对象,则直接返回这个代理对象,参见它里面的ServiceManagerHook的afterInvoke()方法(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。
然后对比一下ProxyHook和BinderHook,发现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); }
先调用ServiceManagerCacheBinderHook的onInstall()方法更新一下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
最后再提一个特殊类型的hook:InstrumentationHook。这个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机制部分基本就分析完了,下一篇分析占坑部分。