Xposed 框架的使用
在 11.3 节中,我们已经初步了解了 Hook 技术,利用这个技术可以在某一逻辑的前后加入我们自定义的逻辑处理代码实现我们想要的功能,例如数据截获、输入和返回值修改等。
那 Hook 技术能否应用在 App 上呢?当然也是可以的。
Hook 技术在 App 上的应用非常广泛,例如修改朋友圈的微信步数,其实就是通过发送 Hook 数据的方法修改了步数;又如处理 SSLPining 问题时,用 Hook 技术修改 SSL 证书的校验结果,从而绕过校验过程。对于 App 爬虫,也可以 Hook 一些关键的方法拿到方法执行前后的结果,从而实现数据的截获。
那这个技术怎么实现呢?这里不得不提到一个框架—— Xposed 框架。
Xposed 框架的简介
Xposed 框架是一套开源的,在 Android 高权限模式下运行的框架服务,可以在不修改 App 源码的情况下影响程序运行(修改系统)。基于 Xposed 框架,可以制作许多功能强大的模块,且这些模块可以在功能不冲突的情况下同时运作。
Xposed 框架的原理我们稍作了解即可:替换系统级别的 /system/bin/app_process 程序,控制 zygote 进程,使得 app_process 在启动过程中加载 XposedBridge.jar 包,这个 jar 包里定义了对系统方法、属性所做的一系列 Hook 操作,同时提供了几个 Hook API 供我们编写 Xposed 模块使用。我们在编写 Xposed 模块时,引用几个 Hook 方法就可以修改系统级别的任意方法和属性了。
这么说可能有点抽象,下面我们编写一个 Xposed 模块,带大家体会一下它的用法,最后再使用 Xposed 模块修改真实 App 的执行逻辑。
准备工作
由于 Xposed 运行在 Android 平台上,所以我们本节的环境和 Android 相关。
在开始之前,先做好如下准备工作。
-
配置好 Android 开发环境,具体的配置方法可以参考 https://setup.scrape.center/android 。
-
准备一个已经 ROOT 的 Android 设备(真机或模拟器均可,并把它和 PC 连接好,可以在 PC 上使用 adb 命令正常连接到该设备。
-
在设备上安装好 Xposed 工具,具体的安装方法可以参考 https://setup.scrape.center/xposed 。
Xposed 本身对 Android 系统和设备是有一定要求的,如果你的设备不满足要求,这里有几个备选方案。
-
对于高版本的 Android 系统,Xposed 可能不提供支持,此时可以安装 EdXposed,具体的安装方法可以参考 https://setup.scrape.center/edxposed。
-
对于没有 ROOT 的 Android 系统,可以使用 VirtualXposed,VirtualXposed 不需要 ROOT 即可使用,具体的安装方法可以参考 https://setup.scrape.center/virtualxposed。
Xposed 模块
Xposed 框架现在的生态系统非常庞大,基于它开发的模块非常多,点击下载菜单就可以看到已发布的 Xposed 模块,如图 13-44 所示。
图13-44 已发布的 Xposed 模块
可以看到,这里有各种各样的模块,当然我们也可以自己编写模块来实现想要的功能。这时大家可能会问,这些模块究竟是干嘛的?到底是什么东西?其实从本质上讲,Xposed 模块就是一个 Android App,开发一个 Xposed 模块和开发一个 Android App 的流程是差不多的,只不过前者多了下面 4 个步骤。
-
需要添加一些标识符,表明这个 App 是一个 Xposed 模块,以便在 Android 设备上安装后,Xposed 框架可以被识别出来。
-
需要引入 XposedBridge.jar 包,从而实现 Hook 操作。
-
需要定义一些 Hook 操作,对本 App 或其他 App 的逻辑进行修改。
-
定义完 Hook 操作的逻辑后,需要告诉 Xposed 框架哪些是我们自已定义的 Hook 操作逻辑,以便 Xposed 执行这些逻辑。
下面我们就一步步实现以上 4 步。
开发一个 Xposed 模块
首先在 Android Studio 中新建一个 Android 项目,会提示我们选择 Activity,直接选择默认的 Empty Activity 即可,如图 13-45 所示。
图13-45 新建一个 Android 项目
然后做一些基础配置,项目名称配置为 XposedTest,包名可以任意取,配置好项目路径和编写语言,同时指定最小 SDK 版本为 15,如图 13-46 所示。
图 13-46 一些基础配置
点击 FINISH 按钮。XposedTest 项目就创建好了,生成的界面如图 13-47 所示。
图13-47 XposedTest 项目
之后我们实现之前说的第 1 步,添加一些标识符,表明这是一个 Xposed 模块。打开 AndroidManifest.xml 文件,找到 application 标签,在与 activity 标签并列的位置添加如下内容:
<meta-data
android:name="xposedmodule"
android:value="true"/>
<meta-data
android:name="xposeddescription" android:value="xposedTest"/>
<meta-data
android:name="xposedminversion" android:value="53"/>
最终 AndroidManifest.xml 文件的内容如图 13-48 所示。
图13-48 AndroidManifest.xml 文件的内容
这段内容指定了 3 个 meta-data。
-
xposedmodule:这里设置为 true,代表这是一个 Xposed 模块。
-
xposeddescription:模块的描述,此处填写模块名称就好,就是一个字符串。
-
xposedminversion:模块运行所要求的 Xposed 最低版本号,这里是 53。
定义好这 3 个内容后,把这个 App 安装到准备好的 Android 设备上,Xposed 框架就能识别出这个 App 是一个 Xposed 模块了。点击运行按钮,在设备上运行这个 App,可以看到设备上显示如下界面,如图 13-49 所示。
图 13-49 在设备上运行 XposedTestApp
此时打开 Xposed Installer 的模块界面,就会发现它检测到了这个模块,如图 13-50 所示。
图13-50 XposedInstaller 的模块界面
我们勾选这个模块,它就成功被启用了。不过需要注意,这个操作得重启设备才能生效,可以手动启动设备或者通过 XposedInstaller 首页的重启选项进行重启。
但是,现在启用了也没什么用啊,因为 XposedTest 里还什么功能都没有呢,需要引人与 Xposed 相关的 SDK,我们才能调用 Xposed 提供的一些 Hook 操作方法,实现 Hook 操作。
于是打开 app/build.gradle 文件,在 dependencies 部分添加如下两行代码:
compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82:sources'
这是 Xposed 的 SDK,添加之后 Android Stuido 会检测到项目配置发生的变化,并在上方显示提示信息。我们点击右上角的 “Sync Now” 选项,就会自动下载和安装新添加的 Xposed SDK,如图 13-51 所示。
现在 Xposed 的 SDK 就安装成功了,下面我们就能使用里面的方法 Hook 代码逻辑了。那怎么实现呢?具体 Hook 什么呢?总得有点头绪吧,头绪在哪呢?不先自己写一个,这里我们会增加一个鼠标响应事件,在点击鼠标后触发算式计算操作。
图13-51 点击 “Sync Now” 选项
首先修改一下页面内容,设置一个按钮,将 activity_main.xml 文件中的内容替换为下面的代码即可:
这时重新运行 App,就会出现一个 TEST 按钮而不是文本框了。然后修改 MainActivity.java 文件,其内容如下:
这里我们定义了一个 Button 对象,然后使用 findViewById 方法从视图里获取了这个对象,并为它添加了一个点击事件,具体是点击该按钮之后生成 Toast 提示,提示内容为 showMessage 方法的返回结果。
showMessage 方法接收两个参数一一int 类型的 x 和 y,返回结果是一个字符串,由 “x + y =” 字符串和计算结果拼接而得,其实就是一个算数表达式。此处我们给 showMessage 方法传入的参数是 1 和 2,所以点击按钮后,界面上应该显示 x+y=3。我们重新运行 App,然后点击 TEST 按钮,运行结果如图 13-52 所示。
图 13-52 点击 TEST 按钮的结果
这就是一个基本的逻辑。下一步我们使用 Xposed 模块对这个逻辑进行 Hook,在与 MainActivity.java 文件同级的位置新建一个 HookMessage.java 文件,文件内容如下:
这里我们定义了与 Hook 操作相关的逻辑,下面梳理一下其中的关键点。
-
HookMessage 类实现了 IXposedHookLoadPackage 接口,需要定义 handleLoadPackage 方法,这个方法会在加载每个 App 包时被执行。
-
在 handleLoadPackage 方法中,调用 loadPackageParam.packageName 属性获取了当前运行的 App 包名,并判断其是否为当前 Xposed 模块对应 App 的包名,然后做后续处理。注意,这里的包名可以是任意 App 的,不一定非得是当前 Xposed 模块对应 App 的包名,只不过我们是要 Hook 在这个 App 中定义的逻辑,所以做这个判断。
-
如果上一步的判断结果为 “是”,就调用 loadClass 方法,并在其参数中指定要加载的类(这里是刚才定义的 MainActivity 类)的路径,然后把动态加载出的类赋值为 clazz,这是一个类对象。
-
调用 XposedHelpers 模块提供的 findAndHookMethod 方法,需要传入类名、方法名、方法的参数类型(有几个参数就写几个类型,写法是参数类型加上类的声明)和 Hook 逻辑。这里传入的就是 clazz 类,showMessage 方法,两个 int.class(showMessage 方法有两个 int 类型的参数)和一个 XC_MethodHook 方法。
-
XC_MethodHook 方法里定义了 Hook 操作的真正逻辑,包含两个方法一一beforeHookedMethod 和 afterHookedMethod,分别代表 Hook showMessage方法前、后所做的操作,这两个方法都有一个 MethodHookParam 类型的参数,里面包含方法执行的参数和结果等信息。
-
一般而言,beforeHookedMethod 方法用来修改被 Hook 方法的参数内容,或者直接定义被 Hook 方法的运行流程。afterHookedMethod 用来对被 Hook 方法做后处理,例如拦截、保存、转发、修改被 Hook 方法的结果。
-
XposedBridge 模块里的 log 方法可以将日志信息记录到 Xposed Installer 中,我们在 Xposed Installer 的日志页面就能看到对应的结果,很方便在调试时使用。
这里我们先用 beforeHookedMethod 方法做处理,修改参数 param 的 args 属性值,这个值其实是一个列表,元素是 showMessage 方法的参数,因为之前传入的参数是 1 和 2,所以这里的 args 属性值就是 [1, 2],我们把其中的第一个元素修改成了 2,那 args 属性值就会变成 [2, 2]。
现在我们已经把 Hook 的逻辑实现好了,还差最后一步,就是告诉 Xposed 模块我们的 Hook 逻辑在哪定义着。因此需要新建一个 Xposed 入口文件。首先在 main 文件夹下新建一个 AssetsFolder,如图 13-53 所示。
图13-53 新建一个 AssetsFolder
然后在 assets 文件夹下新建一个 xposed_init 文件,文件名不需要有任何后缀,如图 13-54 所示。
图13-54 新建一个 xposed_init 文件
把 HookMessage 类的路径写在这个文件中,即文件内容如下:
com.germey.xposedtest.HookMessage
写好后保存这个文件,Xposed 模块就会自动读取 xposed_init 文件,并执行我们自定义的 Hook 逻辑。
最后,重新安装一下 XposedTest App 看看效果,记得安装完成之后重启一下 Xposed 模块,否则是没有效果的。重启 Xposed 模块之后,点击 App 界面上的 TEST 按钮结果如图 13-55 所示。
可以看到,这里的运行结果就和图 13-52 中的不一样了,Toast 提示信息变成了 x+y=4,这说明我们通过 beforeHookedMethod 方法,成功把 args 属性值的第一个元素,也就是 showMessage 方法的 x 参数值修改成了 2,第二个元素则还是 2,相当于在 showMessage 方法被调用之前,其两个参数就被修改成了 2 和 2,所以最后的计算结果是 4。
现在大家对 beforeHookedMethod 方法的用法应该有进一步了解了。这个方法学习完,我们再来体会一下 afterHookedMethod 方法的用法,它用来对被 Hook 方法的返回结果做后处理,例如这里我们把 afterHookedMethod 方法的内容修改为:
protected void afterHookedMethod(MethodHookParam param)throws Throwable {
XposedBridge.log("Called afterHookedMethod");
param.setResult("Hooked");
}
这里我们增加了一行代码一一调用 param 的 setResult 方法,这样可以直接修改方法的返回结果修改完成之后,重新安装一下 XposedTestApp,并重启这个 Xposed 模块,还是点击 TEST 按钮,运行结果变成了图 13-56 所示的这样。
图13-55 Hook 后点击 TEST 按钮的结果 图13-56 修改返回结果后的 Toast 提示信息
可以看到提示信息变成了我们修改的内容,说明 afterHookedMethod 方法起作用了。最后我们来看一下日志,打开 Xposed Installer 的日志页面,其内容如图 13-57 所示。
图13-57 日志页面
可以看到这里输出了我们用 XposedBridge 模块的 log 方法输出的内容。
Xposed 模块提供的 API
现在我们来看一下 Xposed 模块提供的 API。本节前面所讲的 Hook 操作就是由 Xposed 模块提供的一个 API,即 findAndHookMethod 实现的。大家可以打开 https://api.xposed.info/reference/de/robv/android/xposed/XposedHelpers.html 查看所有的 API,这里简单列举几个。
-
callsStaticMethod:调用静态方法。
-
findAndHookConstructor:查找并 Hook 构造方法。
-
findClassIfExists:查找某个类是否存在。
-
findField:获取成员变量。
很多 API 是有类似功能或重合功能的,这里不再一一列举,如果感兴趣可以查看官方的文档说明。另外,非常推荐大家研究一下 Xposed 模块里各个包的用法,地址为 https://api.xposed.info/reference/de/robv/android/xposed/package-summary.html 。大家还可以多研究一些优秀的 Xposed 模块,例如 https://devsjournal.com/best-xposed-modules.html 里就列举了几款很受欢迎的 Xposed 模块。Xposed 框架中文站的地址是 http://xposed.appkg.com/ ,大家可以从中找一些优秀模块的源码研究一下,收获会非常大。