Xposed 框架 hook 简介 原理 案例 MD

Markdown版本笔记 个人GitHub首页 个人博客 个人微信 个人邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

Xposed

官方教程
Xposed框架中文站
Xposed插件官方网站
FAQ,建议开发前仔细阅读一遍php

framework下载地址
xposed依赖的maven地址在jcenter上的下载地址
做者rovo89的GitHub主页,里面有5个项目,包括XposedInstaller、Xposed、XposedBridge、XposedTools、android_art。html

因为Android 5.0以上采用ART,而5.0如下默认采用Dalvik,因此是有两个版本的Xposed,一些下载连接:
Android 4.0.3-4.4官网地址Android 5.0以上官网地址(访问很是慢)
Xposed_installer_3.1.4百度网盘下载地址Xposed_installer_3.1.5官网下载地址java

Xposed 是什么

Xposed 是一款能够在不修改APK的状况下影响程序运行(修改系统)的框架服务。基于它能够制做出许多功能强大的模块,且在功能不冲突的状况下同时运做。好比:直接把APP的界面改为本身想要的样子(好比修改文字、背景),去掉界面里不喜欢的东西(好比广告、弹窗),自动抢红包,消息防撤回,步数修改、修改定位等等,简直酷得不行,网上有不少插件做者开发出来的优秀插件。android

Xposed 理论上可以hook到系统任意一个Java进程,能够说Xposed真的能够随心所欲,任何事情均可以作!git

重要提醒:使用Xposed是须要Root权限的!
PS:因为Xposed是从底层hook,因此须要root权限,而且每次更新都要从新启动设备github

背景介绍

Xposed并非什么新东西了,好几年前就有了,之前看到搞机(基)的人都以为很牛逼哄哄,刷系统,root下,改下系统UI,用各类各样的插件模块改什么什么,屌得不行。api

真正开始学的时候,其实Xposed并无想象中那么复杂,原理和相关的API都很简单,难的是逆向,怎么去实现你要Hook的功能:反编译,调试输出,堆栈跟踪,抓包等等,在这个过程当中你须要去分析不少不少东西,猜想调试,有时候折腾几天可能毫无进展,不过也会收获更多,好比你本身开发APP的时候也会慢慢开始考虑安全相关的东西。安全

The most important thing is finding good methods to hook微信

插件用起来是挺爽的,不过呢,由于Xposed拥有最高权限,若是不法分子在插件里植入了恶意代码,好比登陆劫持,偷偷采集你的帐号密码发送到他们的手里,若是涉及到了金钱,就很恐怖啦,因此在使用Xposed插件的时候,尽可能选那些开源的,并进行代码review,看是否存在恶意代码,再进行安装体验。架构

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


官方教程:How Xposed works

Before beginning with your modification, you should get a rough idea how Xposed works (you might skip this section though if you feel too bored).

在开始修改以前,你应该大体了解Xposed如何工做(若是你以为太无聊,你能够跳过这一部分)

There is a process that is called "Zygote". This is the heart of the Android runtime. Every application is started as a copy ("fork") of it. This process is started by an /init.rc script when the phone is booted. The process start is done with /system/bin/app_process, which loads the needed classes and invokes the initialization methods.

有一个叫作“Zygote”的进程。这是Android运行时的核心。每一个应用程序都做为它的副本(“fork”)启动。启动手机时,此进程由/init.rc脚本启动。进程启动是使用/system/bin/app_process完成的,它会加载所需的类并调用初始化方法。

This is where Xposed comes into play. When you install the framework, an extended app_process executable is copied to /system/bin. This extended startup process adds an additional jar to the classpath and calls methods from there at certain places. For instance, just after the VM has been created, even before the main method of Zygote has been called. And inside that method, we are part of Zygote and can act in its context.

这就是Xposed发挥做用的地方。安装框架时,会将扩展的app_process可执行文件复制到/system/bin。这个扩展的启动过程在类路径中添加了一个额外的jar,并在某些地方从那里调用方法。例如,就在建立VM以后,甚至在调用Zygote的main方法以前。在该方法中,咱们是Zygote的一部分,能够在其context下行动。

The jar is located at /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar and its source code can be found here. Looking at the class XposedBridge, you can see the main method. This is what I wrote about above, this gets called in the very beginning of the process. Some initializations are done there and also the modules are loaded (I will come back to module loading later).

jar位于,其源代码可在此处找到。查看类XposedBridge,您能够看到main方法。这就是我上面写的内容,这个在进程一开始就被调用了。在那里进行了一些初始化,而且还加载了模块(稍后我将回到模块加载)。

Method hooking/replacing

What really creates the power of Xposed is the possibility to "hook" method calls. When you do a modification by decompiling an APK, you can insert/change commands directly wherever you want. However, you will need to recompile/sign the APK afterwards and you can only distribute the whole package.

咱们创造Xposed真正具备的力量是"hook"方法调用具备无限可能性。经过反编译APK进行修改时,能够直接在任意位置插入/更改命令。可是,您须要在以后从新编译/签署APK,而且您只能分发整个包。

With the hooks you can place with Xposed, you can't modify the code inside methods (it would be impossible to define clearly what kind of changes you want to do in which place). Instead, you can inject your own code before and after methods, which are the smallest unit in Java that can be addressed clearly.

使用能够放置在Xposed的hooks,你并不能修改方法内的代码(由于你没法清楚地定义你想在哪一个地方作什么样的改变)。相反,您能够在方法以前和方法以后注入本身的代码,这是Java中能够清楚解决的最小单元。

XposedBridge has a private, native method hookMethodNative. This method is implemented in the extended app_process as well. It will change the method type to "native" and link the method implementation to its own native, generic method. That means that every time the hooked method is called, the generic method will be called instead without the caller knowing about it.

XposedBridge有一个私有的本地方法hookMethodNative。此方法也在扩展的app_process中实现。它会将方法类型更改成“native”,并将方法实现link到其本身的native、generic(泛型)方法。这意味着每次调用hooked方法时,都会调用generic方法,而不会让调用者知道它。

In this method, the method handleHookedMethod in XposedBridge is called, passing over the arguments to the method call, the this reference etc. And this method then takes care of invoking callbacks that have been registered for this method call. Those can change the arguments for the call, change instance/static variables, invoke other methods, do something with the result... or skip anything of that. It is very flexible.

在此方法中,XposedBridge中的handleHookedMethod方法会被调用,将参数传递给方法调用,this引用等。而后,此方法负责调用已为此方法调用注册的回调。这些能够更改调用的参数,更改实例/静态变量,调用其余方法,对结果执行某些操做...或者跳过任何内容。它很是灵活。

Ok, enough theory.

好的,上面的理论已经足够了。

官方简介

Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.

Xposed是一个能够在不触及任何APK的状况下改变系统和应用程序的行为的模块框架。

That's great because it means that modules can work for different versions and even ROMs without any changes (as long as the original code was not changed too much). It's also easy to undo. As all changes are done in the memory, you just need to deactivate the module and reboot to get your original system back.

这很好,由于它意味着模块能够在不作任何更改的状况下为不一样的版本甚至ROM工做(只要原始代码没有太多改变)。 撤消也很容易。 因为全部更改都在内存中完成,您只须要停用模块并从新启动便可恢复原始系统。

There are many other advantages, but here is just one more: Multiple modules can do changes to the same part of the system or app. With modified APKs, you to decide for one. No way to combine them, unless the author builds multiple APKs with different combinations.

还有许多其余优势,但这里只有一个:多个模块能够对系统或应用程序的同一部分进行更改。 使用通过修改的APK,您能够选择一个。 除非做者使用不一样的组合构建多个APK,不然没法组合它们。

Note that this only works with root access on Android 4.0.3 up to Android 4.4.

请注意,这仅适用于Android 4.0.3到Android 4.4 的具备root访问权限的设备。

最新版本支持到 8.1(27)

参考这里

目前最新版原本到3.1.5,支持Android7.0、7.一、Android8.x框架安装。

新版本的主要的功能更新是它带来了适配于当前设备的Xposed框架直接下载安装的选项,固然也包括卸载选项,这样就不用本身再去针对CPU和系统版本手动筛选须要刷入的框架版本了,减小了出错的概率,方便新手用户。

新版本的xposed框架主程序增长一些检查步骤,可以在出错的时候给出更多的提示,因此强烈推荐更新,而且做者也鼓励你们更新到新版本,各方面都会比旧版本要好一些。

第一个 xposed 项目

参考了 官方教程WrBug的简书慢啄网的文章coder-pig的文章

简单来讲就是,须要如下几个基本步骤:

  • 一、安装framework,重启
  • 二、安装XposedInstaller,重启
  • 三、添加依赖,添加三个meta-data
  • 四、编写Hook逻辑,配置完整类名
  • 五、安装APP,重启

模拟器系统环境配置

下面是模拟器下的配置,和实体机的重要区别为

  • 模拟器为 x86 架构,实体机通常为 ARM 结构,其所需安装的 Xposed framework 是不同的
  • 模拟器通常具备 root 权限,实体机通常没有 root 权限,且很难使用工具对手机 root,这是一个很棘手的问题
  • 在模拟器上你能够肆无忌惮的随意折腾,可是在实体机上就要顾忌不少,由于使用 Xposed 有可能让你的手机变砖
  • 模拟器上安装第三方插件不用担忧隐式泄露、资金安全,可是在实体机上你可就要悠着点了

一、根据Android设备系统版本到 framework官网 下载对应的框架,选择.zip结尾的文件,例如 sdk22 系统 x86 环境最新版本下载地址为 xposed-v89-sdk22-x86.zip,下载完成后运行模拟器,将 zip 包拖到模拟器界面便可刷入,完成后重启模拟器

二、到 installer官网 下载安装XposedInstaller应用(可能比较慢),例如最新的 XposedInstaller_3.1.5.apk,也可百度搜索后下载安装。安装完毕后打开此app,会提示你没有激活,再次重启后进入刚刚安装的app,会提示已激活,即安装成功。

咱们能够经过Xposed installer右上角菜单中的"软重启"来重启设备,固然此APP还提供了一切经常使用的其余功能。

实体机系统环境配置

重要提示:刷机前为了以防万一,最好先备份数据,以避免刷入后手机没法启动等问题。

基本条件:手机已经root而且已经刷入第三方recovery
步骤和上面基本同样,首先下载并安装好XposedInstaller
而后下载和手机cpu对应的Framework,并放在手机存储卡上
而后重启到recovery模式,刷入Framework
再重启便可

因为Xposed项目每次安装都要从新启动,在真机上是很是耗时间的,建议选择Genymotion模拟器。

引用 API

在模块中添加依赖:

compileOnly 'de.robv.android.xposed:api:82' //xposed依赖,注意这个版本号和framework版本号并非一致的
compileOnly 'de.robv.android.xposed:api:82:sources' //非必须

Every Xposed modules needs to reference the API.
The Xposed Framework API is published on Bintray/jCentermaven 地址在这里

It is very important that you use compileOnly instead of compile! The latter would include the API classes in your APK, which can cause issues especially on Android 4.x. Using compileOnly just makes the API classes usable from your module, but there will only be references to them in the APK. The actual implementation will be provided when the user installs the Xposed Framework.

使用compileOnly而不是compile是很是重要的! 后者将在您的APK中包含API类,这可能会致使问题,特别是在Android 4.x上。 使用compileOnly只是使API类能够从您的模块中使用,但在APK中只会引用它们。当用户安装 Xposed Framework 时,将提供实际的实现。

Please make sure to disable Instant Run (File -> Settings -> Build, Execution, Deployment -> Instant Run), otherwise your classes aren't included directly in the APK, but loaded via a stub application which Xposed can't handle.

请确保禁用Instant Run,不然您的类不会直接包含在APK中,而是经过Xposed没法处理的stub应用程序加载。


API versions
Generally, the API version equals the Xposed version that it was built on. However, only some framework changes actually result in API changes, as you can see in the change log. I only publish a new API version when there were API changes, and I try to keep them compatible with existing modules as good as possible. So when you build a module with API version 82, it will most likely also work withXposed version 90.

一般。可是,只有个别一些 framework 更改实际上会致使API更改,您能够在更改日志中看到。我只在API更改时发布了新的API版本,并尝试尽量地使它们与现有模块兼容。所以,当您使用API版本82构建模块时,它极可能也适用于Xposed版本90。

I always recommend that end-users use the latest Xposed version, so there's nothing wrong with using the highest API version that's available. You should usually set the xposedminversion in your AndroidManifest.xml to the API version that you use. If you depend on a framework change that didn't cause an API change (e.g. because a certain bug has been fixed), feel free to set you xposedminversion to the least Xposed version that your module requires.

我老是建议终端用户使用最新的Xposed版本,所以使用可用的最高API版本没有任何问题。您一般应该将AndroidManifest.xml中的xposedminversion设置为您使用的API版本。若是您依赖于不会致使API更改的framework更改(例如,由于已修复某个错误),请随意将xposedminversion设置为模块所需的最少Xposed版本。

If you want to support ROMs before Lollipop, you can only use API version 53, as the latest Xposed version for Android 4.x was 54. Note that the sources jar provided for this version doesn't match the actual implementation, it only makes the documentation available.

若是你想在Lollipop以前支持ROM,你只能使用API版本53,由于Android 4.x的最新Xposed版本是54.请注意,为此版本提供的源jar与实际实现不匹配,它只会使可用的文件。

配置 meta-data

在AndroidManifest.xml中添加以下配置:

<meta-data
    android:name="xposedmodule"
    android:value="true"/>
<meta-data
    android:name="xposeddescription"
    android:value="这是对你使用xposed能完成功能的简要描述"/>
<meta-data
    android:name="xposedminversion"
    android:value="89"/>

xposedmodule:是不是一个xpose模块(是否启用)
xposeddescription:a very short description of your module
xposedminversion:the API version from the previous step,注意这里的版本号要和安装的framework版本号一致

配置完成后,安装到模拟器,状态栏弹出以下提示:

咱们点击软重启便可。

需求分析

例如咱们有这么一个简单的Activity

public class XposedActivity extends Activity {
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        textView = new TextView(this);
        setContentView(textView);
    }
}

下面经过 xposed 给 textView 设置文本 Hello Xposed。

思路:Xposed hook onCreate方法,在该方法执行完后获取TextView的实例,经过setText方法设置文本。

A module can have a few entry points. Which one(s) you choose depends on the what you want to modify. You can have Xposed call a function in your module when the Android system boots, when a new app is about to be loaded, when the resources for an app are initialised and so on.

一个模块能够有几个入口点。 您选择哪个取决于您要修改的内容。 当Android系统启动,即将加载新应用程序,应用程序的资源等初始化时,您可让Xposed调用模块中的方法。

Keep in mind that you can "only" hook methods. So you have to find a place where you can insert some code to do the magic either before, after or replacing a method. You should hook methods that are as specific as possible, not ones that are called thousands of times to avoid performance issues and unintended side-effects.

请记住,您“仅”能够hook方法。所以,您必须找到一个位置,以便您能够在方法以前、以后或替换方法时在其中插入一些代码执行magic。您应该hook尽量具体的方法,而不是那些被调用数千次的方法,以免性能问题和意外的反作用。

编写 Xposed 代码

Using reflection to find and hook a method

新建一个类XposedInit实现IXposedHookLoadPackage

public class XposedInit implements IXposedHookLoadPackage {

    private static String HOOK_PACKAGE_NAME = "com.my.bqt";
    private static String HOOK_CLASS_NAME = "com.my.bqt.xposed.XposedActivity";

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) {
        Log.i("bqt", "【handleLoadPackage】" + lpparam.packageName);//任何一个app启动时都会调用
        if (lpparam.packageName.equals(HOOK_PACKAGE_NAME)) { //匹配指定的包名
            //参数:String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback
            XposedHelpers.findAndHookMethod(lpparam.packageName, lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("bqt", "【afterHookedMethod】" + param.method.getName()); //当Hook成功后回调
                    Class c = lpparam.classLoader.loadClass(HOOK_CLASS_NAME);//不能经过Class.forName()来获取Class,在跨应用时会失效
                    Field field = c.getDeclaredField("textView");
                    field.setAccessible(true);
                    TextView textView = (TextView) field.get(param.thisObject);//param.thisObject为执行该方法的对象,在这里指Activity
                    textView.setText("Hello Xposed");
                    //能够调用param.setResult()设置方法的返回值!
                }
            });
        }
    }
}

官方文档相关介绍

XposedHelpers.findAndHookMethod is a helper function. This method looks up the class_A using the ClassLoader_B for the package_C. Then it looks for the method_D in it. If there were any parameters_E to this method, you would have to list the types (classes) of these parameters afterwards. As the last argument, you need to provide an implementation of the XC_MethodHook_F class. For smaller modifications, you can use a anonymous class. If you have much code, it's better to create a normal class and only create the instance here. The helper will then do everything necessary to hook the method as described above.

XposedHelpers.findAndHookMethod是一个helper 函数。 这个方法使用classLoader_Bpackage_C查找class_A。 而后它在其中查找method_D。 若是此方法有任何parameters_E,则必须在以后列出这些参数的类型(classes)。 做为最后一个参数,您须要提供XC_MethodHook_F类的实现。 对于较小的修改,您可使用匿名类。 若是你有不少代码,最好建立一个普通的类,只在这里建立实例。 而后,帮助程序将执行hook方法所需的全部操做,如上所述。

There are two methods in XC_MethodHook that you can override. You can override both or even none, but the latter makes absolutely no sense These methods are beforeHookedMethod and afterHookedMethod. It's not too hard to guess that the are executed before/after the original method.

XC_MethodHook中有两种方法能够覆盖。 你能够覆盖二者,甚至没有,但若是两个方法都不重写那没任何意义。 这些方法是beforeHookedMethodafterHookedMethod。 猜想在原始方法以前/以后执行并非很难。

You can use the "before" method to evaluate/manipulate the parameters of the method call (via param.args) and even prevent the call to the original method (sending your own result). The "after" method can be used to do something based on the result of the original method. You can also manipulate the result at this point. And of course, you can add your own code which should be executed exactly before/after the method call.

您可使用“before”方法评估/操做方法调用的参数(经过param.args),甚至阻止调用原始方法(发送您本身的结果)。 “after”方法可用于根据原始方法的结果执行某些操做。 您也能够在此处操纵结果。 固然,您能够添加本身的代码,这些代码应该在方法调用以前/以后执行。

If you want to replace a method completely, have a look at the subclass XC_MethodReplacementinstead, where you just need to override replaceHookedMethod.

若是你想彻底替换方法,使请用子类XC_MethodReplacement代替,你只须要覆盖replaceHookedMethod

XposedBridge keeps a list of registered callbacks for each hooked method. Those with highest priority (as defined in hookMethod) are called first. The original method has always the lowest priority. So if you have hooked a method with callbacks A (prio high) and B (prio default), then whenever the hooked method is called, the control flow will be this: A.before -> B.before -> original method -> B.after -> A.after. So A could influence the arguments B gets to see, which could further change them before passing them on. The result of the original method can be processed by B first, but A has the final word what the original caller gets.

XposedBridge为每一个挂钩方法保留一个已注册的回调列表。 优先级最高的那些(在hookMethod中定义)首先被调用。 原始方法始终具备最低优先级。 所以,若是您使用回调A(高优先级)和B(默认优先级)hook了一个方法,那么每当调用hooked方法时,控制流将是:A.before -> B.before -> original method -> B.after -> A.after。 因此A能够影响B看到的参数,这能够在传递它们以前进一步改变它们。原始方法的结果由B首先处理,但A具备原始调用者所能得到结果的最终话语权。

为何不能用 Class.forName 方法

在上面的案例中,咱们没有用Class.forName()来获取class,是什么缘由呢?咱们先来看看Class.forName()的源码:

public static Class<?> forName(String className) throws ClassNotFoundException {
    return forName(className, true, VMStack.getCallingClassLoader()); //注意ClassLoader的获取方式
}
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException {
    if (loader == null) {
        loader = BootClassLoader.getInstance();
    }
    Class<?> result;
    try {
        result = classForName(name, initialize, loader);
    } catch (ClassNotFoundException e) {
        Throwable cause = e.getCause();
        if (cause instanceof LinkageError) {
            throw (LinkageError) cause;
        }
        throw e;
    }
    return result;
}
static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;

在一个参数的方法中,ClassLoader是经过VMStack.getCallingClassLoader()获取的。VMStack是一个虚拟机栈,在Android系统中,每一个应用都有一个独立的虚拟机,因此VMStack.getCallingClassLoader()是获取当前应用的ClassLoader,即xposed项目的ClassLoader,因此,若是使用Class.forName("xxx.xxx.xxxActivity")获取不一样应用的类会提示找不到,这就是须要经过lpparam.classLoader.loadClass()获取的缘由。

配置完整类名

Provide a hint for XposedBridge which classes contain such entry points.

新建assets文件夹,文件夹下新建xposed_init文件(文件名固定),在文件中填写 XposedInit 的 fully qualified class name:

com.my.bqt.xposed.XposedInit

XposedBridge会从assets目录中的xposed_init文件中获取入口点
而后安装到模拟器上,而后重启模拟器,重启后打开app,这时textView将显示Hello Xposed

Save your files. Then run your project as Android application. As this is the first time you install it, you need to enable it before you can use it. Open the Xposed Installer app and make sure you have installed the framework. Then go to the "Modules" tab. You should find your app in there. Check the box to enable it. Then reboot.

保存文件。 而后将您的项目做为Android应用程序运行 因为这是您第一次安装它,所以您须要启用它才能使用它。 打开Xposed Installer应用程序并确保已安装框架。 而后转到“模块”选项卡。 你应该在那里找到你的应用程序。 选中此框以启用它。 而后重启。

一个基本的、也是Xposed最主要的功能已经演示完成了。

Xposed Framework API

Xposed Framework API

类结构:

IXposedMod

//Marker interface for Xposed modules. Cannot be implemented directly
interface IXposedMod {}

IXposedMod 接口有四个子接口,咱们可使用的有三个,经常使用的也就两个,具体以下:

  • IXposedHookLoadPackage:Get notified when an app ("Android package") is loaded.
  • IXposedHookInitPackageResources:Get notified when the resources for an app are initialized.
  • IXposedHookZygoteInit:Hook the initialization of Zygote process(es), from which all the apps are forked.
  • IXposedHookCmdInit :【不可用】Hook the initialization of Java-based command-line tools (like pm).

IXposedHookZygoteInit:在Zygote启动时调用,用于系统服务的Hook回调方法initZygote()

IXposedHookLoadPackage 和 XC_LoadPackage.LoadPackageParam

  • IXposedHookLoadPackage是在加载包时开始hook。
  • handleLoadPackage方法会在执行Application.onCreate()方法前调用,而且携带一个XC_LoadPackage.LoadPackageParam类型的参数返回过来。
  • LoadPackageParam中包含了hook到的应用的一些信息,包括应用包名、应用加载后的进程名、应用的classloader以及一个android.content.pm.ApplicationInfo对象。

IXposedHookLoadPackage的定义

//Get notified when an app ("Android package") is loaded.
//This is especially useful to hook some app-specific methods.
//This interface should be implemented by the module's main class. Xposed will take care of registering it as a callback automatically.
public interface IXposedHookLoadPackage extends IXposedMod {
    //This method is called when an app is loaded. It's called very early, even before Application#onCreate is called.
    //Modules can set up their app-specific hooks here.
    //@param lpparam Information about the app.
    //@throws Throwable Everything the callback throws is caught and logged.
    void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
}

XC_LoadPackage.LoadPackageParam的定义

//This class is only used for internal purposes, except for the LoadPackageParam subclass.
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {

    /Wraps information about the app being loaded.
    public static final class LoadPackageParam extends XCallback.Param {
        public String packageName; //The name of the package being loaded
        public String processName; // The process in which the package is executed
        public ClassLoader classLoader; //The ClassLoader used for this package
        public ApplicationInfo appInfo; //More information about the application being loaded
        public boolean isFirstApplication; //Set to true if this is the first (and main) application for this process
    }
}

XC_MethodHook.MethodHookParam

//Callback class for method hooks.
//Usually, anonymous subclasses of this class are created which override beforeHookedMethod and/or afterHookedMethod.
public abstract class XC_MethodHook extends XCallback {
    //Creates a new callback with default priority.
    public XC_MethodHook() {
        super();
    }

    //Creates a new callback with a specific priority.
    public XC_MethodHook(int priority) {
        super(priority);
    }

    //Called before the invocation of the method.
    //You can use MethodHookParam#setResult and MethodHookParam#setThrowable to prevent the original method from being called.
    //Note that implementations shouldn't call super(param), it's not necessary.
    //@param param Information about the method call.
    //@throws Throwable Everything the callback throws is caught and logged.
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}

    //Called after the invocation of the method.
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {}

    //Wraps information about the method call and allows to influence it.
    public static final class MethodHookParam extends XCallback.Param {
        public Member method; //The hooked method/constructor
        public Object thisObject; //The 【this】 reference for an instance method, or null for static methods
        public Object[] args; //Arguments to the method call
        private Object result = null; //the result of the method call。 经过 get/set 方法访问
        private Throwable throwable = null; //the exception thrown of the method call, 经过 get/set/has 方法访问
        boolean returnEarly = false;

        //Returns the result of the method call, or throws the Throwable caused by it.
        public Object getResultOrThrowable() throws Throwable {
            if (throwable != null) throw throwable;
            return result;
        }
    }

    //An object with which the method/constructor can be unhooked.
    public class Unhook implements IXUnhook<XC_MethodHook> {
        private final Member hookMethod; // the method/constructor that has been hooked

        @Override
        public XC_MethodHook getCallback() {
            return XC_MethodHook.this;
        }

        @Override
        public void unhook() {
            XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
        }
    }
}

MethodHookParam中包含与调用方法有关的信息,比较关注的是这个thisObject,表明调用该方法的对象实例,若是是静态方法的话,返回一个Null,好比调用onCreate()方法的是MainActivity,得到的天然是MainActivity的实例。

IXposedHookInitPackageResources 和 XC_InitPackageResources.InitPackageResourcesParam

这个是在资源布局初始化时进行hook,须要实现handleInitPackageResources() 方法,在初始化时调用,resparam有两个字段,一个是应用包名,另外一个是资源相关的android.content.res.XResources,XResources继承自Resources,里面包含了不少资源的信息。

IXposedHookInitPackageResources的定义

//Get notified when the resources for an app are initialized.
//In handleInitPackageResources(), resource replacements can be created.
//This interface should be implemented by the module's main class. Xposed will take care of registering it as a callback automatically.
public interface IXposedHookInitPackageResources extends IXposedMod {
    //This method is called when resources for an app are being initialized.
    //Modules can call special methods of the XResources class in order to replace resources.
    //@param resparam Information about the resources.
    //@throws Throwable Everything the callback throws is caught and logged.
    void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
}

XC_InitPackageResources.InitPackageResourcesParam的定义

//This class is only used for internal purposes, except for the InitPackageResourcesParam subclass.
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
    //Wraps information about the resources being initialized.
    public static final class InitPackageResourcesParam extends XCallback.Param {
        public String packageName; //The name of the package for which resources are being loaded
        public XResources res; //Reference to the resources that can be used for calls to XResources#setReplacement(String, String, String, Object)
    }
}

有了这个XResource对象,就能够拿到布局资源树了,你能够拿到遍历,拿到某个特定控件,而后作一些骚操做。

XC_LayoutInflated.LayoutInflatedParam

//Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout} and its variants.
public abstract class XC_LayoutInflated extends XCallback {
    //Creates a new callback with default priority.
    public XC_LayoutInflated() {
        super();
    }

    //Creates a new callback with a specific priority. See XCallback#priority.
    public XC_LayoutInflated(int priority) {
        super(priority);
    }

    //Wraps information about the inflated layout.
    public static final class LayoutInflatedParam extends XCallback.Param {
        public View view; //The view that has been created from the layout.
        public ResourceNames resNames; //Container with the ID and name of the underlying resource.
        public String variant; //Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). 
        public XResources res; //Resources containing the layout. 
    }

    //This method is called when the hooked layout has been inflated.
    //@param liparam Information about the layout and the inflated view.
    public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;

    //An object with which the callback can be removed.
    public class Unhook implements IXUnhook<XC_LayoutInflated> {
        private final String resDir;
        private final int id; //the resource ID of the hooked layout

        @Override
        public XC_LayoutInflated getCallback() {
            return XC_LayoutInflated.this;
        }

        @Override
        public void unhook() {
            XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
        }
    }
}

XposedBridge 和 XposedHelpers

官方文档地址

There are many helper methods in Xposed that can make developing a module much easier.

XposedBridge

This class contains most of Xposed's central logic, such as initialization and callbacks used by the native side. It also includes methods to add new hooks.

log方法:
The log method is an easy way of logging debug output to the standard logcat and a file called /data/xposed/debug.log. It can take the log message or a Throwable. In the latter case, it will print the stack trace.

XposedBridge.log(“日志内容”):输入日志和写入到/data/xposed/debug.log,Xposed Installer的日志那里能够看到!

hookAllMethods、hookAllConstructors方法:
You can use these methods if you want to hook all methods with a specific name or all constructors in a class. This is useful if there are different variants, but you want to execute some code before/after any of them has been called. Keep in mind that other ROMs might have additional variants that will also be hooked by this. Especially, be careful about the args you get in the callback.

若是要hook具备特定名称的全部方法或类中的全部构造函数,则可使用这两种方法。 若是存在不一样的变量,可是您但愿在调用任何代码以前/以后执行某些代码,这将很是有用。 请记住,其余ROM可能还有其余变量,也会被hook。 特别是要当心你在回调中获得的args

XposedHelpers

Helpers that simplify hooking and calling methods/constructors, getting and settings fields, ...

XposeHelpers提供了一些辅助方法

  • callMethod、callStaticMethod、newInstance
  • findMethod、findConstructor、findField:获取类实例
  • findMethodExact、findConstructorExact:经过反射查找类的成员方法
  • findAndHookXXX:查找并Hook
  • getXXXField、setXXXField、getStaticXXXField、setStaticXXXField:经过反射设置或获取对象数据成员、静态变量的值
  • getAdditionalXXXField、setAdditionalXXXField
  • assetAsByteArray
  • getMD5Sum
  • getProcessPid

第二个 Xposed 案例

要hook的代码

咱们再建立一个Activity测试在资源布局初始化时进行hook的效果:

public class XposedActivity2 extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("bqt", "【setContentView前】");
        setContentView(R.layout.activity_main);
        Log.i("bqt", "【setContentView后】");

        Log.i("bqt", "【inflate前】");
        getLayoutInflater().inflate(R.layout.fragment_tab, null);
        Log.i("bqt", "【inflate后】");
    }
}

具体的hook逻辑

接下来在XposenInit里面实现IXposedHookInitPackageResources接口,而且实现handleInitPackageResources方法,代码以下:

public class XposedInit implements IXposedHookInitPackageResources {

    private static String HOOK_PACKAGE_NAME = "com.my.bqt";
    private static String HOOK_LAYOUT_NAME = "activity_main";
    private static String HOOK_LAYOUT_NAME2 = "fragment_tab";

    @Override
    public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) {
        Log.i("bqt", "【handleInitPackageResources】" + resparam.packageName);//任何一个app的包资源初始化时都会调用
        if (resparam.packageName.equals(HOOK_PACKAGE_NAME)) {
            resparam.res.hookLayout(resparam.packageName, "layout", HOOK_LAYOUT_NAME, new XC_LayoutInflated() {
                @Override
                public void handleLayoutInflated(LayoutInflatedParam liparam) {
                    Log.i("bqt", "【handleLayoutInflated】" + liparam.variant + "," + liparam.resNames.fullName);
                    printView((ViewGroup) liparam.view, 1);
                    TextView textView = liparam.view.findViewById(R.id.tv_title);
                    textView.setText("Hello Xposed");
                }
            });
            resparam.res.hookLayout(resparam.packageName, "layout", HOOK_LAYOUT_NAME2, new XC_LayoutInflated() {
                @Override
                public void handleLayoutInflated(LayoutInflatedParam liparam) {
                    Log.i("bqt", "【handleLayoutInflated2】" + liparam.variant + "," + liparam.resNames.fullName);
                }
            });
        }
    }

    //遍历资源布局树,并打印出来
    private void printView(ViewGroup view, int deep) {
        StringBuilder builder = new StringBuilder();
        String viewDeepFormat;
        for (int i = 0; i < deep - 1; i++) {
            builder.append("\t");
        }
        viewDeepFormat = builder + "\t";
        Log.i("bqt", "【printView】" + builder.toString() + view.toString());

        int count = view.getChildCount();
        for (int i = 0; i < count; i++) {
            if (view.getChildAt(i) instanceof ViewGroup) {
                printView((ViewGroup) view.getChildAt(i), deep + 1);
            } else {
                Log.i("bqt", "【printView】" + viewDeepFormat + view.getChildAt(i).toString());
            }
        }
    }
}

日志分析

安装重启后,打开demo,查看打印的日志:

【handleInitPackageResources】com.my.bqt

【setContentView前】
【handleLayoutInflated】layout,com.my.bqt:activity_main/layout
【printView】android.widget.FrameLayout{2077e3cd V.E..... ......I. 0,0-0,0 #1020002 android:id/content}
【printView】    android.widget.LinearLayout{1802a482 V.E..... ......I. 0,0-0,0}
【printView】        android.widget.TextView{5cbd793 V.ED.... ......ID 0,0-0,0 #7f07009f app:id/tv_title}
【printView】        android.view.View{222bc3d0 V.ED.... ......ID 0,0-0,0}
【printView】        android.widget.FrameLayout{252befc9 V.E..... ......I. 0,0-0,0 #7f070044 app:id/id_container}
【printView】        android.view.View{353a86ce V.ED.... ......ID 0,0-0,0}
【printView】        android.widget.LinearLayout{105259ef V.E..... ......I. 0,0-0,0 #7f07005a app:id/ly_main_tab_bottom}
【printView】            android.widget.TextView{8514cfc V.ED.... ......ID 0,0-0,0 #7f07009e app:id/tv_tab_bottom_weixin}
【printView】            android.widget.TextView{16fabf85 V.ED.... ......ID 0,0-0,0 #7f07009c app:id/tv_tab_bottom_friend}
【printView】            android.widget.TextView{14fc41da V.ED.... ......ID 0,0-0,0 #7f07009b app:id/tv_tab_bottom_contact}
【printView】            android.widget.TextView{1414a60b V.ED.... ......ID 0,0-0,0 #7f07009d app:id/tv_tab_bottom_setting}
【setContentView后】

【inflate前】
【handleLayoutInflated2】layout,com.my.bqt:fragment_tab/layout
【inflate后】

Tips:

  • 日志能够看出handleInitPackageResources会在setContentViewgetLayoutInflater().inflate时调用。
  • 对setContentView有了解的都明白,setContentView也会调用inflate方法,因此,也能够当作是hook了inflate方法。
  • 在返回的数据InitPackageResourcesParam中,有一个liparam.view的字段,经过日志能够看出setContentView方法的是一个FrameLayout,下面包含了LinearLayout,这个LinearLayout也就是咱们activity_main布局最外层的view,获取到这个view之后就能够进行一系列的操做了。

2019-4-13

相关文章
相关标签/搜索