最近借助着邓凡平的这篇文章深入理解Android(三):Xposed详解去阅读了一下Xposed的Hook源码,在这里总结一下Xposed使用的一些东西。
Xposed实现原理简述
Xposed实际上是将Android的zygote给替换了,Xposed自己搞了一个zygote,在这个zygote中加载安装的modules,将这些modules hook到指定的方法当中。最核心的功能就是findAndHookMethod这个函数,它找到对应的Method,将XC_Method_Hook(想要添加的Hook信息)与其匹配起来,将Method的访问标志改为native,并且将它的nativeFunc赋值为hookedMethodCallback,使得Java层调用的Method时候,虚拟机会调用native层hookedMethodCallback方法,而hookedMethodCallback方法先调用XC_Method_Hook接口的before方法,然后再调用原来的Method方法,之后再调用after方法,最后返回结果。整个Hook的流程就是这样的,里面每个细节都需要很多知识。下面分别介绍Xposed用到的东西。
Xposed用到的知识
zygote
首先自然就是zygote了,zygote是Android中所有应用程序的父进程,所以应用程序进程都要经过zygote,zygote通过fork创建一个新的应用程序进程。如果替换掉zygote,那么创建新的进程的时候,zygote的很多东西还是可以影响到新的应用程序进程。
JNI
对于这部分,我本来就接触较多,不过zygote中大部分都是使用动态注册JNI函数,env->RegisterNatives
dvm方法
Dalvik虚拟机提供了很多的dvm开头的方法,在xposed/zygote里面使用了很多,它是Dalvik虚拟机提供的方法。
PathClassLoader
用于加载已经安装了的APK的Class。DexClassLoader是能够加载未安装的apk的dex文件。
修改method 访问标志
在native层,虚拟机提供了方法修改Method的访问标志:
SET_METHOD_FLAG(method,ACC_NATIVE)
通过该方法将Method修改为native方法,这样当运行Java层对应的方法的时候,虚拟机会调用nativeFunc。这样就提供了一种hook的契机。
Dalvik native interface
Dalvik提供了两种internative interface。
一种是供应用程序使用的,使用javah生成,或者直接使用env->RegisterNatives注册,这种方式是我们经常在jni开放中使用的,每个参数都会相互对应。
另外一种是类似于这种接口:
void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self)
将这个函数与Java层的native函数对应,参数和返回参数都放在args中,method是对应的Java层方法在native层的对象。这种native interface的方式只能够在Dalvik虚拟机内部使用。一个这样的函数声明,可以对应Java层中任何native方法,这样就可以很方便地将这种函数与Java层的native方法对应了,只要一个方法就够了。
在xposed中,findMethodAndHook的过程中就将对应的Method的nativeFunc赋值为&hookedMethodCallback。这样在执行Java层方法的时候,Dalvik虚拟机就会运行hookedMethodCallback方法了。下面这段代码是修改访问标志和nativeFunc的:
SET_METHOD_FLAG(method, ACC_NATIVE);
method->nativeFunc = &hookedMethodCallback;
method->insns = (const u2*) hookInfo; //这里是把Hook方法也加入到method当中了。这样在hookedMethodCallback中也可以执行添加的hook方法。
method->registersSize = method->insSize;
method->outsSize = 0;
阅读中的疑惑
如何Hook
上面已经介绍了
如何Hook到APK里面的函数的
一开始一直在想Xposed是怎么Hook APK里面的方法,因为他需要影响到不同的进程,我一直觉得是在启动对应的应用程序的时候再去做的工作,但是发现实际上不是的。它是一开始就Hook了对应的方法,在zygote进程中。为什么在之后还是能够使用呢?因为应用程序都是zygote子进程,在fork的时候,父进程load的Class还是会在子进程当中,fork的时候复制了一份。
还有一些正在学习,比如说Dalvik虚拟机的实现。
总结
看源码的时候,一开始了解整个源码的流程,然后在脑子里想它具体是怎么实现的,遇到不知道怎么弄的时候,再去重新看源码,不断思考为什么,这样的过程能够学到很多东西,也能够更好地理解源码。