转:http://blog.csdn.net/wxyyxc1992/article/details/17320911
1 Introduction
1.1 概述
Xposed是GitHUB上rovo89大大设计的一个针对Android平台的动态劫持项目,通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持。与采取传统的Inhook方式(详见Dynamic Dalvik Instrumentation分析这篇本章 )相比,Xposed在开机的时候完成对所有的Hook Function的劫持,在原Function执行的前后加上自定义代码。
Xposed框架的基本运行环境如下:
Configuration | RequireMent |
Root Access | 因为Xposed工作原理是在/system/bin目录下替换文件,在install的时候需要root权限,但是运行时不需要root权限。 |
版本要求 | 需要在Android 4.0以上版本的机器中 |
1.1.1 GitHub上Xposed资源梳理
- XposedBridge.jar:XposedBridge.jar是Xposed提供的jar文件,负责在Native层与FrameWork层进行交互。/system/bin/app_process进程启动过程中会加载该jar包,其它的Modules的开发与运行都是基于该jar包的。注意:XposedBridge.jar文件本质上是由XposedBridge生成的APK文件更名而来,有 图为证:install.sh
- Xposed:Xposed的C++部分,主要是用来替换/system/bin/app_process,并为XposedBridge提供JNI方法。
- XposedInstaller:Xposed的安装包,负责配置Xposed工作的环境并且提供对基于Xposed框架的Modules的管理。在安装XposedInstaller之后,app_process与XposedBridge.jar放置在了/data/data/de.robv.android.xposed.installer。
- XposedMods:使用Xposed开发的一些Modules,其中AppSettings是一个可以进行权限动态管理的应用
1.2 Mechanism:原理
1.2.1 Zygote
在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的。Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。这也是Xposed选择替换app_process的原因。
Zygote进程在启动的过程中,除了会创建一个Dalvik虚拟机实例之外,还会将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法来前面创建的Dalvik虚拟机实例中去。注意,一个应用程序进程被Zygote进程孵化出来的时候,不仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库。这也就是可以将XposedBridge这个jar包加载到每一个Android应用程序中的原因。XposedBridge有一个私有的Native(JNI)方法hookMethodNative,这个方法也在app_process中使用。这个函数提供一个方法对象利用Java的Reflection机制来对内置方法覆写。具体的实现可以看下文的Xposed源代码分析。
1.2.2 Hook/Replace
Xposed 框架中真正起作用的是对方法的hook。在Repackage技术中,如果要对APK做修改,则需要修改Smali代码中的指令。而另一种动态修改指令的技术需要在程序运行时基于匹配搜索来替换smali代码,但因为方法声明的多样性与复杂性,这种方法也比较复杂。
在Android系统启动的时候,zygote进程加载XposedBridge将所有需要替换的Method通过JNI方法hookMethodNative指向Native方法xposedCallHandler,xposedCallHandler在转入handleHookedMethod这个Java方法执行用户规定的Hook Func。
XposedBridge这个jar包含有一个私有的本地方法:hookMethodNative,该方法在附加的app_process程序中也得到了实现。它将一个方法对象作为输入参数(你可以使用Java的反射机制来获取这个方法)并且改变Dalvik虚拟机中对于该方法的定义。它将该方法的类型改变为native并且将这个方法的实现链接到它的本地的通用类的方法。换言之,当调用那个被hook的方法时候,通用的类方法会被调用而不会对调用者有任何的影响。在hookMethodNative的实现中,会调用XposedBridge中的handleHookedMethod这个方法来传递参数。handleHookedMethod这个方法类似于一个统一调度的Dispatch例程,其对应的底层的C++函数是xposedCallHandler。而handleHookedMethod实现里面会根据一个全局结构hookedMethodCallbacks来选择相应的hook函数,并调用他们的before, after函数。
当多模块同时Hook一个方法的时候,Xposed会自动根据Module的优先级来排序,调用顺序如下:
A.before -> B.before -> original method -> B.after -> A.after
2 源代码分析
本部分参考了看雪论坛某大神的文章,对于上一篇文章中代码分析部分几乎照搬了他的文章深表歉意。最近一直在reading and fucking the code,个人感觉流程方式叙述函数调用虽然调理比较明晰,但是还是较复杂,本文中采取面向函数/对象的介绍方式,在函数的介绍顺序上采取流程式。如果对于某个函数/方法有疑问的直接Ctrl+F在本文内搜索。
2.1 Application:XposedInstaller
XposedInstaller负责对Xposed运行环境的安装配置与Module的管理,无论对于开发者还是普通用户而言,都是接触到的第一个应用。另一方面,如果我们需要抛弃Xposed本来的Module管理机制,改编为我们自己的应用,也需要了解XposedInstaller的基本流程。
2.1.1 InstallerFragment
private String install(); @function 执行app_process的替换与XposedBridge.jar的写入操作。
2.2 Cpp:app_main.cpp
正如第一部分所言,Xposed会在Android系统启动的时候加载XposedBridge.jar并进行Function的替换操作。我们首先从CPP模块的Zygote进程的源代码app_main.cpp谈起。类似AOSP中的frameworks/base/cmds/app_process/app_main.cpp,即/system/bin/app_process这个zygote真实身份的应用程序的源代码。关于zygote进程的分析可以参照Android:AOSP&Core中的Zygote进程详解。
2.2.1 int main(int argc, char* const argv[])
Zygote进程首先从Main函数开始执行。
- int main(int argc, char* const argv[])
- {
- ...
-
-
-
- initTypePointers();
- ...
-
-
-
-
- xposedInfo();
-
- keepLoadingXposed =
-
-
-
-
-
- !isXposedDisabled()
- &&
- !xposedShouldIgnoreCommand(className, argc, argv)
- &&
-
-
-
-
-
- addXposedToClasspath(zygote);
-
-
-
-
- if (zygote) {
- runtime.start(keepLoadingXposed ? XPOSED_CLASS_DOTS : "com.android.internal.os.ZygoteInit",
- startSystemServer ? "start-system-server" : "");
- } else if (className) {
-
- runtime.mClassName = className;
- runtime.mArgC = argc - i;
- runtime.mArgV = argv + i;
- runtime.start(keepLoadingXposed ? XPOSED_CLASS_DOTS : "com.android.internal.os.RuntimeInit",
- application ? "application" : "tool");
- }
- else
- {
- fprintf(stderr, "Error: no class name or --zygote supplied.\n");
- app_usage();
- LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
- return 10;
- }
- }
annotation 1
在annotation 1之后,开始执行Dalvik启动的操作。一般情况下keepLoadingXposed值为true,以启动Zygote为例(zygote==true),分析接下来的代码。如果zygote为false,即是启动普通Application所属Dalvik的情景。
这一行代码是根据keepLoadingXposed 的值来判断是加载Xposed框架还是正常的ZygoteInit类。keepLoadingXposed值为true,则会加载XPOSED_CLASS_DOTS类,XPOSED_CLASS_DOTS值为de.robv.android.xposed.XposedBridge,即XposedBridge类。runtime是AppRuntime的实例,AppRuntime继承自AndroidRuntime。
- ...
- AppRuntime runtime;
- ...
-
-
-
-
- runtime.start(keepLoadingXposed ? XPOSED_CLASS_DOTS : "com.android.internal.os.RuntimeInit",
- application ? "application" : "tool");
2.2.2 void initTypePointers()
-
-
-
- void initTypePointers()
- {
- char sdk[PROPERTY_VALUE_MAX];
- const char *error;
- property_get("ro.build.version.sdk", sdk, "0");
- RUNNING_PLATFORM_SDK_VERSION = atoi(sdk);
- dlerror();
- if (RUNNING_PLATFORM_SDK_VERSION >= 18) {
- *(void **) (&PTR_atrace_set_tracing_enabled) = dlsym(RTLD_DEFAULT, "atrace_set_tracing_enabled");
- if ((error = dlerror()) != NULL) {
- ALOGE("Could not find address for function atrace_set_tracing_enabled: %s", error);
- }
- }
- }
2.2.3 AppRuntime
- ......
- static AndroidRuntime* gCurRuntime = NULL;
- ......
- AndroidRuntime::AndroidRuntime()
- {
- ......
- assert(gCurRuntime == NULL);
- gCurRuntime = this;
- }
2.2.3.1 void AndroidRuntime::start(const char* className, const bool startSystemServer)
AppRuntime继承自AndroidRuntime,其自身没有override start方法,则在AppRuntime中调用的还是其父类的start方法。AndroidRuntime::start(const char* className, const char* options)函数完成Dalvik虚拟机的初始化和启动以及运行参数className指定的类中的main方法。当启动完虚拟机后,会调用onVmCreated(JNIEnv* env)函数。该函数在AppRuntime类中被覆盖。因此直接看AppRuntime::onVmCreated(JNIEnv* env)。
- void AndroidRuntime::start(const char* className, const bool startSystemServer)
- {
- ......
- char* slashClassName = NULL;
- char* cp;
- JNIEnv* env;
- ......
-
- if (startVm(&mJavaVM, &env) != 0)
- goto bail;
-
-
-
- if (startReg(env) < 0) {
- LOGE("Unable to register all android natives\n");
- goto bail;
- }
-
-
-
-
-
- jclass stringClass;
- jobjectArray strArray;
- jstring classNameStr;
- jstring startSystemServerStr;
- stringClass = env->FindClass("java/lang/String");
- assert(stringClass != NULL);
- strArray = env->NewObjectArray(2, stringClass, NULL);
- assert(strArray != NULL);
- classNameStr = env->NewStringUTF(className);
- assert(classNameStr != NULL);
- env->SetObjectArrayElement(strArray, 0, classNameStr);
- startSystemServerStr = env->NewStringUTF(startSystemServer ?
- "true" : "false");
- env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
-
-
-
-
- jclass startClass;
- jmethodID startMeth;
- slashClassName = strdup(className);
- for (cp = slashClassName; *cp != '\0'; cp++)
- if (*cp == '.')
- *cp = '/';
- startClass = env->FindClass(slashClassName);
- if (startClass == NULL) {
- ......
- } else {
- startMeth = env->GetStaticMethodID(startClass, "main",
- "([Ljava/lang/String;)V");
- if (startMeth == NULL) {
- ......
- } else {
- env->CallStaticVoidMethod(startClass, startMeth, strArray);
- ......
- }
- }
- ......
- }
如果对Zygote启动过程熟悉的话,对后续XposedBridge的main函数是如何被调用的应该会很清楚。AndroidRuntime.cpp的start(const char* className, const char* options)函数完成环境变量的设置,Dalvik虚拟机的初始化和启动,同时Xposed在onVmCreated(JNIEnv* env)中完成了自身JNI方法的注册。此后start()函数会注册Android系统的JNI方法,调用传入的className指定类的main方法,进入Java世界。
由于此时参数className为de.robv.android.xposed.XposedBridge,因此会调用XposedBridge类的main方法。
2.2.3.2 virtual void onVmCreated(JNIEnv* env)
- virtual void onVmCreated(JNIEnv* env)
- {
-
-
-
-
-
-
- keepLoadingXposed = xposedOnVmCreated(env, mClassName);
- if (mClassName == NULL) {
- return;
- }
- char* slashClassName = toSlashClassName(mClassName);
- mClass = env->FindClass(slashClassName);
- if (mClass == NULL) {
- ALOGE("ERROR: could not find class '%s'\n", mClassName);
- }
- free(slashClassName);
- mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
- }
2.3 Cpp:xposed.cpp
2.3.1 isXposedDisabled
这个函数负责来判断Xposed框架是否为启用,如果不小心Module写错了,可以通过shell手动创建文件来防止手机鸟掉。
-
-
-
- bool isXposedDisabled() {
-
- if (access(XPOSED_LOAD_BLOCKER, F_OK) == 0) {
- ALOGE("found %s, not loading Xposed\n", XPOSED_LOAD_BLOCKER);
- return true;
- }
- return false;
- }
2.3.2 xposedShouldIgnoreCommand
这个函数写的还是极好的,可以看看来增加点见识。
-
-
-
- bool xposedShouldIgnoreCommand(const char* className, int argc, const char* const argv[]) {
- if (className == NULL || argc < 4 || strcmp(className, "com.android.commands.am.Am") != 0)
- return false;
- if (strcmp(argv[2], "broadcast") != 0 && strcmp(argv[2], "start") != 0)
- return false;
- bool mightBeSuperuser = false;
- for (int i = 3; i < argc; i++) {
- if (strcmp(argv[i], "com.noshufou.android.su.RESULT") == 0
- || strcmp(argv[i], "eu.chainfire.supersu.NativeAccess") == 0)
- return true;
- if (mightBeSuperuser && strcmp(argv[i], "--user") == 0)
- return true;
- char* lastComponent = strrchr(argv[i], '.');
- if (!lastComponent)
- continue;
- if (strcmp(lastComponent, ".RequestActivity") == 0
- || strcmp(lastComponent, ".NotifyActivity") == 0
- || strcmp(lastComponent, ".SuReceiver") == 0)
- mightBeSuperuser = true;
- }
- return false;
- }
2.3.3 addXposedToClasspath
若有新版本的XposedBridge,重命名为XposedBridge.jar并返回false;判断XposedBridge.jar文件是否存在,若不存在,返回false,否则将XposedBridge.jar所在路径添加到CLASSPATH环境变量中,返回true。
-
-
-
- bool addXposedToClasspath(bool zygote) {
- ALOGI("-----------------\n");
-
- if (zygote && access(XPOSED_JAR_NEWVERSION, R_OK) == 0) {
- ALOGI("Found new Xposed jar version, activating it\n");
- if (rename(XPOSED_JAR_NEWVERSION, XPOSED_JAR) != 0) {
- ALOGE("could not move %s to %s\n", XPOSED_JAR_NEWVERSION, XPOSED_JAR);
- return false;
- }
- }
- if (access(XPOSED_JAR, R_OK) == 0) {
- char* oldClassPath = getenv("CLASSPATH");
- if (oldClassPath == NULL) {
- setenv("CLASSPATH", XPOSED_JAR, 1);
- } else {
- char classPath[4096];
- sprintf(classPath, "%s:%s", XPOSED_JAR, oldClassPath);
- setenv("CLASSPATH", classPath, 1);
- }
- ALOGI("Added Xposed (%s) to CLASSPATH.\n", XPOSED_JAR);
- return true;
- } else {
- ALOGE("ERROR: could not access Xposed jar '%s'\n", XPOSED_JAR);
- return false;
- }
- }
2.3.4 bool xposedOnVmCreated(JNIEnv* env, const char* className)
- bool xposedOnVmCreated(JNIEnv* env, const char* className) {
- if (!keepLoadingXposed)
- return false;
-
- startClassName = className;
-
-
-
-
- xposedInitMemberOffsets();
-
-
- patchReturnTrue((void*) &dvmCheckClassAccess);
- patchReturnTrue((void*) &dvmCheckFieldAccess);
- patchReturnTrue((void*) &dvmInSamePackage);
- if (access(XPOSED_DIR "conf/do_not_hook_dvmCheckMethodAccess", F_OK) != 0)
- patchReturnTrue((void*) &dvmCheckMethodAccess);
-
- jclass miuiResourcesClass = env->FindClass(MIUI_RESOURCES_CLASS);
- if (miuiResourcesClass != NULL) {
- ClassObject* clazz = (ClassObject*)dvmDecodeIndirectRef(dvmThreadSelf(), miuiResourcesClass);
- if (dvmIsFinalClass(clazz)) {
- ALOGD("Removing final flag for class '%s'", MIUI_RESOURCES_CLASS);
- clazz->accessFlags &= ~ACC_FINAL;
- }
- }
- env->ExceptionClear();
-
- xposedClass = env->FindClass(XPOSED_CLASS);
- xposedClass = reinterpret_cast<jclass>(env->NewGlobalRef(xposedClass));
-
- if (xposedClass == NULL) {
- ALOGE("Error while loading Xposed class '%s':\n", XPOSED_CLASS);
- dvmLogExceptionStackTrace();
- env->ExceptionClear();
- return false;
- }
-
- ALOGI("Found Xposed class '%s', now initializing\n", XPOSED_CLASS);
-
-
-
- register_de_robv_android_xposed_XposedBridge(env);
- register_android_content_res_XResources(env);
- return true;
- }
2.3.5 JNI函数注册区域
- static const JNINativeMethod xposedMethods[] = {
- {"getStartClassName", "()Ljava/lang/String;", (void*)de_robv_android_xposed_XposedBridge_getStartClassName},
- {"initNative", "()Z", (void*)de_robv_android_xposed_XposedBridge_initNative},
- {"hookMethodNative", "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V", (void*)de_robv_android_xposed_XposedBridge_hookMethodNative},
- };
- static jobject de_robv_android_xposed_XposedBridge_getStartClassName(JNIEnv* env, jclass clazz) {
- return env->NewStringUTF(startClassName);
- }
- static int register_de_robv_android_xposed_XposedBridge(JNIEnv* env) {
- return env->RegisterNatives(xposedClass, xposedMethods, NELEM(xposedMethods));
- }
- static const JNINativeMethod xresourcesMethods[] = {
- {"rewriteXmlReferencesNative", "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)V", (void*)android_content_res_XResources_rewriteXmlReferencesNative},
- };
- static int register_android_content_res_XResources(JNIEnv* env) {
- return env->RegisterNatives(xresourcesClass, xresourcesMethods, NELEM(xresourcesMethods));
- }
2.3.6 initNative
该函数主要完成对XposedBridge类中函数的引用,这样可以实现在Native层对Java层函数的调用。譬如获取XposedBridge类中的handlHookedMethod函数的method id,同时赋值给全局变量xposedHandleHookedMethod。另外,initNative函数还会获取android.content.res.XResources类中的方法,完成对资源文件的处理;调用register_android_content_res_XResources注册rewriteXmlReferencesNative这个JNI方法。
- static jboolean de_robv_android_xposed_XposedBridge_initNative(JNIEnv* env, jclass clazz) {
- ...
- xposedHandleHookedMethod = (Method*) env->GetStaticMethodID(xposedClass, "handleHookedMethod",
- "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
- ...
- xresourcesClass = env->FindClass(XRESOURCES_CLASS);
- xresourcesClass = reinterpret_cast<jclass>(env->NewGlobalRef(xresourcesClass));
- ...
- if (register_android_content_res_XResources(env) != JNI_OK) {
- ALOGE("Could not register natives for '%s'\n", XRESOURCES_CLASS);
- return false;
- }
- xresourcesTranslateResId = env->GetStaticMethodID(xresourcesClass, "translateResId",
- "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I");
- ...
- xresourcesTranslateAttrId = env->GetStaticMethodID(xresourcesClass, "translateAttrId",
- "(Ljava/lang/String;Landroid/content/res/XResources;)I");
- ...
- return true;
- }
2.3.7 hookMethodNative
-
-
-
-
-
- static void de_robv_android_xposed_XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject declaredClassIndirect, jint slot) {
-
- if (declaredClassIndirect == NULL) {
- dvmThrowIllegalArgumentException("declaredClass must not be null");
- return;
- }
-
-
- ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);
- Method* method = dvmSlotToMethod(declaredClass, slot);
- if (method == NULL) {
- dvmThrowNoSuchMethodError("could not get internal representation for method");
- return;
- }
-
- if (findXposedOriginalMethod(method) != xposedOriginalMethods.end()) {
-
- return;
- }
-
-
- xposedOriginalMethods.push_front(*((MethodXposedExt*)method));
-
- SET_METHOD_FLAG(method, ACC_NATIVE);
- method->nativeFunc = &xposedCallHandler;
- method->registersSize = method->insSize;
- method->outsSize = 0;
- if (PTR_gDvmJit != NULL) {
-
- MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true;
- }
- }
2.3.8 xposedCallHandler
- static void xposedCallHandler(const u4* args, JValue* pResult, const Method* method, ::Thread* self) {
- XposedOriginalMethodsIt original = findXposedOriginalMethod(method);
- if (original == xposedOriginalMethods.end()) {
- dvmThrowNoSuchMethodError("could not find Xposed original method - how did you even get here?");
- return;
- }
-
- ThreadStatus oldThreadStatus = MEMBER_VAL(self, Thread, status);
- JNIEnv* env = MEMBER_VAL(self, Thread, jniEnv);
-
-
- jobject originalReflected = env->ToReflectedMethod(
- (jclass)xposedAddLocalReference(self, original->clazz),
- (jmethodID)method,
- true);
-
-
- const char* desc = &method->shorty[1];
- Object* thisObject = NULL;
- size_t srcIndex = 0;
- size_t dstIndex = 0;
-
-
- if (!dvmIsStaticMethod(&(*original))) {
- thisObject = (Object*) xposedAddLocalReference(self, (Object*)args[0]);
- srcIndex++;
- }
-
- jclass objectClass = env->FindClass("java/lang/Object");
- jobjectArray argsArray = env->NewObjectArray(strlen(method->shorty) - 1, objectClass, NULL);
-
- while (*desc != '\0') {
- char descChar = *(desc++);
- JValue value;
- Object* obj;
-
- switch (descChar) {
- case 'Z':
- case 'C':
- case 'F':
- case 'B':
- case 'S':
- case 'I':
- value.i = args[srcIndex++];
- obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
- dvmReleaseTrackedAlloc(obj, NULL);
- break;
- case 'D':
- case 'J':
- value.j = dvmGetArgLong(args, srcIndex);
- srcIndex += 2;
- obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));
- dvmReleaseTrackedAlloc(obj, NULL);
- break;
- case '[':
- case 'L':
- obj = (Object*) args[srcIndex++];
- break;
- default:
- ALOGE("Unknown method signature description character: %c\n", descChar);
- obj = NULL;
- srcIndex++;
- }
- env->SetObjectArrayElement(argsArray, dstIndex++, xposedAddLocalReference(self, obj));
- }
-
-
-
-
-
-
-
-
- jobject resultRef = env->CallStaticObjectMethod(
- xposedClass, xposedHandleHookedMethod, originalReflected, thisObject, argsArray);
-
-
- if (env->ExceptionCheck()) {
- dvmChangeStatus(self, oldThreadStatus);
- return;
- }
-
-
- Object* result = dvmDecodeIndirectRef(self, resultRef);
- ClassObject* returnType = dvmGetBoxedReturnType(method);
- if (returnType->primitiveType == PRIM_VOID) {
-
- } else if (result == NULL) {
- if (dvmIsPrimitiveClass(returnType)) {
- dvmThrowNullPointerException("null result when primitive expected");
- }
- pResult->l = NULL;
- } else {
- if (!dvmUnboxPrimitive(result, returnType, pResult)) {
- dvmThrowClassCastException(result->clazz, returnType);
- }
- }
-
-
- dvmChangeStatus(self, oldThreadStatus);
- }
2.4 Java:XposedBridge
注意,以下介绍的XposedBridge中所调用的方法都是在
2.4.1 private static void main(String[] args)
由Zygote跳入XposedBridge执行的第一个函数。
- private static void main(String[] args) {
-
-
-
- String startClassName = getStartClassName();
-
-
- try {
-
- try {
- File logFile = new File("/data/xposed/debug.log");
- if (startClassName == null && logFile.length() > MAX_LOGFILE_SIZE)
- logFile.renameTo(new File("/data/xposed/debug.log.old"));
- logWriter = new PrintWriter(new FileWriter(logFile, true));
- logFile.setReadable(true, false);
- logFile.setWritable(true, false);
- } catch (IOException ignored) {}
-
- String date = DateFormat.getDateTimeInstance().format(new Date());
- log("-----------------\n" + date + " UTC\n"
- + "Loading Xposed (for " + (startClassName == null ? "Zygote" : startClassName) + ")...");
-
-
-
-
- if (initNative()) {
- if (startClassName == null) {
-
-
-
-
- initXbridgeZygote();
- }
-
-
-
-
- loadModules(startClassName);
- } else {
- log("Errors during native Xposed initialization");
- }
- } catch (Throwable t) {
- log("Errors during Xposed initialization");
- log(t);
- }
-
-
- if (startClassName == null)
-
-
-
- ZygoteInit.main(args);
- else
- RuntimeInit.main(args);
- }
2.4.2 void initXbridgeZygote():Hook系统关键函数
initXbridgeZygote完成对一些函数的hook操作,主要是调用XposedHelpers类中的findAndHookMethod完成。
- private static void initXbridgeZygote() throws Exception {
- final HashSet<String> loadedPackagesInProcess = new HashSet<String>(1);
-
-
-
-
-
-
-
-
- findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- ActivityThread activityThread = (ActivityThread) param.thisObject;
- ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo");
- ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName");
- if (instrumentationName != null) {
- XposedBridge.log("Instrumentation detected, disabling framework for " + appInfo.packageName);
- disableHooks = true;
- return;
- }
- CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo");
- if (appInfo.sourceDir == null)
- return;
-
- setObjectField(activityThread, "mBoundApplication", param.args[0]);
- loadedPackagesInProcess.add(appInfo.packageName);
- LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
- XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
-
- LoadPackageParam lpparam = new LoadPackageParam(loadedPackageCallbacks);
- lpparam.packageName = appInfo.packageName;
- lpparam.processName = (String) getObjectField(param.args[0], "processName");
- lpparam.classLoader = loadedApk.getClassLoader();
- lpparam.appInfo = appInfo;
- lpparam.isFirstApplication = true;
- XC_LoadPackage.callAll(lpparam);
- }
- });
- ...
- }
2.4.3 loadModules(String startClassName):开始Hook Module中自定义函数
-
-
-
-
- private static void loadModules(String startClassName) throws IOException {
- BufferedReader apks = new BufferedReader(new FileReader(BASE_DIR + "conf/modules.list"));
- String apk;
- while ((apk = apks.readLine()) != null) {
- loadModule(apk, startClassName);
- }
- apks.close();
- }
-
-
-
-
-
- private static void loadModule(String apk, String startClassName) {
- log("Loading modules from " + apk);
- if (!new File(apk).exists()) {
- log(" File does not exist");
- return;
- }
- ClassLoader mcl = new PathClassLoader(apk, BOOTCLASSLOADER);
-
- InputStream is = mcl.getResourceAsStream("assets/xposed_init");
- if (is == null) {
- log("assets/xposed_init not found in the APK");
- return;
- }
- BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
- try {
- String moduleClassName;
- while ((moduleClassName = moduleClassesReader.readLine()) != null) {
- moduleClassName = moduleClassName.trim();
- if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
- continue;
- try {
- log(" Loading class " + moduleClassName);
- Class<?> moduleClass = mcl.loadClass(moduleClassName);
- if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
- log("This class doesn't implement any sub-interface of IXposedMod, skipping it");
- continue;
- }
-
-
-
-
- final Object moduleInstance = moduleClass.newInstance();
- if (startClassName == null) {
- if (moduleInstance instanceof IXposedHookZygoteInit) {
- IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
- param.modulePath = apk;
- ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
- }
- if (moduleInstance instanceof IXposedHookLoadPackage)
- hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
- if (moduleInstance instanceof IXposedHookInitPackageResources)
- hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
- } else {
- if (moduleInstance instanceof IXposedHookCmdInit) {
- IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
- param.modulePath = apk;
- param.startClassName = startClassName;
- ((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
- }
- }
- } catch (Throwable t) {
- log(t);
- }
- }
- } catch (IOException e) {
- log(e);
- } finally {
- try {
- is.close();
- } catch (IOException ignored) {
- }
- }
- }
annotation 1
以下代码段主要将module中定义的类使用instanceof操作符确定其所属父类并依次执行操作,Xposed提供的接口类主要分为:
IXposedHookZygoteInit | 该类主要提供ZygoteInit接口函数,用于在Zygote进程启动之前执行相关代码。 |
IXposedHookLoadPackage | 主要的Hook操作类。 |
IXposedHookInitPackageResources | 提供资源Hook相关所需要的函数。 |
IXposedHookCmdInit | Hook并处理启动新的Application Dalvik虚拟机时所需要的参数。 |
2.4.4 hookMethod
XposedBridge类的静态方法hookMethod实现对函数的hook和回调函数的注册。
-
-
-
-
-
- public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
- if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
- throw new IllegalArgumentException("only methods and constructors can be hooked");
- }
-
- boolean newMethod = false;
- TreeSet<XC_MethodHook> callbacks;
-
-
-
-
-
-
- synchronized (hookedMethodCallbacks) {
- callbacks = hookedMethodCallbacks.get(hookMethod);
- if (callbacks == null) {
- callbacks = new TreeSet<XC_MethodHook>();
- hookedMethodCallbacks.put(hookMethod, callbacks);
- newMethod = true;
- }
- }
- synchronized (callbacks) {
- callbacks.add(callback);
- }
-
- if (newMethod) {
- Class<?> declaringClass = hookMethod.getDeclaringClass();
- int slot = (int) getIntField(hookMethod, "slot");
-
-
-
-
-
-
- hookMethodNative(declaringClass, slot);
- }
-
- return callback.new Unhook(hookMethod);
- }
2.4.5 handleHookedMethod
handleHookedMethod将被hook的代码又交还给java层实现。
- private static Object handleHookedMethod(Member method, Object thisObject, Object[] args) throws Throwable {
- if (disableHooks) {
- try {
- return invokeOriginalMethod(method, thisObject, args);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- }
首先判断hook是否被禁用,若是,则直接调用invokeOriginalMethod函数,完成对原始函数的执行。关于如何执行原始函数的,可以继续跟踪下去分析。
- TreeSet<XC_MethodHook> callbacks;
- synchronized (hookedMethodCallbacks) {
- callbacks = hookedMethodCallbacks.get(method);
- }
- if (callbacks == null || callbacks.isEmpty()) {
- try {
- return invokeOriginalMethod(method, thisObject, args);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- }
- synchronized (callbacks) {
- callbacks = ((TreeSet<XC_MethodHook>) callbacks.clone());
- }
根据method值,从hookedMethodCallbacks中获取对应的callback信息。hookedMethodCallbacks的分析可以参考之前对hookMethod的分析。callbacks中存储了所有对该method进行hook的beforeHookedMethod和afterHookedMethod。接着从callbacks中获取beforeHookedMethod和afterHookedMethod的迭代器。
- Iterator<XC_MethodHook> before = callbacks.iterator();
- Iterator<XC_MethodHook> after = callbacks.descendingIterator();
-
- while (before.hasNext()) {
- try {
- before.next().beforeHookedMethod(param);
- } catch (Throwable t) {
- XposedBridge.log(t);
-
-
- param.setResult(null);
- param.returnEarly = false;
- continue;
- }
-
- if (param.returnEarly) {
-
- while (before.hasNext() && after.hasNext()) {
- before.next();
- after.next();
- }
- break;
- }
- }
-
- if (!param.returnEarly) {
- try {
- param.setResult(invokeOriginalMethod(method, param.thisObject, param.args));
- } catch (InvocationTargetException e) {
- param.setThrowable(e.getCause());
- }
- }
-
- while (after.hasNext()) {
- Object lastResult = param.getResult();
- Throwable lastThrowable = param.getThrowable();
-
- try {
- after.next().afterHookedMethod(param);
- } catch (Throwable t) {
- XposedBridge.log(t);
-
-
- if (lastThrowable == null)
- param.setResult(lastResult);
- else
- param.setThrowable(lastThrowable);
- }
- }
-
- if (param.hasThrowable())
- throw param.getThrowable();
- else
- return param.getResult();
通过以上的分析,基本能够弄清楚Xposed框架实现hook的原理。Xposed将需要hook的函数替换成Native方法xposedCallHandler,这样Dalvik在执行被hook的函数时,就会直接调用xposedCallHandler,xposedCallHandler再调用XposedBridge类的handleHookedMethod完成注册的beforeHookedMethod以及afterHookedMethod的调用,这两类回调函数之间,会调用原始函数,完成正常的功能。
2.5 Java:Class XposedHelper
2.5.1 findAndHookMethod
-
-
-
-
-
-
-
- public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
- return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
- }
- public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
-
-
-
- if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
- throw new IllegalArgumentException("no callback defined");
-
- XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
-
-
-
-
-
-
-
-
-
- Method m = findMethodExact(clazz, methodName, parameterTypesAndCallback);
-
-
-
-
-
-
-
- return XposedBridge.hookMethod(m, callback);
- }
2.5.2 findMethodExact
-
-
-
- public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) {
- Class<?>[] parameterClasses = null;
- for (int i = parameterTypes.length - 1; i >= 0; i--) {
- Object type = parameterTypes[i];
- if (type == null)
- throw new ClassNotFoundError("parameter type must not be null", null);
-
-
- if (type instanceof XC_MethodHook)
- continue;
-
- if (parameterClasses == null)
- parameterClasses = new Class<?>[i+1];
-
- if (type instanceof Class)
- parameterClasses[i] = (Class<?>) type;
- else if (type instanceof String)
- parameterClasses[i] = findClass((String) type, clazz.getClassLoader());
- else
- throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
- }
-
-
- if (parameterClasses == null)
- parameterClasses = new Class<?>[0];
-
- return findMethodExact(clazz, methodName, parameterClasses);
- }
-
-
-
-
-
-
-
-
- public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
- StringBuilder sb = new StringBuilder(clazz.getName());
- sb.append('#');
- sb.append(methodName);
- sb.append(getParametersString(parameterTypes));
- sb.append("#exact");
- String fullMethodName = sb.toString();
-
-
-
-
- if (methodCache.containsKey(fullMethodName)) {
- Method method = methodCache.get(fullMethodName);
- if (method == null)
- throw new NoSuchMethodError(fullMethodName);
- return method;
- }
-
- try {
- Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
- method.setAccessible(true);
-
-
-
-
- methodCache.put(fullMethodName, method);
- return method;
- } catch (NoSuchMethodException e) {
- methodCache.put(fullMethodName, null);
- throw new NoSuchMethodError(fullMethodName);
- }
- }
annotation 1
在findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes)函数中,首先将类名、方法名以及参数信息构建成一个键值,以该键值从methodCache中查找是否存在Method实例,methodCache相当于缓存了对应的Method实例。如果没有找到,会调用Class类的getDeclaredMethod(String name,Class<?>... parameterTypes)方法获取Method实例,同时将该Method设置为可访问,加入到methodCache中。
2.5 Java:Class XC_MethodHook
XC_MethodHook类中的beforeHookedMethod函数会在被hook的函数调用之前调用,而afterHookedMethod函数会在被hook的函数调用之后调用。这两个函数的方法体为空,需要在实例化XC_MethodHook时根据情况填充方法体。XC_MethodHook的内部类MethodHookParam保存了相应的信息,如调用方法的参数,this对象,函数的返回值等。
- public abstract class XC_MethodHook extends XCallback {
- public XC_MethodHook() {
- super();
- }
- public XC_MethodHook(int priority) {
- super(priority);
- }
-
-
-
-
-
-
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
-
-
-
-
-
-
- protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
-
-
- public static class MethodHookParam extends XCallback.Param {
-
- public Member method;
-
- public Object thisObject;
-
- public Object[] args;
-
- private Object result = null;
- private Throwable throwable = null;
- boolean returnEarly = false;
-
-
- public Object getResult() {
- return result;
- }
-
-
-
-
-
-
- public void setResult(Object result) {
- this.result = result;
- this.throwable = null;
- this.returnEarly = true;
- }
-
-
- public Throwable getThrowable() {
- return throwable;
- }
-
-
- public boolean hasThrowable() {
- return throwable != null;
- }
-
-
-
-
-
-
- public void setThrowable(Throwable throwable) {
- this.throwable = throwable;
- this.result = null;
- this.returnEarly = true;
- }
-
-
- public Object getResultOrThrowable() throws Throwable {
- if (throwable != null)
- throw throwable;
- return result;
- }
- }
- public class Unhook implements IXUnhook {
- private final Member hookMethod;
- public Unhook(Member hookMethod) {
- this.hookMethod = hookMethod;
- }
-
- public Member getHookedMethod() {
- return hookMethod;
- }
-
- public XC_MethodHook getCallback() {
- return XC_MethodHook.this;
- }
- @Override
- public void unhook() {
- XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
- }
- }
- }