Xposed 源码剖析(一)
0x00 简介
-
是什么: Xposed framework是一个基于Android系统实现的能够给用户提供修改系统层面或第三方APP功能的框架服务。
-
如何工作: Android中有一个叫做Zygote的核心进程,它会随Android系统的启动而启动,然后加载系统所需的类,最后再调用初始化方法。每一个APP的进程都是从Zygote进程fork出的子进程,这个进程的文件是/system/bin/app_process。当安装Xposed framework后,Xposed framework会替换一个新的app_process至/system/bin/中,同时还会替换虚拟机和其他若干文件。Zygote启动时,会加载Xposed所需的JAR包(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar)至系统目录,并启动Xposed替换的虚拟机。 Xposed framework的主要接口由XposedBridge.jar提供,框架的核心功能在替换的虚拟机中实现。
0x01 示例代码
用户可以使用Xposed framework去Hook方法,下面是作者本人给出的一个示例:
这个示例代码实现了对Andoird系统时钟输出样子和颜色的修改,完整的源码地址是https://github.com/rovo89/XposedExamples/tree/master/RedClock。如此简单的几行代码,它的内部实现机制是怎么的呢?想知道这些,最好的办法当然是分析源码。
0x02 Hook流程分析
这里使用Xposed framework
Hook
java.net.URLEncoder
类的encode
方法作为例子分析,调用逻辑如下所示:
handleLoadPackage
会在任意一个APP加载的时候被调用,然后就可以在这个接口内部根据包名做逻辑判断,如果是Hook
方法所在的包,则进一步调用findAndHookMethod
去Hook
指定的方法。 通过findAndHookMethod()
接口找到指定方法并进行Hook
。它的参数较多,第一个参数是类名,第二个参数是类加载器,第三个参数是方法名,后面是可变参数,把方法的参数类型依次作为参数,最后一个参数是XC_MethodHook()
对象。另外,在其内部重写beforeHookedMethod
和afterHookedMethod
方法,这两个方法分别会在Hook
方法执行的前后执行,用户可以在其中实现自己的逻辑。 findAndHookMethod
这个接口的实现在XposedHelpers.java
这个文件中,代码如下:
首先,接口内部会根据类名和类加载器查找Class
对象,再根据Class
对象和方法名去查找Method
对象,最后调用XposedBridge.hookMethod
完成对方法的Hook
。 看一下方法查找的实现:
在findMethodExact
内部,fullMethodName
变量就是这个方法的完全表示名,形如java.net.URLEncoder#encode(java.lang.String,java.lang.String)#exact
。使用方法的完全表示名在methodCache表中查询,如果这个
Method对象在表中就从表中获取并返回,如果不在就创建一个新的
Method```对象放入表中并返回。
进一步看一下XposedBridge.hookMethod
方法的实现,代码片段如下:
这里的逻辑比较清晰,如果这个方法不存在对应的回调对象集合,那么就创建一个并把回调对象放入这个集合中。然后如果是新Hook
的方法,获取这个方法的参数和返回类型等信息作为参数去调用hookMethodNative
方法,这是一个native
方法,它注册的地方在libXposed_common.cpp
中,注册逻辑如下:
NATIVE_METHOD
是一个宏定义,根据这个宏定义,进一步找到hookMethodNative
对应的native
方法是XposedBridge_hookMethodNative
。这里以art
为例,这个方法的实现在libXposed_art.cpp
文件中,代码如下:
通过FromReflectedMethod
获取要Hook
方法对应的ArtMethod
对象,然后再调用EnableXposedHook
接口。在art
虚拟机中,每一个加载的类方法都有一个对应的ArtMethod
对象,它的实现在ArtMethod.cc
中,下面是EnableXposedHook
方法的代码实现:
这部分代码是关键所在,首先创建一个备份的ArtMethod
,并添加访问标志位kAccXposedOriginalMethod
,表示其为Hook
方法的原方法,然后为备份的ArtMethod
创建对应的Method
对象。把Method
对象、方法额外信息和原始方法保存至XposedHookInfo
结构体中,并调用SetEntryPointFromJni()
把这个结构体变量的内存地址保存在ArtMethod
对象中。 这个位置原本是用来保存native
方法的入口地址的,既然使用了这个位置,那么就必须把对应的标志位清除,代码实现的最后调用SetAccessFlags((GetAccessFlags() & ~kAccNative & ~kAccSynchronized) | kAccXposedHookedMethod)
来完成标志位的清除。这并不会有问题,此时这个ArtMethod
对象对应是Hook
后的方法,这个方法的实现不是native
的。 接着看下面的代码逻辑,需要结合StackReplaceMethod()
的实现来分析:
它实现的功能是挂起所有线程并在每一个线程中查找是否存在这个修改后的ArtMethod
对象,有就替换成未修改前的ArtMethod
对象。
再回到EnableXposedHook
中,接着调用SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler())
和SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge)分别设置机器指令和解释器的入口地址。从解释器入口进入之后会调用artInterpreterToCompiledCodeBridge
,下面是artInterpreterToCompiledCodeBridge
代码片段:
前面的代码逻辑都先不用关注,看最后一行调用了ArtMehod
的Invoke
方法,这正是机器指令执行的路径。所以说,当被Hook
的方法被调用时,不管是机器指令还是解释器执行的,都会进入ArtMethod::Invoke()
中,下面是它的代码片段:
ArtMethod::Invoke()
会进一步调用art_quick_invoke_stub
,art_quick_invoke_stub
的内部实现是汇编语言的,art_quick_invoke_stub
与具体的机器架构相关,这里以arm
架构为例:
汇编指令blx ip
跳转到entry_point_from_compiled_code_
指定的地址,这个地址就是通过SetEntryPointFromQuickCompiledCode()
函数设置的,可知此时执行流跳入到GetQuickProxyInvokeHandler()
的地址中,即artQuickProxyInvokeHandler
:
这个代理方法会判断当前是否是xposed
环境
如果是就调用InvokeXposedHandleHookedMethod()
方法。
这个方法的内部实现是,先获取保存在ArtMethod
对象中的XposedHookInfo
数据,然后通过CallStaticObjectMethodA
接口调用XposedBridge.handleHookedMethod()
方法,注意此时已经从native
中跳入到JAVA
里了。继续看XposedBridge.handleHookedMethod()
方法的实现:
进入handleHookedMethod()
后,首先调用所有before method
的回调方法,然后调用原始方法,最后调用所有after method
的回调方法。到这里就可以看出跟之前应用层的代码形成的对应关系。
Xposed framework
通过invokeOriginalMethodNative
方法去调用原方法,其对应的native
方法如下:
InvokeMethod()
的实现在Reflection.cc
中,代码如下所示:
通过FromReflectedMethod()
获取到Method
对象对应的ArtMethod
对象,这个就是在Hook
时创建的原始ArtMethod
对象的备份。然后,再通过InvokeWithArgArray
调用原始的方法。
0x03 总结
至此,完成了对Xposed framework Hook
的源码分析。我们再整体看一下它的流程,当Hook
一个方法时,首先需要提供方法的所在的包名、方法名、参数类型以及回调对象去调用Hook
接口;Xposed
在art
虚拟机中找到方法对应的ArtMethod
对象,备份这个ArtMethod
对象,然后修改这个对象的解释器和机器指令入口,关键所在。然后把每个线程中加载的这个被修改后的ArtMethod
对象替换成原始备份的ArtMethod
对象。最后在修改的机器指令入口地址指向的函数里,实现回调方法和原始方法的调用。
很多细节还没有涉及
下一期会从/system/bin/app_process
的实现着手分析其框架部分代码的实现。
始发于微信公众号:同程研发中心