前言
最近在处理微信公众号的数据采集,需要逆向获取微信你公众号的消息推送,接触到了传说中的逆向神器Xposed。据说可以用它为所欲为,哈哈哈,做网络安全的不会让你得逞。但是虽然做不到为所欲为,至少可以在网络安全师的草场上打马悠行一遭,顺便捡捡数据金矿。话不多说,本文先带你了解一下什么是Xposed。
本文主要参考崔庆才文章:https://blog.csdn.net/cqcre/article/details/106561367
Xposed Framework介绍
Xposed 框架是一款可以在不修改 APK 的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。Xposed不是个新鲜工具,早年就有各种应用场景,比如刷系统,获取root权限,更改UI,开发插件……在这些场景的催化下逐渐形成了Xposed生态,目前已经有相当多种类的插件被开发出来,这也归功于开源,尤其是Android的开源。
使用xposed需要有一定的Android基础,基本上需要有读懂源码的能力,xposed本身并不复杂,复杂点在于反编译后从源码中获取到一个口子,然后插根管子进去(hook),要么是改变数据变量,要么是截取数据。所以玩Xposed有两个关键点,一个是找那个口子,另外一个是插根管子。
Xposed原理
Android基于Linux,第一个启动的进程自然是init进程,该进程会启动所有Android进程的父进程——Zygote(孵化)进程,该进程的启动配置在/init.rc脚本中,而Zygote进程对应的执行文件是/system/bin/app_process,该文件完成类库的加载以及一些函数的调用工作。在Zygote进程创建后,再fork出SystemServer进程和其他进程。
而Xposed Framework呢,就是用自己实现的app_process替换掉了系统原本提供的app_process,加载一个额外的jar包,然后入口从原来的:com.android.internal.osZygoteInit.main()被替换成了:de.robv.android.xposed.XposedBridge.main(),然后创建的Zygote进程就变成Hook的Zygote进程了,而后面Fork出来的进程也是被Hook过的。这个Jar包在:
/data/data/de.rbov.android.xposed.installer/bin/XposedBridge.jar
这里从另外一个地方找到了Xposed原理讲解比较通俗:
https://sspai.com/post/40121
Xposed 是一个 Android 平台上的动态劫持框架,通过替换手机上的孵化器 zygote 进程为 Xposed 自带的 zygote,使其在启动过程中加载 XposedBridge.jar,模块开发者可以通过 jar 提供的 API 来实现对所有的 Function 的劫持,在原 Function 执行的前后加上自定义代码。
说成人话就是 Android 上所有正在运行的应用程序都是通过一个万物之主 —— Zygote 创造出来的,但是这个万物之主不听我们的话呀,Xposed 就是把这个万物之主换成自个家的,然后用一本天书(就是前面的 XposedBridge.jar)和他沟通,就可以实现对于系统上任意程序任意数据和行为运行前后的修改,至于我们不会写天书或者读懂天书也没关系,只要我们下载已经写好的天书(模块)就可以完成特定的需求。
xposed原理
举个实际的例子,在某个 App 的界面被系统渲染出来之前,Hook(勾住)负责渲染界面的函数,把 App 传递过来的参数(例如红色)改成绿色,最后 App 界面就可以成功变成你想要的颜色啦(当然是选择原谅她)。
xposed修改
Xposed环境说明
Xposed需要最高权限即Root权限,因此在不明源码的情况下尽量不要使用别人写出来的代码,特别注意不要在生产环境或者自己常用手机系统上搞,容易被人当枪使还泄露自己隐私。
相关文档:
环境
首先需要在电脑端安装好Android环境,另外需要root环境,可以用真机刷个root环境,也可以用Android模拟器,因为它自带root环境。
Android环境配置请参考以下的教程:
https://juejin.im/post/5d255101e51d4556d86c7b4f
https://juejin.im/post/5d5eb3ed5188252ae10be138
本次暂且在网易Android模拟器mumu上操作吧。
安装Xposed Installer
要安装 Xposed 我们需要借助于一个 App,叫做 Xposed Installer,它就是用来安装 Xposed 框架的,利用它我们可以下载和安装 Xposed 框架,同时还能查看和管理 Xposed 模块,还能查看一些 Xposed 框架输出等日志信息等。
在官网上可以下载Xposed Installer:
https://repo.xposed.info/module/de.robv.android.xposed.installer
其中明确指定5.0以上Android系统的下载
下载Xposed Installer
下载链接:https://forum.xda-developers.com/attachment.php?attachmentid=4393082&d=1516301692
安装完Xposed Installer后需要安装Xposed,下载更新并激活后重启设备后即可使用
激活Xposed框架
Xposed开发
其实本质上来说,它就是一个安卓 App,开发一个安卓模块其实流程上就和开发一个安卓 App 差不多,只不过相比 App 开发来说多了下面四个步骤:
- 1.这个 App 里面要加上一些标识,标明这个 App 是一个 Xposed 模块,以便安装之后 Xposed 框架可以识别出来。
- 2.这个 App 里面需要引入 Xposed 的 jar 包,从而能实现 Hook 操作。
- 3.App 里面定义一些 Hook 操作,可以对本 App 或其他的 App 的逻辑进行修改。
- 4.定义完这些 Hook 操作逻辑之后,还需要告诉 Xposed 框架哪些是我们自己定义的 Hook 操作逻辑,以便 Xposed 执行这些 Hook 逻辑。
就这么四步,这四步这么来实现呢,下面我们来一步步实现。
首先在Android studio上创建一个 Empyty Activity项目。
创建项目
接下来给项目起一个名称,指定项目存储位置。
项目命名
创建完后会自动生成一个MainActivity
MainActivity
第一步就是要做一些标识符,让Xposed框架认识这个模块。
我们打开 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 标签并列,最终内容如图所示:
image.png
这里指定了三个 meta-data,分别为:
•xposedmodule:这里设置为 true,代表这是一个 Xposed 模块。
•xposeddescription:模块的描述,填写模块描述就好,就是一个字符串。•xposedminversion:模块运行要求的 Xposed 最低版本号,这里是 53。
定义好这三个内容之后,把这个 App 安装到手机,Xposed 就能识别出这个 App 是一个 Xposed 模块了。
我们点击运行按钮,在手机上运行这个 App。
在模拟器上会有如下显示:
image.png
此时xposed模板里面也有了XposedActivity模块
image.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 便会自动下载和安装,如图所示:
image.png
好,现在 Xposed 的 SDK 就安装成功了,下面我们就能使用里面的方法实现逻辑的 Hook 了。
那怎么来实现逻辑的 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="click me"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
接下来再修改下 MainActivity.java 文件,内容如下:
package com.example.xposedactivity;
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 按钮,可以看到如下运行结果:
image.png定义好了基本程序之后呢,下一步我们就来用 Xposed 进行 Hook 吧,我们在 MainActivity.java 同级新建一个 Java Class,文件名一定要和Class名一样,此处就因为没有命名成同样的找了半天问题,浪费时间内容如下:
package com.example.xposedactivity;
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.example.xposedactivity")) {
XposedBridge.log("Hooked com.example.xposedactivity Package");
Class clazz = loadPackageParam.classLoader.loadClass(
"com.example.xposedactivity.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,如图所示:
批注 2020-08-13 134748.png
然后在 assets 文件夹下新建一个 xposed_init 文件,不需要有任何后缀。内容就把 Hook 的这个类的路径写好就好了,内容如下:
com.example.xposedactivity.HookMessage
这样保存之后,Xposed 就能自动读取这个 xposed_init 文件来执行我们自定义的 Hook 逻辑了。
最后,我们就来重新运行看下效果吧。
记得安装完成之后重启一下 Xposed,否则是没有效果的。
重启模块之后,点击 click me 按钮,可以看到就出现了如下效果,如图所示:
image.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 方法的调用,利用它我们可以直接将方法的返回值修改掉。
重新运行这个模块,然后重启手机,同样地还是点击 click me按钮,这时候我们发现其结果就变成了如下内容,如图所示:
image.png
可以看到最后方法的返回结果被修改了,这正是 afterHookedMethod 所起的作用。
由此,我们通过 beforeHookedMethod 和 afterHookedMethod 的修改可以实现 showMessage 在调用前和调用后的修改。
好,最后我们再来看下日志,打开 Xposed Installer 的日志页面,可以看到内容如图所示:
image.png
可以看到这里就输出了我们用 XposedBridge 的 log 方法输出的内容。
到此为止我们就实现了 Xposed 的 Hook 逻辑了,通过这个案例大家应该就能体会到 Xposed 的效用了。
网友评论