The quiter you become,the more you are able to hear!

Xposed翻译文档

Author: geneblue

Blog: https://geneblue.github.io/

英文原版地址:这里

注:文档去掉了Xposed Module建立的过程,上述地址介绍的比较详细且容易看懂。有些地方翻译的比较生硬,还是查看对应的英文文档比较好。


Development tutorial

Xposed原理

在Android运行时空间里(Android runtime)有一个非常重要的进程是“Zygote”。每个Android应用程序的启动都是由Zygote进程fork而出的。当手机启动的时候,Zygote进程就会由/init.rc脚本启动。最终由/system/bin/app_process进程完成Zygote进程的启动,app_process进程会加载一些必要的类库并初始化一些方法。

Xposed会参与到上述Zygote的启动过程。当安装完Xposed框架后,一个修改的并被扩展的app_process可执行程序会被拷贝到/system/bin目录下,原有的app_process文件会被更名为app_process.orig(卸载Xposed后会恢复原有的app_process文件)。这个扩展的app_process程序会添加额外的jar包到环境变量中去并且会在恰当的地方调用方法。例如,在VM刚被创建之后,Zygote的main方法会被调用。在main方法中,Xposed会成为Zygote的一部分并可以在它的上下文做一些处理。

被加载的jar包位于/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar 上述描述的过程会在XposedBridge类的main方法中体现,Zygote进程早期启动时,这个main方法会被调用。一些初始化过程和模块也会被处理。

方法的hook/替换

Xposed真正起作用的部分是能够hook方法的调用。当你通过反编译对APK文件做修改的时候,你可以直接在任何你想在的地方插入或改变命令。但是,最终你需要重编译并签名该APK文件。当使用Xposed中的hook方法后,你不必再修改APK源码。反而,你可以使用 before 和after 方法注入自己的代码。before和after 方法都是很小的java代码单元。

XposedBridge有一个私有的本地方法 hookMethodNative。这个方法也是在那个扩展的app_process中实现的。这样会将hookMethodNative这个方法类型改变为“本地”方法并且会链接这个方法的实现到它的泛型方法。也就是说每次被hook的方法被调用时,泛型方法就会被调用而且调用者并不知道。在hookMethodNative方法中,XposedBridge中的handleHookedMethod方法通过方法的参数,this引用等被调用了。然后这个方法会调用回调函数,这个回调函数已经为这个方法调用注册过了。这些可以为调用改变参数,可以改变 instance/static 变量,调用其它方法,在返回结果中做一些处理等或者跳过任何部分。这是很复杂的。

使用反射reflection来查找并hook一个方法

在编写Xposed Module程序时,一般在入口类(xposed_init文件中声明的类)中使用反射进入到要hook的程序进程。

findAndHookMethod这个helper类会使用classLoader来定位要hook的具体函数。如果要hook的函数带有参数,还要在findAndHookMethod的参数列表中列出来,参数列表的最后一个参数是XC_MethodHook 类。对于较小的修改,可以使用匿名类,但对于较大的代码修改,最好创建一个普通的类并给出类的实例。最终,方法的hook会有findAndHookMethod函数完成。 在XC_MethodHook中有两个方法(beforeHookedMethod and afterHookedMethod)可以被override。这两个方法分别是在被hook的原方法 前或后 被执行。beforeHookedMethod方法可以通过 param.args 篡改 要hook的方法的参数,甚至可以通过发送自己的返回值阻止原方法的调用。afterHookedMethod方法可以在得到原方法的返回值的基础上做其他处理,当然也就可以做到返回值的虚假。在这两个方法中,我们还可以增添自己的代码到要Hook的方法中去执行。 如果想要整个替换掉方法,可以使用XC_MethodHook的子类XC_MethodReplacement,使用这个类时只要override replaceHookedMethod方法即可。


Helpers

下面介绍Xposed中的helper 方法,使用这个方法可以让我们更方便地开发Xposed Module程序。

在类 XposedBridge中

log方法

log方法比较简单,它可以将log信息输出到标准的logcat程序中,也能输出到/data/xposed/debug.log文件中。它可以捕获log信息或者一个异常。对于异常,它会打印栈跟踪信息stack trace。

hookAllMethods和hookAllConstructors方法

如果想通过明确的函数名或者一个类的构造函数 来hook所有的方法,我们可以使用这两个方法。如果要hook的类有不同的变体,这将是很有用的,但你想执行的代码(before/after)已经被调用了。注意其他不同的ROM可能也会有不同的变体,那么他们也会被hook。特别要留意你得到的回调函数的参数。

在类XposedHelpers中

推荐将这个类添加到Eclipse的favorites下作为static imports。具体过程是:Windows=>Preferences=>Java=>Editor=>Content Assist=>Favorites=>New Type,键入de.robv.android.xposed.XposedHelpers。通过这种做法,Eclipse会自动在你写代码时提示这个类中的方法,并将这个类以静态方式import到你的类中。

findMehtod/findConstructor/findField方法

上述三种方法可以不适用反射技术来检索methods,constructors和fields。当然,你可以使用“best match”通过确切的参数类型来查找methods和constructors。例如:调用findMethodBestMatch(Class<?> clazz,String methodName,Object… args)

callMethod/callStaticMethod/newInstance方法

通过利用上述的findxxx 方法,可以很容易地调用方法或创建一个类的实例。调用者无需对此使用反射。在这之前不许检索方法,仅仅使用上述三个方法就可以做到即时调用。参数类型会自动地从实际参数值和被调用的best-matching方法中拷贝过来。假使你想要精确地为一个参数指定类型,那么需要创建一个 Class<?>数组并传递给callMethod/callStaticMethod/newInstance方法。你可以丢弃数组中一些null值仅仅使用类的实际参数,但是数组的长度不得不与参数的数量想匹配。

getxxxField/setxxxField/getStaticxxxField/setStaticxxxField方法

上述四种方法常被使用来获取或设置实例的内容和类的变量。只需要获得对对象的引用,就可以获得对象的field的名称和类型。如果你想要 get/set 一个静态的字段,并且没有对象的引用,那么这时就可以使用getStaticxxx和setStaticxxx方法。当你获得一个对象的引用时,就可以使用getxxx和setxxx方法,因为没必要再区分静态和实例字段。

getAdditionalxxxField/setAdditionalxxxField方法

上面的两个方法可以让你要么与一个对象的实例关联任何值,要么与整个类关联任何值。这些值都是以键值对的形式存储在map中,所以你可以为每个对象保存许多值。键名key可以是string类型的,包括对象真实的字段名。请注意你不能通过调用getAdditionalInstanceField方法来检索一个以setAdditionalStaticField方法存储的值。使用getAdditionalStaticField方法可以。

#####assetAsByteArray方法 这个方法可以将一个asset资源以字节数组的形式返回。如果想要加载module的资源,可以通过以下的方法实现:

public class XposedTweakbox implements IXposedHookZygoteInit {
  @Override
  public void initZygote(StartupParam startupParam) throws Throwable {
      Resources tweakboxRes = XModuleResources.createInstance(startupParam.modulePath, null);
      byte[] crtPatch = assetAsByteArray(tweakboxRes, "crtfix_samsung_d506192d5049a4042fb84c0265edfe42.bsdiff");
...
getMD5Sum方法

这个方法可以返回文件系统中的一个文件的md5值。这需要app有对文件读取的权限。

getProcessPid方法

这个方法可以通过它的/proc/[pid]/cmdline的第一部分找到一个进程并且以字符串的形式返回PID。