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

目录

Xposed

官方教程

Xposed框架中文站

Xposed插件官方网站

FAQ,建议开发前仔细阅读一遍

framework下载地址

xposed依赖的maven地址在jcenter上的下载地址

作者rovo89的GitHub主页,里面有5个项目,包括XposedInstaller、Xposed、XposedBridge、XposedTools、android_art。

由于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官网下载地址

Xposed 是什么

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

Xposed 理论上能够hook到系统任意一个Java进程,可以说Xposed真的可以为所欲为,任何事情都可以做!

重要提醒:使用Xposed是需要Root权限的!

PS:由于Xposed是从底层hook,所以需要root权限,并且每次更新都要重新启动设备

背景介绍

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

真正开始学的时候,其实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.1、Android8.x框架安装。

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

新版本的xposed框架主程序增加一些检查步骤,能够在出错的时候给出更多的提示,所以强烈推荐更新,并且作者也鼓励大家更新到新版本,各方面都会比旧版本要好一些。

第一个 xposed 项目

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

简单来说就是,需要以下几个基本步骤:

  • 1、安装framework,重启
  • 2、安装XposedInstaller,重启
  • 3、添加依赖,添加三个meta-data
  • 4、编写Hook逻辑,配置完整类名
  • 5、安装APP,重启

模拟器系统环境配置

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

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

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

2、到 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

Xposed 框架 hook 简介 原理 案例 MD的更多相关文章

  1. RxJava RxPermissions 动态权限 简介 原理 案例 MD

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

  2. Xposed框架Hook Android应用的所有类方法打印Log日志

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80954759 在进行Android程序的逆向分析的时候,经常需要Android程 ...

  3. - 反编译 AndroidKiller 逆向 实践案例 MD

    目录 目录 反编译 AndroidKiller 逆向 实践案例 MD AndroidKiller 简介 插件升级 基本使用 实践案例 修改清单文件 打印 debug 级别的日志 方式一:直接代理 Lo ...

  4. android hook 框架 ADBI 简介、编译、运行

    Android so注入-libinject2 简介.编译.运行 Android so注入-libinject2  如何实现so注入 Android so注入-Libinject 如何实现so注入 A ...

  5. LeakCanary 内存泄漏 监测 性能优化 简介 原理 MD

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

  6. LruCache DiskLruCache 缓存 简介 案例 MD

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

  7. android hook 框架 libinject2 简介、编译、运行

    Android so注入-libinject2 简介.编译.运行 Android so注入-libinject2  如何实现so注入 Android so注入-Libinject 如何实现so注入 A ...

  8. Xposed框架之函数Hook学习

    作者:Fly2015 Xposed是Android下Java层的开源Hook框架类似的有cydiasubstrate框架而且据说cydiasubstrate框架能实现Android的Java层和Nat ...

  9. SpringBoot系列之日志框架介绍及其原理简介

    SpringBoot系列之日志框架介绍及其原理简介 1.常用日志框架简介 市面上常用日志框架:JUL.JCL.jboss-logging.logback.log4j.log4j2.slf4j.etc. ...

随机推荐

  1. net core体系-web应用程序-3项目结构、配置文件详解

    一.应用程序文件结构 如下图所示,相比于Asp.Net项目,在新建的Asp.Net Core项目中,没有了Global.asax以及Web.config这样的文件,但多了几个其他主要的文件,它们分别为 ...

  2. websocket/dwebsocket 实现前后端的实时通信

    1.  用bottle框架,自己写一个服务端实现: 转载   :http://www.linuxyw.com/813.html 功能:用websocket技术,在运维工具的浏览器上实时显示远程服务器上 ...

  3. iframe获取元素

    原生js在网页中,父元素获取iframe中的元素: window.onload=function () { 例如: console.log(window.frames["iframe的nam ...

  4. jquery的div局部刷新

    jquery的div局部刷新 //div的局部刷新 $(".dl").load(location.href+" .dl"); 全页面的刷新方法 window.l ...

  5. day40 mycql 视图,触发器,存储过程,函数

    视图,触发器,存储过程,自定义函数 -- 回顾 1.mysql 约束 1.非空 not null 2. 主键约束 primary key 3. 唯一约束 unique 4. 外键约束 foreign ...

  6. SEED-DVS6467_SDK的交叉编译环境搭建问题

    今天在ubuntu16.04上安装arm的交叉编译器arm_v5t_le-gcc,环境变量配置好以后,运行arm_v5t_le-gcc命令,总提示No such file or directory.然 ...

  7. HDU 4825 Xor Sum (模板题)【01字典树】

    <题目链接> 题目大意: 给定n个数,进行m次查找,每次查找输出n个数中与给定数异或结果最大的数. 解题分析: 01字典树模板题,01字典树在求解异或问题上十分高效.利用给定数据的二进制数 ...

  8. JavaScript函数、闭包、原型、面向对象

    JavaScript函数.闭包.原型.面向对象 断言 单元测试框架的核心是断言方法,通常叫assert(). 该方法通常接收一个值--需要断言的值,以及一个表示该断言目的的描述. 如果该值执行的结果为 ...

  9. Alpha(2/10)

    鐵鍋燉腯鱻 项目:小鱼记账 团队成员 项目燃尽图 冲刺情况描述 站立式会议照片 各成员情况 团队成员 学号 姓名 git地址 博客地址 031602240 许郁杨 (组长) https://githu ...

  10. os模块、os.path模块、shutil模块、configparser模块、subprocess模块

    一.os模块 os指的是操作系统 该模块主要用于处理与操作系统相关的操作,常用的是文件操作(读.写.删.复制.重命名). os.getcwd()  获取当前文件所在的文件夹路径 os.chdir()  ...