关于Hook相关知识的学习二

接着前面一篇文章,当Android系统启动一个应用的时候,有一步是对dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载dex文件的时候执行的。这个过程会生成一个odex文件,即Optimised Dex。生成可执行文件odex,保存到data/dalvik-cache目录,最后把apk文件中的dex文件删除。
紧接着解析odex,将odex mmap到内存中( odex – > dexFile结构体),解析的目的是在内存中创建专用的数据结构dexFile来描述odex文件,使虚拟机对odex文件中的各个部分的类数据都是可到达的,为随后的加载某一个类做准备。注意,这个时候只是完成了对odex文件的解析且保存到了内存,但此时odex文件尚未加载到Dalvik虚拟机运行时环境。
接着加载目标类( dexFile --> ClassObject )类的加载的过程就是从内存中读取DexFile结构,并把对应类加载到Dalvik虚拟机中形成
ClassObject的结构对象。ClassObject的成员变量基本上包含了目标类在运行期间所需要用到的全部资源。虚拟机在获取一个类加载指令后,首先确定加载类所属的Dex文件,然后在全局变量中查看虚拟机是否已经完成了对此Dex文件的解析。如果已经完成类的解析,则返回该Dex文件所对应的DexFile数据结构,再根据欲加载类的描述符在DexClassLookup哈希表中查找获取目标类的各个部分数据地址,当得到Dex文件中相关类数据的存储地址后,将通过调用相关的加载函数对指定的各个类信息进行解析并加载,使之以ClassObject类型的数据结构存储于运行时环境中,并为解释器的执行提供相应类方法的字节码。构建ClassObject对象的同时,也构建了这个ClassObject关联的Method。 接着字节码验证器使用dvmVerifyCodeFlow()函数来对加载到DVM虚拟机的代码进行校验,虚拟机调用FindClass()函数查找并装载main()方法类,然后JNIEnv类调用成员函数CallStaticVoidMethod执行main方法,程序开始运行。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ClassObject对象如下:
在这里插入图片描述
由图可知Method分为两种,dalvik虚拟机在处理的时候有区别,一种是directMethod,即Java世界里面实现的方法,一种是virtualMethod(nativeMethods),即在c/c++里面实现的方法。ClassObject里面有两个集合,分别存放了这个Class下定义的directMethods和nativeMethods,DVM通过比对method结构体(method结构体详见上图)中的accessFlags与ACC_NATIVE(常量)是否相等来识别该方法是否是NATIVE方法。
Method结构体中,有两个非常重要的指针:
在这里插入图片描述
对于directMethod,insns存放了该方法的字节码指针(odex被mmap到内存中,这个指针就是这段内存里面指向code区域的开始处的指针)。虚拟机在调用directMethod时,在构建好方法栈以后,pc指针指向了insns,于是可以从内存中取得字节码,然后解释执行。
而在使用NativeMethod时,首先得使用System.loadLibrary对so进行加载,其最终是使用dlopen函数加载了指定的so文件。之后在我们调用nativeMehtod的时候,会根据方法描述符,通过特定的映射关系(是否主动进行了注册会有不同)得到一个native层的函数名,再从之前dlopen获得的句柄中使用dlsys去查找对应的函数,得到了函数指针后,将这个指针赋值给 insns。在nativeFunc这个桥接函数中,将insns解析为函数指针,然后进行调用。
有前面这些知识后,再理解Xposed的hook原理就不难了。
前面提到,一个java方法在虚拟机里面对应的Method为directMethod,其insns指向了字节码位置。Xposed在对java方法进行hook时,先将虚拟机里面这个方法的Method改为nativeMethod(将java方法method结构体中accessFlags标识字段修改为NATIVE方法FLAG,因为当DVM执行到这一函数时会优先执行Native层函数,然后再去执行Java层函数,以此完成函数的hook),然后将该方法的nativeFunc指向自己实现的一个native方法(统一的入口点设置为xposedCallHander),而insns依旧存储该方法真实执行地址(没有注册成Native方法前的执行位置),这样方法在调用时,就会调用到xposed自己实现的native方法,接管了控制权。在这个native方法中,xposed直接调用了一个java方法,这个java方法里面对原方法进行了调用,并在调用前后插入了钩子,于是就hook住了这个方法。最后返回原zygote中完成原本zygote需要做的工作。
Xposed的hook原理就是这么简单,但它有其他的问题要解决:如何将hook的代码注入到目标app的进程中?Xposed的实现是依赖与root,重写android的zygote代码,加入自身的加载逻辑。zygote是android 系统最最初运行的程序,之后的进程都是通过它fork出来的。 于是zygote中加载的代码,在所有fork出来的子进程都含有(app进程也是fork出来的)。
详细过程如下所述:
Xposed由于修改了app_process程序,在执行第一个java程序(com.android.internal.os.ZygoteInit)之前进行截获,改变执行流程,所以启动的是Xposed版的zygote进程,接着进入到自身的main函数体内,这部分java层代码都写在了XposedBridge.jar中。执行的入口点在XposedBridge.jar包中的XposedBridge.java。main函数执行体内主要完成了下面四个部分功能,我们来逐条研究:
在这里插入图片描述
1.initNative()
为了方便Xpsoed框架的native方法对上层java方法的调用,在该部分对相关native方法进行了初始化工作。刚才提到的native层方法xposedCallHander回调的handleHookedMethod真正实现体就放在java层。同时将invokeOriginalMethodNative注册为本地方法,初始化了hook资源文件时用到的对象等。
2.initXbridgeZygote()
主要hook了几个涉及到应用进程创建、启动的关键类。新的应用进程创建时会调用这几个类的特定方法(调用其中的一种,与应用进程创建模式有关系),Xposed框架通过截获应用进程创建时的相关信息来决定对其处理逻辑(是否加载开发人员所写的hook模块,有感兴趣的应用,如微信、相机等创建时,去注册hook模块中所指定的相关方法为本地方法)。
3.loadModules
下面在来看看main函数体内做的第三件重要的事情,读取系统中放置的hook模块。简单的说就是把hook模块随便找个地方丢进去,然后把该模块的路径写进conf/modules.list中就可以了。
•读取“conf/modules.list”文件中写入的apk名称。
•从每一个apk中读取其“/assets/xposed_init”文件中声明的hook主模块类的名称。
•classLoader加载各hook主模块类。
•判断加载的类属于哪一种类别,根据类别调用不同的处理方法,常用的IXposedHookLoadPackage。
4.Xposed框架加载完毕后执行将执行权还给com.android.internal.os.ZygoteInit,完成正常的系统启动流程。Xposed框架加载完毕,系统正常启动。

最后,简单总结一下,Xposed框架执行流程:

•创建新应用,获取包名等信息。
•调用XC_LoadPackage.callAll,依次执行各hook模块的代码。
•如果有包名匹配的hook模块,则注册模块中要hook的方法为本地方法。
•当该方被调用的时候,转移到本地xposedCallHandler。
•xposedCallHandler回调上层handlerHookedMethod(因为加载的hook模块代码,一些变量都存储在java层)。
•handlerHookedMethod执行加载的各hook模块。

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/103755998
今日推荐