Xposed、Dexposed、Epic对比总结:

1、Xposed框架是需要root的,他的功能很全,能够hook掉系统方法,同时也可以hook掉其他应用的一些方法。

2、Dexposed框架是不需要root的,但是他只能hook掉在自己的应用进程中的一些方法,其他应用进程是没办法hook的。

3、Epic是基于Dexposed进行的修改,支持art虚拟机上面的Hook。

一、Xposed原理:

 

Android 系统是基于 Linux 的,其第一个由内核启动的用户进程是 init 进程。init 进程随后会创建 zygote 进程,Android 应用程序进程都是由 zygote 进程孵化而来。zygote 所对应的可执行程序是 app_process,xposed 框架通过替换系统的 app_process 可执行文件以及虚拟机动态链接库,让 zygote 在启动应用程序进程时注入框架代码,进而实现对应用程序进程的劫持。

设备root之后,system/bin/下面的文件替换了app_process等文件,app_process就是zygote进程文件。所以Xposed通过替换zygote进程实现了控制手机上所有app进程。因为所有app进程都是由Zygote fork出来的。使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持。

Xposed的基本原理是修改了ART/Davilk虚拟机,将需要hook的函数注册为Native层函数。当执行到这一函数时虚拟机会优先执行Native层函数,然后再去执行Java层函数,这样完成函数的hook。

EnableXposedHook 方法的实现,代码位于 android_art/runtime/art_method.c,这里涉及较深入的 ART 虚拟机知识,大致可以这么理解这一段代码的意思,首先拿到 Hook 对象方法的执行地址,在此地址上替换成增加了 before 以及 after 实现的方法,让 Hook 对象方法运行时,可以按顺序执行 before、原方法以及 after。

Xposed RPC 返回JOSN_sed

通过读Xposed源码发现其启动过程:

1、手机启动时init进程会启动zygote这个进程。由于zygote进程文件app_process已被替换,所以启动的时Xposed版的zygote进程。

2、Xposed_zygote进程启动后会初始化一些so文件(system/lib system/lib64),然后进入XposedBridge.jar中的XposedBridge.main中初始化jar包完成对一些关键Android系统函数的hook。

3、Hook则是利用修改过的虚拟机将函数注册为native函数。

4、然后再返回zygote中完成原本zygote需要做的工作。

这只是在宏观层面稍微介绍了下Xposed,要想详细了解需要读它的源码了。下面两篇写的挺好,要想深入理

那么这里就引出了一个问题,为什么要替换app_process程序呢?

Android 系统是基于 Linux 的,其第一个由内核启动的用户进程是 init 进程。init 进程随后会创建 zygote 进程,Android 应用程序进程都是由 zygote 进程孵化而来。zygote 所对应的可执行程序是 app_process,xposed 框架通过替换系统的 app_process 可执行文件以及虚拟机动态链接库,让 zygote 在启动应用程序进程时注入框架代码,进而实现对应用程序进程的劫持。

每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。这也是Xposed选择替换app_process的原因。

Zygote进程在启动的过程中,除了会创建一个Dalvik虚拟机实例之外,还会将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法来前面创建的Dalvik虚拟机实例中去。注意,一个应用程序进程被Zygote进程孵化出来的时候,不仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库。这也就是可以将XposedBridge这个jar包加载到每一个Android应用程序中的原因。

XposedBridge有一个私有的Native(JNI)方法hookMethodNative,这个方法也在app_process中使用。这个函数提供一个方法对象利用Java的Reflection机制来对内置方法覆写。

综上就是Xposed框架选择app_proess作为入口的一个原因,因为这个入口有两个好处:

1、一旦修改了,就可以修改了所有的app

2、这里的时机是最早的,可以加载所有的东西

二、Dexposed原理

前置知识点:修改非native方法为native方法

如何将Android中一个非native方法改成native的,并且可以将这个native方法指定成特定执行的函数。要对一个java函数进行hook需要步骤有
[1] 把修改method的属性修改成native
[2] 修改method的registersSize、insSize、nativeFunc、computeJniArgInfo
[3] RegisterNatives注册目标method的native函数

 

这里给出大致流程:

1. 调用DexposedBridge.findAndHookMethod,进入到DexposedBridge类(Java层逻辑)
2. 通过XposedHelpers.findMethodExact找到要hook的java方法,然后再用hookMethod进行真正的hook。
3. hookMethod方法先把hook成功后的callback、要hook的方法的参数和返回值类型保存到AdditionalHookInfo中,把它作为参数传给hookMethodNative。hookMethodNative是一个native方法,它的第3个参数slot表示该Method在class的方法表中所处的位置,在native的实现中会用到这个slot。
4. hookMethod 方法中调用hookMethodNative,执行Native层方法。
6. com_taobao_android_dexposed_DexposedBridge_hookMethodNative (JNI入口),把Java层传递的信息构造成DexposedInfo信息,然后设置hook方法accessFlags设置为ACC_NATIVE ,即native方法,并且指定nativeFunc函数 ( 为什么会这么做: method的结构体表示了一个Java层函数, 而其中的accessFlags属性如果是ACC_NATIVE , 则dvm在调用原Java函数的时候, 会转向调用属性nativeFunc 所指向的函数)。
7. native层用dvm的各种函数来操作Method的指针和对象来控制函数,最后通过反射调用handleHookedMethod回到Java层的方法handleHookedMethod,回到Java世界。
 

主要分为Java层和Native层的,通过下图一目了然:

Xposed RPC 返回JOSN_sed_02

一、Java层

1)需要hook的方法对象

2)需要hook的方法所属的类

3)额外附加信息,比如我们需要hook方法的回调的对象

4)handleHookedMethod方法,这个方法是整个Dexposed框架Java层最核心的一个方法,这个方法就是用来替换我们需要hook的那个方法,具体如何替换的等下面说到native层再说。然后这个方法中做了三件事:

1、执行需要hook之前的所有回调方法beforeMethod

2、执行被hook的原生方法

3、执行需要hook之后的所有回调方法afterMethod

二、native层的实现 dexposed.cpp

在com_taobao_android_dexposed_DexposedBridge_hookMethodNative函数中主要做了两件事:

        1)把Java层传递的信息构造成DexposedInfo信息

        2)设置hook方法为native,并且指定nativeFunc函数

最后在执行第二步中的nativeFunc函数dexposedCallHandler函数中主要做了两件事:

        1)获取刚刚构造的DexposedInfo信息

        2)调用Java层DexposedBridge.java中的handleHookedMethod方法

 

ART虚拟机方法调用原理:

在ART中,每一个Java方法在虚拟机内部都由一个ArtMethod对象表示(native层,实际上是一个C++对象),这个native 的 ArtMethod对象包含了此Java方法的所有信息,比如名字,参数类型,方法本身代码的入口地址(entrypoint)等,一个Java方法的执行非常简单:

1、想办法拿到这个Java方法所代表的ArtMethod对象

2、取出其entrypoint,然后跳转到此处开始执行

ART有什么特别的?

为什么Dexposed能够在Dalvik上为所欲为,到ART时代就不行了呢?排除其他非技术因素来讲,ART确实比Dalvik复杂太多;更要命的是,从Android L到Android O,每一个Android版本中的ART变化都是天翻地覆的,大致列举一下:

  • Android L(5.0/5.1) 上的ART是在Dalvik上的JIT编译器魔改过来的,这个编译器会做一定程度的方法内联,因此很多基于入口替换的Hook方式一上来就跪了。
  • Android M(6.0) 上的ART编译器完全重新实现了:寄存器分配方式改变。且不说之前在Android L上的Hook实现要在M上重新做一遍,这个编译器的寄存器分配比quick好太多,结果就是hook实现的时候你要是乱在栈或者寄存器上放东西,代码很容易就跑飞。
  • Android O(8.0) Android O的Runtime做了很多优化,传统Java VM有的一些优化手段都已经实现,比如类层次分析,循环优化,向量化等;除此之外,DexCache被删除,跨dex方法内联以及Concurrent compacting GC的引入,使得Hook技术变的扑朔迷离。