深入理解Android(三):Xposed详解

编者按:随着移动设备硬件能力的提升,Android系统开放的特质开始显现,各种开发的奇技淫巧、黑科技不断涌现,InfoQ特联合《深入理解Android》系列图书作者邓凡平,开设深入理解Android专栏,探索Android从框架到应用开发的奥秘。

\u0026#xD;\u0026#xD;\u0026#xD;\u0026#xD;

Xposed,大名鼎鼎得Xposed,是Android平台上最负盛名的一个框架。在这个框架下,我们可以加载很多插件App,这些插件App可以直接或间接操纵系统层面的东西,比如操纵一些本来只对系统厂商才open的功能(实际上是因为Android系统很多API是不公开的,而第三方APP又没有权限)。有了Xposed后,理论上我们的插件APP可以hook到系统任意一个Java进程(zygote,systemserver,systemui好不啦!)。

\u0026#xD;\u0026#xD;

功能太强大,自然也有缺点。Xposed不仅仅是一个插件加载功能,而是它从根上Hook了Android Java虚拟机,所以它需要root,所以每次为它启用新插件APP都需要重新启动。而如果仅是一个插件加载模块的话,当前有很多开源的插件加载模块,就没这么复杂了。

\u0026#xD;\u0026#xD;

Anyway,Xposed强大,我们可以学习其中的精髓,并且可以把它的思想和技术用到自己的插件加载模块里。这就是我们要学习Xposed的意义。

\u0026#xD;\u0026#xD;

Xposed支持32位和64位的dalvik以及ART,同时支持selinux。我仔细看了下,如果拓展把这些东西都讲的话,一个是很枯燥,另外一个是背离了我们本章讲解插件的主线。所以,本章将围绕下面几个点开展介绍:

\u0026#xD;\u0026#xD;

l  32位dalvik上非selinux模式下的xposed实现原理

\u0026#xD;\u0026#xD;

64位、selinux模式其实难度不在虚拟机上,而是在selinux上。

\u0026#xD;\u0026#xD;

二、  初识Xposed

\u0026#xD;\u0026#xD;

本章先来介绍下Xposed,这是一个大工程,包含多个项目,我也是花了不少时间才把它整个玩转起来的。Xposed包含如下几个工程:

\u0026#xD;\u0026#xD;
  • XposedInstaller,这是Xposed的插件管理和功能控制APP,也就是说Xposed整体管控功能就是由这个APP来完成的,它包括启用Xposed插件功能,下载和启用指定插件APP,还可以禁用Xposed插件功能等。注意,这个app要正常无误得运行必须能拿到root权限。\u0026#xD;\t
  • Xposed,这个项目属于Xposed框架,其实它就是单独搞了一套xposed版的zygote。这个zygote会替换系统原生的zygote。所以,它需要由XposedInstaller在root之后放到/system/bin下。\u0026#xD;\t
  • XposedBridge。这个项目也是Xposed框架,它属于Xposed框架的Java部分,编译出来是一个XposedBridge.jar包。\u0026#xD;\t
  • XposedTools。Xposed和XposedBridge编译依赖于Android源码,而且还有一些定制化的东西。所以XposedTools就是用来帮助我们编译XposedXposedBridge的。\u0026#xD;

提示,提示,提示:

\u0026#xD;\u0026#xD;

我把所有相关代码都下载并放到下面的地址了:

\u0026#xD;\u0026#xD;

https://code.csdn.net/Innost/xposed-learning  里边包含:

\u0026#xD;\u0026#xD;

1  xposed所有代码库的内容

\u0026#xD;\u0026#xD;

2  XposedDemo:Xposed插件APP Demo,非常简单

\u0026#xD;\u0026#xD;

3  XposedDemoTarget:XposedDemo将hook上的目标App

\u0026#xD;\u0026#xD;

2.1  编译Xposed

\u0026#xD;\u0026#xD;

下面介绍下如何编译Xposed,这里以Android 4.4.4为例。做Android开发最好配一个Nexus手机或Pad。我用得是Nexus 7 2013Wi-Fi版。Anyway,Xposed的编译和具体机器无关,不过下面的前提条件需要满足:

\u0026#xD;\u0026#xD;
  • 准备Android 4.4.4源码。我分享了很多AOSP源码,注意:我提供的源码没有.repo和.git,虽然省了很多空间,不过在编译Xposed的时候,还是需要有.repo。\u0026#xD;\t
  • 下载我提供的Xposed源码和测试Demo。\u0026#xD;

好,马上开始我们的步骤:

\u0026#xD;\u0026#xD;

2.1.1  下载支持库

\u0026#xD;\u0026#xD;

根据XposedTools的说明,我们先要修改下AOSP源码里的.repo,具体步骤如下:

\u0026#xD;\u0026#xD;
  • 进入AOSP/.repo目录。\u0026#xD;\t
  • 在.repo目录下新建local_manifests目录。\u0026#xD;\t
  • 把XposedTools/local_manifests/下的目标文件拷贝过去。local_manifests/目录下是各种API版本(即SDK=19,21之类)对应的xml文件。由于本例对应的SDK版本是19,所以需要把该目录下xposed_sdk19.xml文件拷贝到.repo/local_manifests/目录下。\u0026#xD;

这个文件是什么内容呢?来看图1:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

这个文件内容是啥意思呢?

\u0026#xD;\u0026#xD;
  • \u0026lt;remote\u0026gt;:新添远程仓库地址为github。\u0026#xD;\t
  • 第一个\u0026lt;project\u0026gt;:为frameworks/base/cmds/下新增加xposed工程。当然,这个工程我们刚才下载过了。你可以直接copy到指定目录下。\u0026#xD;\t
  • \u0026lt;remove-project\u0026gt;:移除AOSP/build目录。xposed有自己的处理。\u0026#xD;\t
  • 第二个\u0026lt;project\u0026gt;:下载xposed自己的build。path=\"build\",表示下载路径就是AOSP/build。\u0026#xD;

配置好后,请在AOSP目录下执行repo sync。这样它会根据manifests更新AOSP源码。当然,也可以只是下载frameworks/base/cmds/xposed工程和更新build工程。

\u0026#xD;\u0026#xD;

注意,repo sync是一个重型操作,会导致所有工程都进行一次同步。我建议的方法是直接下载和更新对应的工程

\u0026#xD;\u0026#xD;

比如,下载xposed工程,用repo sync frameworks/base/cmds/xposed即可。对于build目录,先把原来的build挪到其他地方。然后repo sync build即可。

\u0026#xD;\u0026#xD;

好了,到此,所有源码都已经ready了。

\u0026#xD;\u0026#xD;

2.1.2  修改build.conf

\u0026#xD;\u0026#xD;

下面我们进入XposedTools目录,然后修改其中的build.conf文件。该文件用于指示AOSP源码等参数。如图2所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

XposdTools提供了一个build.conf.sample模板,图2中的build.conf文件是在这个模板基础上修改而来。红框中是我修改的结果。其他选项没有变化。

\u0026#xD;\u0026#xD;

2.1.3  执行build.pl

\u0026#xD;\u0026#xD;

到XposedTools目录下,执行:./build.pl -t arm:19命令,这表明我要编译arm平台上SDK=19版本的xposed框架。注意,./build.pl --help会打印出使用方法。build.pl是一个perl脚本。图3是编译过程截图:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图3中,你可以发现build.pl跑到AOSP源码目录下,执行了:

\u0026#xD;\u0026#xD;
  • . build/envsetup.sh:初始化AOSP编译环境\u0026#xD;\t
  • lunch aosp_flo-userdebug:选择交叉编译平台。注意,这一块我是修改了XposedTools/xposed.pom,使它单独为我的nexus 7编译,而不是针对ARM平台做generic的编译。\u0026#xD;\t
  • make -j4 xposed libxposed_dalvik:编译xposed和libxposed_dalvik这两个目标文件。\u0026#xD;

在使用build.pl时,它还依赖一些Perl的类库,请童鞋们按照下面步骤下载这些依赖库:

\u0026#xD;\u0026#xD;

sudo apt-get install libconfig-inifiles-perl

\u0026#xD;\u0026#xD;

sudo apt-get install libio-all-perl

\u0026#xD;\u0026#xD;

sudo apt-get install libfile-readbackwards-perl

\u0026#xD;\u0026#xD;

sudo apt-get install libfile-tail-perl

\u0026#xD;\u0026#xD;

sudo apt-get install libtie-ixhash-perl

\u0026#xD;\u0026#xD;

build.pl执行过程中,如果报还有其他依赖库未找到,请通过下面命令

\u0026#xD;\u0026#xD;

apt-cache search perl XXX  来查找需要apt-get install哪个目标库。XXX是build.pl执行过程中报错时提供的库信息

\u0026#xD;\u0026#xD;

2.1.4  编译结果

\u0026#xD;\u0026#xD;

编译完成后,将产生一个zip包到AOSP/out/sdk19/arm下。AOSP/out是我在build.conf中指定的目录。如图4所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

编译结果是一个xposed-v65-arm-custom-build-xyz-20151030.zip包,这个包可以通过recovery刷到手机上。包的内容就是files文件夹下的内容,包含:

\u0026#xD;\u0026#xD;
  • system/bin/app_process_xposed:xposed版zygote。\u0026#xD;\t
  • system/bin/libxposed_dalvik.so:xposed框架的native层。\u0026#xD;\t
  • system/xposed.prop:xposed框架信息,包含版本号等。内容如右边图所示。\u0026#xD;

三、  XposedInstaller

\u0026#xD;\u0026#xD;

XposedInstaller是Xposed的App,用于管理Xposed框架和插件App。本节我们主要讨论它是如何安装Xposed框架和插件App的。

\u0026#xD;\u0026#xD;

XposedInstaller启动界面如图5所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图5中可知XposedInstaller提供好几项子页面。第一个“框架”用来安装或卸载Xposed框架的。我们来看它。

\u0026#xD;\u0026#xD;

3.1  安装Xposed框架

\u0026#xD;\u0026#xD;

如图6所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

注意,图6右上角的“程序自带”两个版本号,分别是app_process版本号为58,XposedBridge.jar版本号是54.

\u0026#xD;\u0026#xD;

而“激活”这两项为空,因为我们的系统还没有安装Xposed框架。

\u0026#xD;\u0026#xD;

“程序自带”是什么意思?原来,XposedInstaller在自己的assets目录下携带了所需要的xposed框架程序和模块,如图7所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

从图7可知,XposdInstaller自带了xposed版zyote(比如app_process_xposed_sdk16),为了更好得支持不同版本的Android,它还区分了SDK版本。另外,XposedInstaller也支持刷机包把xposed框架模块刷入系统,比如Xposed-Installer-Recovery.zip,里边包含的主要内容就是一个脚本,在recovery模式下运行,其内部也是把assets里的文件拷贝到/system相关目录中。这一块我们后续看代码就知道怎么玩儿了。

\u0026#xD;\u0026#xD;

安装Xposed框架的主要功能由InstallerFragment.java提供,我们看看相关代码。

\u0026#xD;\u0026#xD;

3.1.1  onCreateView

\u0026#xD;\u0026#xD;

onCreateView函数是Fragment里初始化UI的核心回调,其代码如图8所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图8代码中最后的refreshVersions用于获取版本号,也就是图6中右上角“程序自带”要显示的信息,它包括两个东西:

\u0026#xD;\u0026#xD;
  • xposed版app_process的版本,包括程序自带(也就是XposedInstaller放在assets目录下的)和系统安装的。当然,只有该系统安装过Xposed版app_process才有必要检查它的版本。图6中由于没有装过,所以“激活”那一栏显示为“-”。\u0026#xD;\t
  • XposedBridge.jar:也包括程序自带和系统已安装两个jar包的版本。\u0026#xD;

检查版本主要是为了兼容性考虑。代码中的refreshVersions用于获取他们的版本号,代码如图9所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图9中:

\u0026#xD;\u0026#xD;
  • getInstalledAppProcessVersion:读取/system/bin/app_process的版本号。\u0026#xD;\t
  • getLatestAppProcessVersion:读取自带app_process_sdkXX的版本号。\u0026#xD;\t
  • getJarInstalledVersion,读取/data/data/de.robv.android.xposed.installer/bin/ XposedBridge.jar版本号。对,你没看错,XposedBridge.jar是放在XposedInstaller自己的目录下的。\u0026#xD;\t
  • getJarLatestVersion:读取自带XposedBridge.jar的版本号。\u0026#xD;

想知道怎么获取版本系想你吗?来看图10:

\u0026#xD;\u0026#xD;


\u0026#xD; 

\u0026#xD;\u0026#xD;
  •  对于app_process,直接读取文件内容,然后找到红框中的那一行,后面的58就是版本号。\u0026#xD;\t
  •  对于XposedBrdige.jar,打开这个jar包,读取assets/VERSION的内容,里边就是存储了一个版本号信息。\u0026#xD;

太简单了,不惜得说。

\u0026#xD;\u0026#xD;

注意XposedBridge.jar包安装版的位置,它在/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar里

\u0026#xD;\u0026#xD;

3.1.2  安装xposed框架

\u0026#xD;\u0026#xD;

InstallerFragment的install函数用于安装xposed框架相关的模块。这个函数一点也不复杂,不过还是给大家看看。如图11所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

install函数没什么难度,但是我们要总结下xposed框架安装到底做了什么手脚:

\u0026#xD;\u0026#xD;
  • 将xposed版的app_process拷贝到/system/bin/,系统原来的app_process改名为app_process.orig\u0026#xD;\t
  • 将XposedBridge.jar放到/data/data/de.robv.android.xposed.installer下\u0026#xD;\t
  • 删除/data/data/de.robv.android.xposed.installer/conf/disabled文件。如果有disabled文件则表明整个Xposed功能被禁止使用。\u0026#xD;

嗯嗯,也没什么太多可说的。

\u0026#xD;\u0026#xD;

3.1.3  安装xposed插件

\u0026#xD;\u0026#xD;

xposed插件,在xposed世界里我们说它是插件,但是放到Android世界里它就是一种特殊的APP。这种类型的APP由xposed框架识别并加载,然后hook到其他的App进程。

\u0026#xD;\u0026#xD;

当然,这里既然提到进程,那么这些APP就必须要被先启动起来才是。

\u0026#xD;\u0026#xD;

XposedInstaller的插件管控由ModulesFragment界面来处理。本节主要想介绍下XposedInstaller是怎么对待Xposed插件APP的。

\u0026#xD;\u0026#xD;

来看代码,如图12所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

我们重点来看看Xposed插件APP是怎么被load的,代码其实在ModuleUtil的reloadInstalledModules函数中。代码很简单如图13所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图13左下角已经告诉各位Xposed插件APP该是什么样的了,右下角是XposedDemo示例。

\u0026#xD;\u0026#xD;

在AndroidManifest.xml里定义这些东西只是告诉Xposed自己是一个插件APP。但是作为一个挂钩插件,Xposed还需要知道这个APP里哪个类是用来挂钩的。这句话的意思是:

\u0026#xD;\u0026#xD;

1  这个APP是一个插件APP。该APP包含很多功能。

\u0026#xD;\u0026#xD;

2  这个APP包含的众多功能中,有一个功能是给目标进程挂钩。挂钩操作是Xposed框架来做的,所以它需要知道该APP中哪个类是继承了Xposed钩子接口。这样,Xposed框架找到插件APP之后会触发这个app的钩子接口进行挂钩。

\u0026#xD;\u0026#xD;

要做到第二步就得在assets/下放一个名叫xposed_init文件,里边指明插件APP的挂钩类名。XposedDemo的

\u0026#xD;\u0026#xD;

assets/xposed_init的内容就是:com.xposed.demo.MyXposedModule。当然,这个插件类必须实现Xposed的IXposedHookLoadPackage接口类。这个我们以后再讨论。

\u0026#xD;\u0026#xD;

四、  Xposed框架介绍

\u0026#xD;\u0026#xD;

Xposed框架分为xposed版app_process和XposedBridge.jar两部分。app_process就是zygote,我们先看看xposed版的zygote干了些什么。

\u0026#xD;\u0026#xD;

4.1  Xposed版zygote

\u0026#xD;\u0026#xD;

注意,本章只分析32位,dalvik版的xposed app_process,其入口main函数位于app_main.cpp里。

\u0026#xD;\u0026#xD;

图14所示的代码展示了Xposed版zygote与众不同之处。

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图14中,左上角的框是app_main的main函数,里边有两处不同之处:

\u0026#xD;\u0026#xD;
  • AppRuntime:此处的AppRuntime类是xposed版的AppRuntime。它的与众不同之处从左下图的onVmCreated开始。当检查到isXposedLoaded为真是时,将调用xposed::onVmCreated函数。\u0026#xD;\t
  • xposed::onVmCreated函数位于右上框,它先判断当前虚拟机是ART还是dalvik,然后加载xposed一个特殊so,此处是/system/bin/libxposed_dalvik.so。注意,这里的xposed框架和XposedInstaller略有区别。因为XposedInstaller比较旧了(也就支持sdk 16的样子),还没有涉及到ART和dalvik的区别。在onVmCreate中,它将调用libxposed_dalvik.so中的xposedInitLib函数,然后再调用so中设置的onVmCreated函数。这个onVmCreated函数由xposedInitLib设置。\u0026#xD;\t
  • 左上框中还有一个xposed:initialized函数。这个函数初始化了XposedShared类型的全局变量xposed,同时还把一个重要的jar包加到了CLASSPATH里。来看代码。如图15所示:\u0026#xD;

\u0026#xD;\u0026#xD;

图9展示了initialize函数的内容,主要是最后一个把/system/bin/XposedBridge.jar(这个jar包的位置和我们在XposedInstaller那里看到得不同,原因前面解释过了)加到CLASSPATH比较重要。

\u0026#xD;\u0026#xD;

注意,我们这里虽然对initialize介绍的内容很少,但实际上这个函数要真正看明白还是很需要技术实力的:

\u0026#xD;\u0026#xD;

1  logcat:start:里边fork了logcat进程用来存储xposed自己的log

\u0026#xD;\u0026#xD;

2  service:startAll:为了完美支持selinux,这里的处理更是很有技巧。selinux是一个完整的知识体系,想彻底掌握它的童鞋请参考我的三部曲文章《深入理解SELinux SEAndroid》 

\u0026#xD;\u0026#xD;

1\u0026amp;2其实很真实得反映出xposed的作者在Android、Linux上水平很高,经验很丰富。

\u0026#xD;\u0026#xD;

最后,如果xposed框架启用成功,那么zygote的入口类将由以前的com.android.internal.os.ZygoteInit变成de.robv.android.xposed.XposedBridge

\u0026#xD;\u0026#xD;

下面我们按照执行流程,把相关函数分析一遍:

\u0026#xD;\u0026#xD;
  • AppRuntime的onVmCreated,它最终会导致libxposed_dalvik.so中的onVmCreated被调用。我们直接分析最后这个onVmCreated。\u0026#xD;\t
  • 然后AppRuntime里会调用de.robv.android.xposed.XposedBridge.main函数。\u0026#xD;

4.2  onVmCreated

\u0026#xD;\u0026#xD;

图16为代码:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

onVmCreated比较简单了:

\u0026#xD;\u0026#xD;
  • 针对android/content/res/XResource\u0026amp;XTypedArray进行了一些处理,这是为将来资源替换时候做准备。以后我们碰到再说。\u0026#xD;\t
  • XposedBridge注册JNI函数\u0026#xD;

xposed官方说MIUI大量用了xposed的东西,并且不共享,以后碰到MIUI的问题他们不再支持。Don't know what to say....

\u0026#xD;\u0026#xD;

4.3  XposdBridge.main函数

\u0026#xD;\u0026#xD;

main函数代码如图17所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

main函数里有三个重要函数:

\u0026#xD;\u0026#xD;
  • initNative:好好学习下\u0026#xD;\t
  • initForZygote:好好学习下\u0026#xD;\t
  • loadModules:加载App插件\u0026#xD;

4.3.1  initNative

\u0026#xD;\u0026#xD;

initNative很重要,来看代码,如图18所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图18中,我们重点看一下callback_XposedBridge_initNativeregister_natives_XResources这两个函数。这两个函数比较简单,我们统一放到图19中:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

不多说了,没什么难度。

\u0026#xD;\u0026#xD;

4.3.2  initForZygote

\u0026#xD;\u0026#xD;

从这个函数开始,xposed就开始给系统一些关键函数挂钩子了。我们看看它怎么玩儿的。代码如图20所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图20中我在eclipse里用了代码缩略显示的方法,可知一共有五个框,分别hook了一些关键内容。我们从上到下一次分析。先来分析下Xposed框架提供的挂钩函数findAndHookMethod

\u0026#xD;\u0026#xD;
(1)  findAndHookMethod介绍
\u0026#xD;\u0026#xD;

findAndHookMethod用来对指定类的指定函数进行挂钩。这个函数很重要,开发插件APP时用得最多。来看它的代码,如图21所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

findAndHookMethod代码Java层面的逻辑还是比较好理解的:

\u0026#xD;\u0026#xD;
  • 首先是通过class相关的函数找到指定的函数对象。这里的查找需要指定函数所在的类,函数名,函数参数信息等,属于精确(exact)查找。注意,findAndHookMethod函数的最后一个参数必须是XC_MethodHook类型,即钩子对象的数据类型。\u0026#xD;\t
  • 然后就是XposedBridge.hookMethod。\u0026#xD;

来看hookMethod,如图22所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

hookMethod可知,一个目标函数可以挂多个钩子,这些钩子由一个集合来存储。然后我们将转到JNI层去看看hookMethodNative干了什么事情。这才是hook的核心。代码如图23所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

hookMethodNative完成了真正的挂钩处理,其思想很简单:

\u0026#xD;\u0026#xD;
  • 先找到目标函数在native层对应的Method对象。\u0026#xD;\t
  • 修改这个Method为native方法,并设置nativeFunchookedMethodCallback函数。\u0026#xD;\t
  • 然后还要保存原函数的一些信息到insns域。\u0026#xD;

我们在《深入理解Android之dalvik》一文中介绍过,JVM调用java函数时候,发现这个函数为native的话,就调用它的nativeFunc。下面我们看看钩子函数的调用。

\u0026#xD;\u0026#xD;
(2)  调用钩子函数
\u0026#xD;\u0026#xD;

hook钩子函数后我们就要调用它。上一节我们发现xposed在挂钩子的时候会把原函数改造成native属性(即Dalvik会按native函数的方式调用它),对应的nativeFunc是hookedMethodCallback,其代码如图24所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

注意喔,我们现在已经在处理被挂钩函数的调用了喔....从JNI会进入到java层的钩子函数dispatch总入口,及handleHookedMethod,这个函数比较复杂,我们一段一段来看它。

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

很简单。接着看第二段,如图26所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

貌似也很简单喔...

\u0026#xD;\u0026#xD;

现在来看原目标函数的调用,即invokeOriginalMethodNative。代码如图27所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

同样很容易,不多说了。到此,我们已经看到了Xposed挂钩的所有过程。好像也没什么复杂的,只要对dalvik稍微属性点,应该是比较容易做的。

\u0026#xD;\u0026#xD;

当然,本文只是给大家show了主要流程,真要自己动手做,我觉得难点在于参数传递等方面。anyway,了解了大体流程,后面的事情也好办,多尝试几次就好。

\u0026#xD;\u0026#xD;

下面我们回过头来看initForZygote里加的几个钩子都干了什么。

\u0026#xD;\u0026#xD;
(3)  initForZygote钩子设置
\u0026#xD;\u0026#xD;

图20的initForZygote代码示意中可知xposed对下面几个函数进行hook(此处先不讨论钩子函数干了什么)

\u0026#xD;\u0026#xD;
  • ActivityThread.handleBindApplication:这个函数是ActivityManagerService内部调用ActivityThread.bindApplication间接触发的。该函数的详情可参考《深入理解Android卷2》第六章深入理解ActivityManagerService的“ApplicationThread的bindApplication分析”一节。handleBindApplication主要工作是初始化APP(APP由zygote进程fork而来,在hanldeBindApplication之前,这个APP进程和zygote没什么区别。只有调用完handleBindApplication之后,这个APP进程才是APP,比如该进程有了对应的名字,Aplication对象被创建等)。\u0026#xD;\t
  • com.android.server.ServerThread. initAndLoop函数:这个函数是给system_server用的,用于启动系统各种重要服务。\u0026#xD;\t
  • LoadedApk构造函数:通过hookAllConstructors来对LoadedApk各种构造函数进行挂钩。LoadedApk是APK文件在APP里的代表对象,里边有很多重要的信息。\u0026#xD;\t
  • android.app.ApplicationPackageManager. getResourcesForApplication:挂钩PackageManager的资源获取函数。\u0026#xD;\t
  • hookResources:对资源进行hook。这一块由于涉及到android APP对资源的管控,以后有机会我们再详细介绍。\u0026#xD;

4.3.3  loadModules

\u0026#xD;\u0026#xD;

main函数在initForZygote之后的下一个动作就是loadModules,这就是加载所有的插件APP。我们来看看这个函数,代码如图28所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

我们来看下loadModules的处理:

\u0026#xD;\u0026#xD;
  • 先从XposedInstaller那获取当前已经安装(并启用)的插件APP列表,然后调用loadModule来加载这个APP。\u0026#xD;\t
  • loadModule从APP的assets/xposed_init文件里看看这个apk里声明了哪些钩子类。\u0026#xD;\t
  • loadModule校验这些钩子类是不是符合Xposed定义的钩子类类型,然后作对应处理。图29列出了Xposed支持的钩子类类型。\u0026#xD;

\u0026#xD;\u0026#xD;

图30展示了hookLoadPackagehookInitPackageResource两个函数的内容,特别简单。

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

嗯嗯,就是把对应的钩子函数保存起来先。

\u0026#xD;\u0026#xD;

好了,下面我们就来开始分析initForZygote里挂上的几个钩子分别有什么用。

\u0026#xD;\u0026#xD;

4.3.4  handleBindApplication钩子

\u0026#xD;\u0026#xD;

initForZygote为hanldeBindApplication设置了前处理钩子,代码如图31所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

前面说过,在APP生命周期内,handleBindApplication是APP刚准备好相关信息的一个重要点,在这个点去进行挂钩处理简直是最好不过了。当然,此处的挂钩处理也就是准备好这个APP的相关信息然后调用所有的IXposedHookLoadPackage类型的钩子。

\u0026#xD;\u0026#xD;

注意,除了handleBindApplication之外,由于一个APP进程事实上可以加载多个APK(比如那些申明同样的uid和运行在同一进程的APP),在LoadedApk的构造函数中也做了类似的处理

\u0026#xD;\u0026#xD;

IXposedHookLoadPackage钩子一般会干些什么呢?图31对XposedInstaller的处理就很明显了。一般而言,这种钩子会对目标APP中感兴趣的函数进行挂钩(调用findAndHookMethod),比如XposedInstaller对getActiveXposedVersion进行了挂钩,用于返回系统里正在使用的Xposed框架版本。

\u0026#xD;\u0026#xD;

我们应该在钩子函数里干些什么?这是一个重要问题。我也不废话了,直接上XposedDemo的源码,如图32所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图32是我在xposed-learning项目中提供的XposedDemo示例,可知:

\u0026#xD;\u0026#xD;
  • Xposed框架对handleBindApplication进行hook后,当有APP启动的时候,该框架就会调用所有IXposedHookLoadPackage类型的钩子。\u0026#xD;\t
  • 这种钩子一定要区分当前被hook的APP是不是自己想要Hook的APP。如果不是,请直接return。如果是自己的目标APP,则进一步通过findAndHookMethod进行挂钩。\u0026#xD;

简单点说,我们在IXposedHookLoadPackage的handleLoadPackage中把该挂的钩子都挂上就好。

\u0026#xD;\u0026#xD;

4.3.5  initAndLoop钩子

\u0026#xD;\u0026#xD;

initAndLoop是system_server进程的关键函数,在这个函数里Android Framework的绝大部分Service都将被创建。真是艺高人胆大,这个进程居然都提供了挂钩处理。其代码如图33所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

代码倒是很简单,无非是针对system_server进行hook。插件函数如果想区分被hook的进程是否为system_server的话,只需要判断packageName是否为\"android\"即可。

\u0026#xD;\u0026#xD;

再次强调,system_server是Android Java层Framework的核心,要hook它需要万分小心,否则或导致手机系统出现会各种不稳定,崩溃,重启等情况。

\u0026#xD;\u0026#xD;

下面我们介绍下Xposed框架对资源是怎么Hook的。

\u0026#xD;\u0026#xD;

4.4  对资源的Hook

\u0026#xD;\u0026#xD;

前面章节介绍了Xposed框架如何对代码调用逻辑进行hook。在Android APP中,除了代码逻辑外,Xposed还支持对资源进行Hook。对资源Hook的原理其实和对代码调用进行Hook的原理类似。这里我们简单介绍下Xposed框架如何对资源进行Hook。

\u0026#xD;\u0026#xD;

在Android APP中,资源有三个重要类:

\u0026#xD;\u0026#xD;
  • ResourcesManager:一个APP进程有一个ResourcesManager。一个ResourcesManager可以管理多个Resouces。\u0026#xD;\t
  • Resources:Resources是一个APK中资源的代表,注意,它不是指单独的一种类型的资源(比如字符串,图片等),它是一个APK文件中资源文件的代表。Resources对象有一个AssetManagerAssetManager能操作APK文件(其实就是一个压缩包)assets以及res目录里的内容。\u0026#xD;\t
  • Resources通过ResourcesKey将自己保存到ResourceManager中的一个ArrayMap里。\u0026#xD;\t
  • 对于资源挂钩来说,Xposed框架及插件APP做如下几件重要的事情:\u0026#xD;\t
  • handleBindApplication类似,在APP进程某个时间点的时候(猜测:初始化资源相关模块)进行Hook,然后调用IXposedHookInitPackageResources类型的钩子。\u0026#xD;\t
  • IXposedHookInitPackageResources类型的钩子通过Xposed提供的XResources类的setReplacement对原有的资源进行替换。在Xposed框架中,XResources是用于替代Resources的。\u0026#xD;\t
  •  App运行时候,Xposed框架截获相关的资源获取函数(比如Context的getString),先看看是否被replace了,如果是,则返回被replace的结果。\u0026#xD;

就这么简单。我们一步一步来看。

\u0026#xD;\u0026#xD;

注意,XposedDemo并没有hook资源,请感兴趣的童鞋们自行加上该功能进行测试。

\u0026#xD;\u0026#xD;

4.4.1  hookResource

\u0026#xD;\u0026#xD;

hookResource对ResourceManager等进行了挂钩处理。来看代码,如图34所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

hookResources主要是对ResourcesManager的getTopLevelResources进行了Hook。APP中原来使用的是Resources代表资源,Hook之后,Xposed用XResources代替了Resources。图35展示了XResources的派生关系。

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

接着看hookResources第二段代码,如图36所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

第二段代码中,Xposed资源Hook框架调用了资源hook类型的钩子。同样,我们需要关注在这种类型的钩子里插件APP要干得事情,那就是:

\u0026#xD;\u0026#xD;
  • 通过XResources的setReplacement函数将目标资源的id和内容进行替换。\u0026#xD;

上面替换的还是APP自己的资源,除此之外,Xposed还能替换系统资源(即framework-res.apk里声明的资源),这部分代码也在hookResources里,来看图37:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

图37中,Xposed将Resources里代表系统资源的mSystem对象也进行了替换。不过这里没有独立调用回调。没关系,因为在第二部分的回调中,插件APP通过XResourcessetSystemWideReplacement可以对系统资源进行替换。

\u0026#xD;\u0026#xD;

注意,hookResources之后,APP里调用的Resources相关的函数就全部转到XResources来处理了。比如获取字符串的getString函数,其最终会调用到XResources的getText函数,代码如图38所示:

\u0026#xD;\u0026#xD;

\u0026#xD;\u0026#xD;

到此,我们对Xposed框架如何hook资源进行了介绍。不过,layout资源的hook我这里并没有介绍,请童鞋们阅读XResources的getLayout函数和init函数。

\u0026#xD;\u0026#xD;

五、 总结

\u0026#xD;\u0026#xD;

到此Xposed 32位dalvik版框架基本介绍完了。这一趟绝对不是本篇这20多页文章这么轻松的事情。正如我在《深入理解Android之Dalvik》一文写得那样,我是在研究xposed的时候,发现必须要搞清楚dalvik,所以才先写了dalvik的文章,然后才能走到今天这一步。

\u0026#xD;\u0026#xD;

Xposed是一个成熟框架,高度体现了开发者在Java虚拟机这块有着非常深厚的知识积累。同时,如果加上selinux的话,那开发者对linux系统也是相当相当熟悉。另外,貌似开发者是用业余时间搞出来的,这在当下上班时间强制为996的码农而言几乎是不可能的事情。

\u0026#xD;\u0026#xD;

再次向开发者致敬,也同时呼吁基于xposed框架的派生框架开发者遵守相关开源协议。

你可能感兴趣的:(深入理解Android(三):Xposed详解)