转载

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

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

一、背景

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

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

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

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

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

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

二、  初识Xposed

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

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

提示,提示,提示:

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

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

1  xposed所有代码库的内容

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

3  XposedDemoTarget:XposedDemo将hook上的目标App

2.1  编译Xposed

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

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

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

2.1.1  下载支持库

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

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

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

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

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

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

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

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

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

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

2.1.2  修改build.conf

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

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

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

2.1.3  执行build.pl

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

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

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

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

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

sudo apt-get install libconfig-inifiles-perl

sudo apt-get install libio-all-perl

sudo apt-get install libfile-readbackwards-perl

sudo apt-get install libfile-tail-perl

sudo apt-get install libtie-ixhash-perl

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

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

2.1.4  编译结果

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

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

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

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

三、  XposedInstaller

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

XposedInstaller启动界面如图5所示:

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

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

3.1  安装Xposed框架

如图6所示:

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

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

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

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

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

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

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

3.1.1  onCreateView

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

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

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

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

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

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

图9中:

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

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

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

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

太简单了,不惜得说。

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

3.1.2  安装xposed框架

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

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

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

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

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

3.1.3  安装xposed插件

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

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

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

来看代码,如图12所示:

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

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

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

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

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

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

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

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

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

四、  Xposed框架介绍

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

4.1  Xposed版zygote

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

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

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

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

  • AppRuntime :此处的AppRuntime类是xposed版的AppRuntime。它的与众不同之处从左下图的 onVmCreated 开始。当检查到 isXposedLoaded 为真是时,将调用 xposed::onVmCreated 函数。
  • 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 设置。
  • 左上框中还有一个 xposed:initialized 函数。这个函数初始化了 XposedShared 类型的全局变量xposed,同时还把一个重要的jar包加到了CLASSPATH里。来看代码。如图15所示:

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

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

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

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

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

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

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

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

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

4.2  onVmCreated

图16为代码:

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

onVmCreated比较简单了:

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

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

4.3  XposdBridge.main函数

main函数代码如图17所示:

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

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

  • initNative:好好学习下
  • initForZygote:好好学习下
  • loadModules:加载App插件

4.3.1  initNative

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

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

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

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

不多说了,没什么难度。

4.3.2  initForZygote

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

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

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

(1)  findAndHookMethod介绍

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

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

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

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

来看hookMethod,如图22所示:

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

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

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

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

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

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

(2)  调用钩子函数

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

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

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

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

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

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

貌似也很简单喔...

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

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

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

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

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

(3)  initForZygote钩子设置

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

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

4.3.3  loadModules

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

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

我们来看下loadModules的处理:

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

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

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

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

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

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

4.3.4  handleBindApplication钩子

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

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

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

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

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

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

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

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

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

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

4.3.5  initAndLoop钩子

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

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

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

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

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

4.4  对资源的Hook

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

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

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

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

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

4.4.1  hookResource

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

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

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

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

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

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

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

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

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

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

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

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

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

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

五、 总结

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

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

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

正文到此结束
Loading...