“
阅读本文大概需要 8 分钟。
”如果大家对逆向有所涉猎的话,可能听说过词叫做 Hook,利用 Hook 技术我们可以在某一逻辑的前后加入我们自定义的逻辑处理代码,几乎可以实现任意逻辑的修改。
在前面的 JavaScript 逆向实战一节我们也初步体会了 Hook 的功效,如果你对 Hook 的概念还不太了解,可以搜索一下「Hook 技术」相关的内容来了解下。
对于 App 来说,Hook 技术应用非常广泛。比如朋友圈微信步数的修改,其实就是 Hook 了数据发送的方法,实现步数的修改。比如处理安卓的 SSL Pining,用 Hook 技术也可以修改 SSL 证书校验结果,实现校验的绕过。对于 App 爬虫来说,我们也可以 Hook 一些关键的方法拿到方法执行前后的结果,从而实现数据的截获。
那这些技术怎么来实现呢?这里就不得不提一个框架,叫做 Xposed。
Xposed 介绍
Xposed框架(Xposed Framework)是一套开源的,在 Android 高权限模式下运行的框架服务,可以在不修改 App 源码的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
Xposed 框架的原理是通过替换系统级别的 /system/bin/app_process
程序控制 zygote 进程,使得app_process
在启动过程中会加载 XposedBridge.jar
这个 jar 包,这个 jar 包里面定义了对系统方法、属性的一系列 Hook 操作,同时还提供了几个 Hook API 供我们编写 Xposed 模块来使用。我们编写一个 Xposed 模块时,引用 Xposed 提供的几个 Hook 方法就可以实现对系统级别任意方法和属性的修改了。
这么说可能有点抽象,下面我们来编写一个 Xposed 模块带大家体会一下 Xposed 的用法,最后再使用 Xposed 来实现一个真实 App 执行逻辑的修改。
准备工作
由于 Xposed 是运行在 Android 平台上的,所以我们的环境就和 Android 相关。
需要做如下准备工作:
•配置好 Android 开发环境,开发环境的搭建流程可以参加下面的几个链接:•https://juejin.im/post/5d255101e51d4556d86c7b4f•https://juejin.im/post/5d5eb3ed5188252ae10be138•准备一个已经 ROOT 的安卓设备并连接好 PC,可以使用虚拟机或真机,比如我使用的是虚拟机网易 Mumu,已经自带了 ROOT 功能,当然如果你有已经 ROOT 的真机也是可以的。
准备好了如上环境之后,我们就开始 Xposed 模块的编写吧。
Xposed Installer 安装
有了如上的环境之后,首先我们需要先安装一下 Xposed。
要安装 Xposed 我们需要借助于一个 App,叫做 Xposed Installer,它就是用来安装 Xposed 框架的,利用它我们可以下载和安装 Xposed 框架,同时还能查看和管理 Xposed 模块,还能查看一些 Xposed 框架输出等日志信息等。
怎么安装呢?
我们可以先打开 Xposed 的官网 https://repo.xposed.info/module/de.robv.android.xposed.installer,然后就可以看到一些有关 Xposed 的提示信息。
这里提示了这么一条重要信息:
For Android 5.0 or higher (Lollipop/Marshmallow), these versions don't work! Use this instead: http://forum.xda-developers.com/showthread.php?t=3034811
很明显了,如果你的手机安卓版本在 5.0 以下,可以直接点击首页下方的 apk 下载,如果是 5.0 或更高版本,那么就到 http://forum.xda-developers.com/showthread.php?t=3034811 这个链接下载,后者的下载的真实链接是 https://forum.xda-developers.com/attachment.php?attachmentid=4393082&d=1516301692,可以直接点击下载。
注意:由于时间上的变化,下载地址以官方介绍为准。
下载下来之后会得到一个 apk 文件,我们可以直接在真机或模拟机上安装。
安装完成之后便会出现这样的一个图标:
![null](https://i-blog.csdnimg.cn/blog_migrate/2ed537b927b0ade69d909ff39d0ffbb0.png)
这就代表 Xposed Installer 已经安装成功了。
下面我们打开它,它可能会提示需要 ROOT 权限,授予即可。
打开之后的界面可能如下所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/38f8508e729bb3d8c2b9079ba2b994d2.png)
点击安装,如图所示。
![null](https://i-blog.csdnimg.cn/blog_migrate/f995b20fa90c46fa7cd539afd10391fa.png)
![null](https://i-blog.csdnimg.cn/blog_migrate/a127f97251a4d310535e544f0ef4a63e.png)
接着我们重启设备,便可以看到如下界面,代表 Xposed 框架已经安装并激活,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/266db8050363862be256edc20df622c0.png)
Xposed 模块
现在 Xposed 的生态非常庞大,基于 Xposed 开发的模块非常之多,可以点击下载菜单可以看到已有的发布的 Xposed 模块,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/4cd16abbf236318f4127c8c2e2fb9e1f.png)
当然我们也可以编写自己的模块来实现想要的功能。
这时候大家可能就会问了,这模块究竟是干嘛的?它到底是一个什么东西?
其实本质上来说,它就是一个安卓 App,开发一个安卓模块其实流程上就和开发一个安卓 App 差不多,只不过相比 App 开发来说多了下面四个步骤:
1.这个 App 里面要加上一些标识,标明这个 App 是一个 Xposed 模块,以便安装之后 Xposed 框架可以识别出来。2.这个 App 里面需要引入 Xposed 的 jar 包,从而能实现 Hook 操作。3.App 里面定义一些 Hook 操作,可以对本 App 或其他的 App 的逻辑进行修改。4.定义完这些 Hook 操作逻辑之后,还需要告诉 Xposed 框架哪些是我们自己定义的 Hook 操作逻辑,以便 Xposed 执行这些 Hook 逻辑。
就这么四步,这四步这么来实现呢,下面我们来一步步实现。
Xposed 模块开发
下面我们来根据上面的四步来进行一个 Xposed 模块开发吧。
首先我们用 Android Studio 新建一个安卓项目,第一步提示我们选择 Activity,直接选择默认的 Empty Activity 即可,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/8b04e048d2f7c2af12d48a08064eb1e5.png)
名称叫做 XposedTest,包名可以任意取,然后指定好项目路径和编写语言,同时指定最小 SDK 版本为 15,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/622ba9c1c9ca6e279f846408c1ea6439.png)
最后生成的界面如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/ff079c18a1ee192628da2576849a9814.png)
我们打开 AndroidManifest.xml 文件,添加如下内容:
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Xposed Test" />
<meta-data
android:name="xposedminversion"
android:value="53" />
到 application 标签内,和 activity 标签并列,最终内容如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/3084090f575716d2b15a747f25dd752b.png)
这里指定了三个 meta-data,分别为:
•xposedmodule:这里设置为 true,代表这是一个 Xposed 模块。•xposeddescription:模块的描述,填写模块描述就好,就是一个字符串。•xposedminversion:模块运行要求的 Xposed 最低版本号,这里是 53。
定义好这三个内容之后,把这个 App 安装到手机,Xposed 就能识别出这个 App 是一个 Xposed 模块了。
我们点击运行按钮,在手机上运行这个 App。
这时候可以看到手机上出现了如下界面,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/a6df7fdda1b54e067fe23aa0b7dde67d.png)
![null](https://i-blog.csdnimg.cn/blog_migrate/9708595b89b3bdee897066a32c8d2587.png)
但是,现在启用了也没什么用啊,因为这个模块里面什么功能都还没有呢。
接下来我们再在项目中引入 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 便会自动下载和安装,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/749da9685ddd756e820472653e0f0218.png)
那怎么来实现逻辑的 Hook 呢?Hook 什么呢?那总得有点逻辑吧?哪来的逻辑呢?自己先写一个吧。
说干就干,这里我们就加一个鼠标响应事件,点击之后触发一个算式计算的逻辑吧。
首先我们修改下页面内容,把当前的文本框设置成一个按钮,以便点击触发,修改 app/src/main/res/layout/activity_main.xml 文件,内容替换为如下内容:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
这时候重新运行 App 就会出现一个按钮了,而不是一行文本框。
接下来再修改下 MainActivity.java 文件,内容如下:
package com.germey.xposedtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this, showMessage(1, 2), Toast.LENGTH_SHORT).show();
}
});
}
public String showMessage(int x, int y) {
return "x + y = " + (x + y);
}
}
这里我们定义了一个 Button,然后使用 findViewById 方法从视图里面获取到了这个 Button 对象,同时我们为这个 Button 添加了一个点击事件,点击之后会生成一个 Toast 提示,其内容为 showMessage 方法返回的结果。
这个 showMessage 方法接收两个参数,是 int 类型的 x 和 y,返回的结果是一个字符串,即「x + y = 」这个字符串再拼接上二者计算得到的结果,其实就是一个算数表达式。
这里 showMessage 在调用的时候我们传入了 1 + 2,所以最后 showMessage 显示的结果应该为 「x + y = 3」,我们重新运行下 App,然后点击 TEST 按钮,可以看到如下运行结果:
![null](https://i-blog.csdnimg.cn/blog_migrate/412aa6fb29ceb0239f73831ca3068f76.png)
这样我们的一个基本的逻辑就定义好了。
定义好了之后呢,下一步我们就来用 Xposed 进行 Hook 吧,我们在 MainActivity.java 同级新建一个 Java Class,内容如下:
package com.germey.xposedtest;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookMessage implements IXposedHookLoadPackage {
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (loadPackageParam.packageName.equals("com.germey.xposedtest")) {
XposedBridge.log("Hooked com.germey.xposedtest Package");
Class clazz = loadPackageParam.classLoader.loadClass(
"com.germey.xposedtest.MainActivity");
XposedHelpers.findAndHookMethod(clazz, "showMessage", int.class, int.class, new XC_MethodHook() {
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("Called beforeHookedMethod");
param.args[0] = 2;
XposedBridge.log("Changed args 0 to " + param.args[0]);
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("Called afterHookedMethod");
}
});
}
}
}
这里我们就定义了 Hook 的相关逻辑了,这里梳理几个关键的点:
•这里的 class 实现了 IXposedHookLoadPackage 接口,需要定义 handleLoadPackage 这个方法,这个方法会在每个 App 包加载时执行。•在 handleLoadPackage 里面我们通过 loadPackageParam.packageName
获取到了 App 包名,然后判断了一下是否是我们当前 App 的包名。这里包名可以是任意 App 的包名,不一定是当前 App 的包名,只不过是因为我们为了方便,在当前 App 里面定义了一个逻辑,所以这里我们 Hook 的是当前 App 的逻辑,才填写了当前 App 的包名。•利用 loadClass 方法并指定 class 的路径可以动态地加载这个 class,是一个 Class 对象。•利用 XposedHelpers 提供的 findAndHookMethod 方法可以从 class 里面查找对应的方法,这里需要传入的参数分别为 Class 对象,方法名称,方法的参数类型,处理方法。这里方法的参数类型是有几个写几个,比如这里 showMessage 有两个 int 类型的参数,这里就需要顺次写两个 int.class,如果是其他的类型也是分别写类型再加 class 的声明。•XC_MethodHook 里面定义了我们施行 Hook 的真正逻辑,这里通常可以实现两个方法,分别叫做 beforeHookedMethod 和 afterHookedMethod,分别代表在被 Hook 方法(这里为 showMessage)执行前的操作和执行后的操作,同时二者都有一个 MethodHookParam 类型的参数,里面包含了方法执行的参数和结果等信息。•一般来说 beforeHookedMethod 方法可以用来修改被 Hook 方法的参数内容,或者直接定义被 Hook 方法的运行流程。afterHookedMethod 可以用来对被 Hook 方法进行后处理,比如对被 Hook 方法的结果进行拦截、保存、转发、修改等操作。•XposedBridge 里的 log 方法可以记录将 Log 信息记录到 Xposed Installer 里面,我们通过 Xposed Installer 里面日志页面就可以看到对应结果,方便做调试使用。
这里我们先对 beforeHookedMethod 处理,对 param 的 args 属性做了处理,这里的 args 属性是一个列表,就是 showMessage 方法的调用参数,因为我们之前传入的是 1 和 2,所以这里 args 属性的值其实就是 [1, 2]
,那这里我们是把它改写了一下,把第一个内容改写成了 2,那这里 args 其实就会变成 [2, 2]
了
好,现在 Hook 的逻辑我们已经实现好了,还差最后一步,那就是告诉 Xposed 我们的 Hook 逻辑是定义在哪里了,我们需要新建一个 Xposed 入口文件。
在 main 文件夹新建一个 assets folder,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/ab318db298e1e1e007e4e60b14b595a2.png)
然后在 assets 文件夹下新建一个 xposed_init 文件,不需要有任何后缀,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/87a55887ed08f3d047dd66da9d783784.png)
其内容就把 Hook 的这个类的路径写好就好了,内容如下:
com.germey.xposedtest.HookMessage
好,这样保存之后,Xposed 就能自动读取这个 xposed_init 文件来执行我们自定义的 Hook 逻辑了。
最后,我们就来重新运行看下效果吧。
记得安装完成之后重启一下 Xposed,否则是没有效果的。
重启模块之后,点击 TEST 按钮,可以看到就出现了如下效果,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/9a9616c31a85c66bf4e34cbbd9e0c5d2.png)
这里可以看到,最后的运行效果就不一样了,出现了「x + y = 4」的这个现象,这说明通过 beforeHookedMethod 的定义,我们成功把 args 的第一个参数,也就是 x 修改成了 2,而第二个参数没有修改,还是 2,最后就相当于 showMessage 调用之前,两个参数就被修改成了 2 和 2,最后答案就是 4 了。
这下我们就体会到了 beforeHookedMethod 的用法了。
刚才我们是用了 beforeHookedMethod 来实现了参数替换的效果,接下来我们再来体会一下 afterHookedMethod 的用法,它可以对方法的返回结果进行后处理,比如这里我们把 afterHookedMethod 修改为如下内容:
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("Called afterHookedMethod");
param.setResult("Hooked");
}
这里我们增加了 param 的 setResult 方法的调用,利用它我们可以直接将方法的返回值修改掉。
重新运行这个模块,然后重启手机,同样地还是点击 TEST 按钮,这时候我们发现其结果就变成了如下内容,如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/f806df1608bfdc3e02060bf32165d7ab.png)
可以看到最后方法的返回结果被修改了,这正是 afterHookedMethod 所起的作用。
由此,我们通过 beforeHookedMethod 和 afterHookedMethod 的修改可以实现 showMessage 在调用前和调用后的修改。
好,最后我们再来看下日志,打开 Xposed Installer 的日志页面,可以看到内容如图所示:
![null](https://i-blog.csdnimg.cn/blog_migrate/6b6ed8eacbc8785fc1bb39858d0f1755.png)
可以看到这里就输出了我们用 XposedBridge 的 log 方法输出的内容。
OK,到此为止我们就实现了 Xposed 的 Hook 逻辑了,通过这个案例大家应该就能体会到 Xposed 的效用了。
总结
本文我们介绍了 Xposed 的基本理念,并通过案例来实现了 Xposed Hook App 的流程。
Xposed 的功能非常强大,利用它,App 尽在我们掌控之中,为所欲为不再是奢望。
参考链接
•https://forum.xda-developers.com/showthread.php?t=3034811•https://repo.xposed.info/module/de.robv.android.xposed.installer•https://www.freebuf.com/articles/terminal/189021.html•https://blog.csdn.net/fuchaosz/article/details/53143216•https://www.jianshu.com/p/655a07b687ec
后文我们再介绍一下 Xposed 对于真实 App 的 Hook 操作,敬请期待。
崔庆才丨静觅
隐形字
同名公众号「崔庆才丨静觅」
在这里分享自己的一些经验、想法和见解。
长按识别二维码关注
好文和朋友一起看~