Xposed模块开发基本方法记录

Xposed模块开发基本方法记录

  由于某些课程实验的要求,需要通过xposed框架对某应用进行hook操作,笔者选用了开源且免费的xposed框架进行实现。虽然网上存在一些利用xposed实现特定功能的文章资源,但大多均将xposed模块的构建作为一个小节内容一笔带过,而且介绍的内容随着考虑的因素、使用的编辑环境不同也大有区别,使得笔者在实际构建过程中往往提心吊胆,出现了错误也不知道如何去改正。故而这里特将笔者最近摸索和学习到的简单xposed构建的知识记录,以供查阅和参考。

Xposed 框架

  想要使用Xposed模块实现特定的功能,必须依赖Xposed框架,正如 apk 的运行需要Android系统提供支持一样,Xposed模块任务的完成也离不开Xposed框架的支持,Xposed框架提供了Xposed模块运行所必需的环境和功能接口,同时也可以对系统上已安装的模块进行禁用、卸载等管理。故而在想通过Xposed模块完成一定任务之前,必须安装Xposed框架。

  Xposed框架Installer下载地址:http://repo.xposed.info/module/de.robv.android.xposed.installer,安装的过程可参考网络资料。

  注意:由于Xposed框架安装时需要将系统文件夹/system/bin的部分文件进行替换和备份,故而手机需要具备root权限

Xposed 模块

  下面以Android Studio 3.0.1为例,总结下简单的Xposed模块的编写所需的配置和注意事项。

  项目创建

  Xposed模块实际上是作为一种较为特殊的apk安装在系统上,由于Xposed 模块的功能通常借助于其他应用和系统资源实现,其一般没有应用界面,故而创建一个没有活动的项目即可。

  A.在Android Studio中,点击 file -> new -> new project 进行新项目的创建。指定应用名称、公司域名、存储位置和包名等信息,这里需要注意应用名和包名这两个名字;

  Xposed模块开发基本方法记录

  B.勾选 Phone and Tablet 选项,选择该 Xposed 模块( 实际就是apk)将要运行的 Android 环境所对应的 API。注意这里的API指定的是Android环境与apk之间交互的接口,只有该API与之后apk实际运行的Android环境相匹配,apk才能正确的安装和运行。如笔者的 Xposed 模块将要运行在 Android 4.4上,则应该选择 Android 4.4 对应的API 19.

  Xposed模块开发基本方法记录

  C.在接下来的界面中,选择“ add no activity ”即不添加活动( Xposed 模块不需要活动 ),即可完成项目的创建。

  项目配置

  Xposed模块被视为一种特殊的apk,故而在创建项目之外,还需要针对其进行 Xposed 模块相关的配置。

  A. 切换目录结构。点击界面左上方的竖式 Project,展开项目界面,将项目结构从 Android 切换至 Project 模式,方便之后的编辑;

  Xposed模块开发基本方法记录

  B. 添加 Xposed API依赖。Xposed 模块的功能借助 Xposed 框架实现,Xposed 模块通过对应的 Xposed 框架 API 来使用 Xposed 框架提供的功能。想要使用 Xposed 框架 API,则必须提供对应的库( 名为 XposedBridge API jar)的路径等信息。(以下信息来自 Using the Xposed Framework API )

    *针对 Android Studio

    Android Studio 中使用gradle进行项目的构建,故而想要使用 Xposed 框架 API 对应的库,则需要在 gradle 的配置文件中进行指定。在 应用名(test) -> app -> build.gradle 中加入以下内容:

1      repositories {  //通常新项目的 build.gradle 中不包含有repositories块,直接在空白处增加1-3行即可
2          jcenter();       }
3  
4      dependencies {  //通常 build.gradle 文件中已包含dependncies块,所以只需将第6行的内容添加进已有的 dependencies 中即可
5          provided 'de.robv.android.xposed:api:53'
6        provided 'de.robv.android.xposed:api:53:sources'   //该行是可选的,用于下载API相关的文档等信息
7  }   

    其中,第2行表示将 jcenter 作为代码仓库,可在上面引用开源项目,而第 6 行则指定引用项目的项目名和版本信息。

    第6行的声明需要注意两点:  

    a) 使用 provided 关键字而不是 compile,后者会将引用的 Xposed 框架 API 类打包至生成的 apk 中,而这些类在安装好的 Xposed 框架中是已经存在的,所以可能会产生冲突,而 provided 关键字则仅保留对 API 的引用,实现具体功能的类则由安装好的 Xposed 框架提供;     

    b)使用系统对应的 Xposed 框架 api 版本,不同的 Xposed 框架 API 在不同的 Android 环境中发挥作用。如Android 4.x 环境则只能选择 API 53。

    

    *针对 eclipse

    eclipse 环境下无法通过指定 jcenter 中的开源项目来实现对其的引用,所以必须在实际的项目中手动添加所需引用的库文件 XposedBridge API jar 的路径,在这里手动下载所需的 API 版本,将其复制到项目的文件夹下( 注意不要放置在项目本身已经有的 lib / libs 文件夹下,原因与Android Studio环境下使用 provided 而不是 compile 一致 ),选中 jar 包 右键 -> Build Path -> add to build path 即可。

  

  C. Android Studio 环境下需要禁用 Instant run 。在 File -> Settings -> Build, Execution, Deployment -> Instant Run 中取消其勾选,否则源程序中的类会由一个 stub 应用加载,而不是直接包含在 apk 文件中,而 Xposed 框架目前无法处理这样的情况。

  D. 修改 AndroidManifest.xml 文件中的属性。在 应用名(test) -> app -> src -> main 文件夹中找到 AndroidManifest.xml,修改其中的属性,使得我们最终生成的 apk 能够被 Xposed 框架识别。请保证最终文件的部分内容为以下格式。

    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme" >    // App相关的信息

    <meta-data
            android:name="xposedmodule"    //标志该 apk 为一个 Xposed 模块,供 Xposed 框架识别
            android:value="true" />
    <meta-data
            android:name="xposedminversion"   
            android:value="53" />       //xposed最小版本号,请与(2)步中选用的api版本保持一致
    <meta-data
            android:name="xposeddescription"
            android:value="hook a function" /> //针对该模块的描述,会在Xposed框架的模块管理界面中显示出来,便于模块管理

    </application>

  完成上述步骤后,则可将项目文件打包成 apk ( Android Studio 下打包 apk 的简单步骤可以参见这里),并在手机中安装。安装成功后,在 Xposed 框架的模块管理界面会出现该模块对应的管理项,证明对 Xposed 模块的配置是成功的。勾选对应的模块,并重启设备,重启之后该模块则开始发挥功能。当然目前我们并没有实现任何功能。

  

  简单示例

  在完成 Xposed 框架的配置后可在该项目中进行框架功能的编写。在 应用名(test) -> app -> src -> main -> java 目录下,可以看到 Android Studio 已经自动根据我们在创建项目时指定的包名生成了一个包( package )的条目,如笔者项目中的包名为 xposed.test(见创建项目时的界面截图),选中该包右键 -> new -> java  ->class ,创建自己的一个 class ,如 test.class 。用户可在该 class 中实现简单的xposed hook功能。

  ( 以下部分不涉及具体的实现原理,仅提供函数的简单功能的描述,更多关于Xposed 框架API的功能可在这里查看)

  一个 Xposed 模块可以有多个不同的入口,其中实现的方法既可以在 Android 系统启动时被调用,也可在一个应用程序包被加载时被调用,想要在不同的时期被调用,则该功能类需要实现不同的接口。

    Xposed公共接口
    package:de.robv.android.xposed.IXposedHookZygoteInit       
    interface :IXposedHookZygoteInit   //在Android系统启动时被调用,作用于初始的zygote进程,可用于实现应用于所有应用的hook

    package:de.robv.android.xposed.IXposedHookLoadPackage
    interface:IXposedHookLoadPackage  //当指定应用被加载时被调用,一般用于hook特定应用的方法

    package:de.robv.android.xposed.IXposedHookInitPackageResources
    interface:IXposedHookInitPackageResources //指定应用的资源进行初始化时被调用,一般用于资源的替换

   

  以实现 IXposedHookLoadPackage 接口的模块为例,实现一个简单的hook功能。

  通常使用 findAndHookMethod 方法来定位待 hook 的类中的方法(官方解释在这里)

    findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
     className:被hook的方法所在类的完整名称,包括 包名 + 类名
     classLoader:可通过 lpparam.classLoader 获得
      methodName:被hook的方法的名,注意如有混淆,则应该用混淆后的名字
      object... 与为被hook的方法的参数对应
      Callback  :指定该方法被调用时,需要被执行的回调

  例如,想要hook如下代码,即hook包 com.test.example 中的类 a 下的 b 方法

    package com.test.example
    public class a{
         public int b(String s, int i, MyClass m) 
         {}
    }    

  则对应的 findAndHookMethod方法应该写作

   findAndHookMethod("com.test.example.a", lpparam.classLoader,"b", String.class, int.class, "com.test.example.MyClass", new XC_MethodHook() {
   @Override
   protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
     do somthing
   }
 });
    这里需要注意:
    (1)对于待hook函数中的java自带类型的参数,如 int 和 String,则应使用 int.class 和 String.class 作为findAndHookMethod的参数
    (2)若待Hook函数中包含有形如 MyClass 这样的自定义类型的参数,则直接使用该类型的完整路径名即可,如 "com.test.example.MyClass" 

  在 findAndHookMethod 方法的参数中,直接定义了一个 XC_MethodHook 回调,其中一般有两个方法可供使用者重新定义。

   @Override
      protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
          // 该方法在被hook函数之前被调用
      }
     @Override
      protected void afterHookedMethod(MethodHookParam param) throws Throwable {
          // 该方法在被hook函数之后被调用
      }

  以下代码实现了微信的一个日志函数的简单 hook 。

 1 package xposed.xposed_wechat_log;  //实现的类所在的包
 2 
 3 import android.util.Log;       //系统提供的Log方法的包
 4 import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;  //findAndHookMethod方法所在的包
 5 import de.robv.android.xposed.IXposedHookLoadPackage;           
 6 import de.robv.android.xposed.XC_MethodHook;
 7 import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
 8 
 9 public class test implements IXposedHookLoadPackage {  //test类实现了IXposedHookLoadPackage接口
10     @Override
11     public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {  // handleLoadPackage 在任意包被加载时被调用,参数lpparam,包含有加载的 app 的信息
12         if ( lpparam.packageName.equals("com.tencent.mm")) {             // 可通过 lpparam 中包含的包信息对包进行筛选,如这里表示 com.tencent.mm 包加载时,才执行下一步操作
13     findAndHookMethod("com.tencent.mm.sdk.platformtools.x",lpparam.classLoader, "d" , String.class, String.class, Object[].class,
14             new XC_MethodHook() {
15                 @Override
16                 protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
17                     String str0  = (String) param.args[0];              //可通过 param.args[i]获得 hook 方法的参数
18                     String str1  = (String) param.args[1];
19                     Object[] obj2= (Object[]) param.args[2];
20                     String  str = obj2 == null ? str1 : String.format(str1, obj2);
21 
22                     if( str==null)
23                         str = "";
24 
25                     Log.e( "Xposed_hook_d"+str0,str);
26                     super.beforeHookedMethod(param);
27                 }
28             });
29      } 
30   } 
31 }
simple_hook

  

  完成上述模块功能的编写后,需在 应用名(test) -> app -> src -> main 目录下新建一个 assets 目录,并在该目录下建立一个名为 exposed_init 的文本文件。该文件中记录模块中所有实现了Xposed 功能接口的类的完整路径名,每一行书只写一个这样的路径。

  如在上述实例模块中,test 类实现了接口 IXposedHookLoadPackage,则 exposed_init 文件中应该有以下内容:

    xposed.test.test  //其中 xposed.test 为包名,test为实现接口的类名

  所有实现了 Xposed 接口的类均要在该文件中记录,以供 Xposed 框架进行处理。

  之后即可打包完成的项目,将其安装至设备上进行验证了。

参考资料:

  1. [TUTORIAL]Xposed module development详细的xposed模块开发示例

  2. Using the Xposed Framework API关于如何在模块中使用Xposed框架提供的API

  3.  Development tutorial简单的xposed模块示例