打造自己的 Xposed 插件

点击查看高级玩机(搞机)Hook 工具 Xposed、EdXposed、VirtualXposed、Magisk、TaiChi(太极)、Xpatch 介绍

Xposed 插件和普通的 Android 应用一样,对于系统来说它们都是应用。只需要在 Xposed 插件中添加一些特殊的 meta data 和文件,Xposed 框架就可以识别出当前的应用是一个 Xposed 插件。

如果本文内提供的链接无法访问,表示可能需要梯子。

如果手上没有可以支持 Xposed/VirtualXposed 的设备,或者没有太极的无极码,那么开发调试 Xposed 插件可能很不方便,可以下载 Android 虚拟机如:VMOS、光速虚拟机、51 虚拟机等进行调试。

1、创建工程

像创建普通应用时一样新建一个工程,唯一的区别就是如果你的插件不需要提供给用户可见的页面,可以选择 “No Activity”,或者在创建后将默认的 Activity 删除即可。

2、添加 Xposed Framework API

点击查看官方教程

在 app/build.gradle 的 dependencies 闭包中添加依赖,如下:

dependencies {
    compileOnly 'de.robv.android.xposed:api:82'
    compileOnly 'de.robv.android.xposed:api:82:sources' // API 文档
}

注意:这里一定要使用 compileOnly 而不是 implementation,否则可能会出现问题。像 Android Framework 一样,Xposed Framework 的真正实现是由用户给设备安装 Xposed 框架时提供的,我们开发过程中添加的依赖只是用于编译。

如果你想支持 Lollipop 之前的 ROM,你只能使用 API 版本 53。

3、配置 AndroidManifest.xml

文章开头我们就提到 Xposed 通过查找具有特殊 meta data 标志的应用程序来确认当前应用是否是 Xposed 插件。

我们需要在 AndroidManifest.xml 文件中,添加【xposedmodule】、【xposeddescription】和 【xposedminversion】三个 meta-data,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xiaomai.myxposed">

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="My Xposed Plugin" />
        <meta-data
            android:name="xposedminversion"
            android:value="82" /> <!--注意:建议这个值要和当前使用的 xposed 框架版本相同-->
    </application>

</manifest>

4、选择 Hook 的入口点

一个插件模块可以从 3 个入口点来进行 Hook,从而调用模块中的函数,选择哪一个取决于要修改的内容,这三个入口点分别是:

  • Android 系统启动时,需实现 IXposedHookZygoteInit.java 接口;
  • 应用程序即将启动时(程序的每个进程启动时都会进入该入口),需实现 IXposedHookLoadPackage.java 接口;
  • 应用程序资源初始化,需实现 IXposedHookInitPackageResources 接口。

以上 3 个入口点对应的接口类的父类是 IXposedMod

以应用程序即将启动时为例:

创建一个类实现接口 IXposedHookLoadPackage.java,如下:

package com.demo.myxposed;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class MyPlugin implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("Hello World! I'm from " + lpparam.processName + "|" + lpparam.packageName);
    }
}

XposedBridge.log 方法将会在 Logcat 中以标准的日志形式输出,tag 为 Xposed,同时也会写入到 /data/data/de.robv.android.xposed.installer/log/debug.log 文件中,通过 Xposed Installer 很方便查看。

注意:所有入口类一定要声明为 public,否则运行时会异常。

更多 Xposed API 的使用方法,点击查看官方 API 文档

5、为 XposedBridge 指定插件入口点

Android 开发者都知道普通的应用的入口点需要在 AndroidManifest.xml 中,给入口 Activity 添加指定的 <intent-filter> 和 <action> 即可。

那么 Xposed 框架怎么知道 Xposed 插件该从哪个开始运行呢?同样也需要按照协议声明一个入口给 XposedBridge 使用,具体步骤如下:

assets 目录下创建一个名字为 xposed_init 的文本文件,在这个文件中每行声明一个全路径类名。

在我们的例子中,入口类是 MyPlugin.java,所以 xposed_init 文件的内容如下:

com.demo.myxposed.MyPlugin

现在一切准备就绪,可以运行看下效果了。

注意:如果你的 Android Studio 开启了 Instant Run,一定要先关闭 Instant Run。

安装好插件后,打开 Xposed Installer,开启该插件,然后重启设备,即可看到如下日志:

I/Xposed: -----------------
I/Xposed: Starting Xposed version 89, compiled for SDK 25
I/Xposed: Device: vmos (vmos), Android version 7.1.2 (SDK 25)
I/Xposed: ROM: NZH54D
I/Xposed: Build fingerprint: Android/aosp_arm64/vpro_arm64:7.1.2/NZH54D/frank04270949:user/release-keys
I/Xposed: Platform: arm64-v8a, 32-bit binary, system server: no
I/Xposed: SELinux enabled: no, enforcing: no
I/Xposed: -----------------
I/Xposed: Added Xposed (/system/framework/XposedBridge.jar) to CLASSPATH
D/AndroidRuntime: >>>>>> START de.robv.android.xposed.XposedBridge uid 2000 <<<<<<
I/Xposed: Detected ART runtime
I/Xposed: Found Xposed class 'de/robv/android/xposed/XposedBridge', now initializing
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/art: Starting a blocking GC Xposed
I/Xposed: Loading modules from /data/app/com.demo.myxposed-2/base.apk
I/Xposed:   Loading class com.demo.myxposed.MyPlugin
I/art: Starting a blocking GC Xposed
I/Xposed: Hello World! I'm from android|android
I/Xposed: Hello World! I'm from android|com.android.providers.settings
I/Xposed: Hello World! I'm from com.android.inputmethod.latin|com.android.inputmethod.latin
I/Xposed: Hello World! I'm from com.android.systemui|com.android.systemui
I/Xposed: Hello World! I'm from com.android.phone|com.android.phone
I/Xposed: Hello World! I'm from android|com.android.server.telecom
I/Xposed: Hello World! I'm from com.android.phone|com.android.providers.telephony
I/Xposed: Hello World! I'm from com.android.settings|com.android.settings
I/Xposed: Hello World! I'm from android|com.android.location.fused
I/Xposed: Hello World! I'm from android.ext.services|android.ext.services
I/Xposed: Hello World! I'm from android|com.vmos.romex
I/Xposed: Hello World! I'm from com.iflytek.inputmethod.oem|com.iflytek.inputmethod.oem
I/Xposed: Hello World! I'm from android.process.media|com.android.providers.downloads
I/Xposed: Hello World! I'm from com.android.providers.downloads|com.android.providers.media
I/Xposed: Hello World! I'm from com.android.launcher3|com.android.launcher3
I/Xposed: Hello World! I'm from com.android.keychain|com.android.keychain
I/Xposed: Hello World! I'm from com.iflytek.inputmethod.assist|com.iflytek.inputmethod.oem
I/Xposed: Hello World! I'm from com.UCMobile:push|com.UCMobile
I/Xposed: Hello World! I'm from com.android.defcontainer|com.android.defcontainer
I/addFolderItem: item:-1-Xposed Installer
D/addFolderItem: addItemToDatabase item:Xposed Installer  container:-100
I/addFolderItem: 3sBgItemsIdMap0-Xposed Installer-4-0
I/addFolderItem: bindItems:Xposed Installer-0-4-0--100
I/addFolderItem: bindItems-Mid:Xposed Installer-0-4-0--100
I/addFolderItem: bindItems  item:Xposed Installer   forceAnimateIcons:falsei:4
D/shortInfo: getIcon info.title:Xposed Installer-null
I/Xposed: Hello World! I'm from com.android.managedprovisioning|com.android.managedprovisioning
I/Xposed: Hello World! I'm from com.android.onetimeinitializer|com.android.onetimeinitializer

分析上述日志后,可以看出大部分应用时系统应用,开机时即启动也很正常。但是也会发现 UC 浏览器启动了推送通知进程。

到此一个简单的插件创建与测试流程就完成了。接下来就是根据自己的想法开始施展功力了~~

有助于插件编写的一些小 Tips

  • 反编译目标应用
  • 如果是要 Hook 系统的行为,可以查看 AOSP 代码
  • 你只能 Hook Java 类中的方法,因此可以选择把要修改的代码添加到被 Hook 的方法运行之前、运行之后或者直接替换方法。
  • Hook 方法支持优先级,如果目标方法被多次 Hook,将按优先级顺序分别执行。

常见问题及解决办法

1、Cannot load module: The Xposed API classes are compiled into the module’s APK.

E/Xposed:   Cannot load module:
E/Xposed:   The Xposed API classes are compiled into the module's APK.
E/Xposed:   This may cause strange issues and must be fixed by the module developer.
E/Xposed:   For details, see: http://api.xposed.info/using.html

从 Xposed 提供的错误信息中已经可以得知问题的原因了:Xposed API 被编译到了插件中。文章前面也提到过,插件项目中导入的 Xposed API 依赖只是供给开发者开发和打包过程中编译使用的。所以,需要修改 build.gradle 中依赖 Xposed API 的配置为 compileOnly。

2、xxx is not accessible from java.lang.Class<de.robv.android.xposed.XposedInit>

E/Xposed:     Failed to load class com.demo.myxposed.MyPlugin
    java.lang.IllegalAccessException: java.lang.Class<com.demo.myxposed.MyPlugin> is not accessible from java.lang.Class<de.robv.android.xposed.XposedInit>
        at java.lang.Class.newInstance(Native Method)
        at de.robv.android.xposed.XposedInit.loadModule(XposedInit.java:546)
        at de.robv.android.xposed.XposedInit.loadModules(XposedInit.java:466)
        at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:96)

出现该问题的原因是入口类不是 public 的,文章前面提到过入口类一定要声明为 public。所以把入口类修改为 public 即可。

3、Cannot hook abstract methods

E/Xposed: java.lang.IllegalArgumentException: Cannot hook abstract methods: public abstract android.content.pm.PackageInfo android.content.pm.PackageManager.getPackageInfo(java.lang.String,int) throws android.content.pm.PackageManager$NameNotFoundException
        at de.robv.android.xposed.XposedBridge.hookMethod(XposedBridge.java:209)
        at de.robv.android.xposed.XposedHelpers.findAndHookMethod(XposedHelpers.java:187)
        at com.demo.myxposed.MyPlugin.handleLoadPackage(MyPlugin.java:17)
        at de.robv.android.xposed.IXposedHookLoadPackage$Wrapper.handleLoadPackage(IXposedHookLoadPackage.java:34)
        at de.robv.android.xposed.callbacks.XC_LoadPackage.call(XC_LoadPackage.java:61)
        at de.robv.android.xposed.callbacks.XCallback.callAll(XCallback.java:106)
        at de.robv.android.xposed.XposedInit$4$1.beforeHookedMethod(XposedInit.java:176)
        at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:340)
        at com.android.server.SystemServer.startBootstrapServices(<Xposed>)
        at com.android.server.SystemServer.run(SystemServer.java:342)
        at com.android.server.SystemServer.main(SystemServer.java:224)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1082)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:956)
        at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:107)

不能直接 Hook 抽象类/接口,而是要 Hook 具体的实现类,这点跟我们平时写代码一样,不能直接创建一个抽象类/接口的实例。所以,改成 Hook 具体的实现类就可以了。

更多 Xposed 插件开发官方文档

  • https://github.com/rovo89/XposedBridge/wiki

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注