Android Hook框架Xposed原理與原始碼分析

lvxiangan發表於2019-01-16

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框架的基本執行環境如下:


 

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[])
{
    ...
    /**
    @function 該函式對於SDK>=18的會獲取到atrace_set_tracing_enabled的函式指標,獲取到的指標會在Zygote初始化過程中呼叫,函式定義見程式碼段下方
    */
    initTypePointers();
    ...
    /**
    @function 該函式主要獲取一些屬性值譬如SDK版本,裝置廠商,裝置型號等資訊並且列印到Log檔案中
    @description xposedInfo函式定義在xposed.cpp中
    */
    xposedInfo();
    //變數標誌是否需要載入Xposed框架
    keepLoadingXposed =
    
    /**
    @function 該函式主要分析Xposed框架是否被禁用了。
    @description 該函式定義在了xposed.cpp中
    */
    !isXposedDisabled() 
    && 
    !xposedShouldIgnoreCommand(className, argc, argv) 
    && 
    /**
    @function 將XposedBridge所在路徑加入到系統環境變數CLASSPATH中
    @para zygote bool型別,描述是否需要重啟zygote,引數來源於啟動指令“--zygote”引數
    @description 該函式定義在了xposed.cpp中
    */
    addXposedToClasspath(zygote);
    
    /**
    @annotation 1
    */
    if (zygote) {
        runtime.start(keepLoadingXposed ? XPOSED_CLASS_DOTS : "com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        // Remainder of args get passed to startup class main()
        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;
...
/**
@function AppRuntime類的方法,用於啟動Dalvik虛擬機器。
@description 定義於app_main.cpp中
*/
runtime.start(keepLoadingXposed ? XPOSED_CLASS_DOTS : "com.android.internal.os.RuntimeInit",
                  application ? "application" : "tool");

2.2.2 void initTypePointers()

/**
@function 該函式對於SDK>=18的會獲取到atrace_set_tracing_enabled的函式指標,獲取到的指標會在Zygote初始化過程中呼叫
*/
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);        // one per process
    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;
    ......
    /* start the virtual machine */
    if (startVm(&mJavaVM, &env) != 0)
        goto bail;
    /*
    * Register android functions.
    */
    if (startReg(env) < 0) {
        LOGE("Unable to register all android natives\n");
        goto bail;
    }
    /*
    * We want to call main() with a String array with arguments in it.
    * At present we only have one argument, the class name.  Create an
    * array to hold it.
    */
    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);
    /*
    * Start VM.  This thread becomes the main thread of the VM, and will
    * not return until the VM exits.
    */
    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)
  {
      /**
      @function 用於載入XposedBridge.jar這個類,本函式只有這個函式是增加的
      @para JNI環境指標
      @para mClassName runtime.mClassName = className這句指定
      @description 定義於xposed.cpp中
      */
        keepLoadingXposed = xposedOnVmCreated(env, mClassName);
        if (mClassName == NULL) {
            return; // Zygote. Nothing to do here.
        }
        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手動建立檔案來防止手機鳥掉。

/**
@function 判斷Xposed框架是否被啟用 
*/
bool isXposedDisabled() {
    //該函式通過讀取/data/data/de.robv.android.xposed.installer/conf/disabled檔案來判斷Xposed框架是否被禁用,如果該檔案存在,則表示禁用Xposed。
    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

這個函式寫的還是極好的,可以看看來增加點見識。

/**
@function 為了避免Superuser類似工具濫用Xposed的log檔案,此函式會判斷是否是SuperUser等工具的啟動請求。
*/
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。

/**
@function 將XposedBridge.jar新增到CLASSPATH
*/
bool addXposedToClasspath(bool zygote) {
    ALOGI("-----------------\n");
    // do we have a new version and are (re)starting zygote? Then load it!
    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;
    //將要啟動的Class的Name賦值給startClassName    
    startClassName = className;
    /**
    @function 根據JIT是否存在對部分結構體中的成員偏移進行初始化。
    @description 定義在xposed.cpp中。關於JIT的介紹詳見我的關於Dalvik虛擬機器的介紹
    */
    xposedInitMemberOffsets();
 
    //下面開始對部分訪問檢查設定為true
    patchReturnTrue((void*) &dvmCheckClassAccess);
    patchReturnTrue((void*) &dvmCheckFieldAccess);
    patchReturnTrue((void*) &dvmInSamePackage);
    if (access(XPOSED_DIR "conf/do_not_hook_dvmCheckMethodAccess", F_OK) != 0)
        patchReturnTrue((void*) &dvmCheckMethodAccess);
    //針對MIUI作業系統移除android.content.res.MiuiResources類的final修飾符
    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();
    //開始獲取XposedBridge類並new一個新的全域性引用
    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);
    /**
    @function 開始註冊XposedBridge中JNI函式
    */
    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

/**
@function hookMethodNative 將輸入的Class中的Method方法的nativeFunc替換為xposedCallHandler
@para declaredClassIndirect 類物件
@para slot Method在類中的偏移位置
*/
static void de_robv_android_xposed_XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject declaredClassIndirect, jint slot) {
    // Usage errors?
    if (declaredClassIndirect == NULL) {
        dvmThrowIllegalArgumentException("declaredClass must not be null");
        return;
    }
    
    // Find the internal representation of the method
    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()) {
        // already hooked
        return;
    }
    
    // Save a copy of the original method
    xposedOriginalMethods.push_front(*((MethodXposedExt*)method));
    // Replace method with our own code
    SET_METHOD_FLAG(method, ACC_NATIVE);
    method->nativeFunc = &xposedCallHandler;
    method->registersSize = method->insSize;
    method->outsSize = 0;
    if (PTR_gDvmJit != NULL) {
        // reset JIT cache
        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);
    
    // get java.lang.reflect.Method object for original method
    jobject originalReflected = env->ToReflectedMethod(
        (jclass)xposedAddLocalReference(self, original->clazz),
        (jmethodID)method,
        true);
  
    // convert/box arguments
    const char* desc = &method->shorty[1]; // [0] is the return type.
    Object* thisObject = NULL;
    size_t srcIndex = 0;
    size_t dstIndex = 0;
    
    // for non-static methods determine the "this" pointer
    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));
    }
    
    /**
    @function 呼叫Java中方法
    @para xposedClass 
    @para xposedHandleHookedMethod     
    xposedHandleHookedMethod = env->GetStaticMethodID(xposedClass, "handleHookedMethod",
        "(Ljava/lang/reflect/Member;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
    */
    jobject resultRef = env->CallStaticObjectMethod(
        xposedClass, xposedHandleHookedMethod, originalReflected, thisObject, argsArray);
        
    // exceptions are thrown to the caller
    if (env->ExceptionCheck()) {
        dvmChangeStatus(self, oldThreadStatus);
        return;
    }
    
    // return result with proper type
    Object* result = dvmDecodeIndirectRef(self, resultRef);
    ClassObject* returnType = dvmGetBoxedReturnType(method);
    if (returnType->primitiveType == PRIM_VOID) {
        // ignored
    } 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);
        }
    }
    
    // set the thread status back to running. must be done after the last env->...()
    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) {
        /**
        @function Native方法,用於獲取當前啟動的類名
        */
        String startClassName = getStartClassName();
 
        // 初始化Xposed框架與模組
        try {
            // 初始化Log檔案
            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) + ")...");
            /**
            @function 負責獲取XposedBridge中Java函式的引用
            @description Native函式,定義於xposed.cpp中
            */
            if (initNative()) {
                if (startClassName == null) {
                /**
                @function 如果是啟動Zygote程式,則執行initXbridgeZygote函式
                @description 定義在XposedBridge類中
                */
                    initXbridgeZygote();
                }
                /**
                @function 負責載入所有模組
                @description 定義在XposedBridge類中
                */
                loadModules(startClassName);
            } else {
                log("Errors during native Xposed initialization");
            }
        } catch (Throwable t) {
            log("Errors during Xposed initialization");
            log(t);
        }
        
        // 呼叫原來的啟動引數
        if (startClassName == null)
        /**
        @description com.android.internal.os.ZygoteInit
        */
            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);
        
        /**
        @function 執行Hook替換操作
        @para ActivityThread.class 需要hook的函式所在的類;
        @para "handleBindApplication" 需要hook的函式名
        @para  "android.app.ActivityThread.AppBindData" 不定引數
        @description 定義在XposedHelper中
        */
        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中自定義函式

/**
@function 用於載入所有Xposed模組中定義的Hook操作
@para startClassName 當前開啟的Class Name
*/
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();
}
/**
@function 從各個模組定義的xposed_init檔案中進行目標函式的Hook
@para apk 模組名稱,即所屬Application
@para startClassName 當前開啟的Class Name
*/
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;
                    }
                    /*
                        @description 在Zygote啟動之前執行自定義的ZygoteInit函式等自定義的Module指令
                        @annotation 1
                    */
                    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和回撥函式的註冊。

/**
 * @function Hook指定方法並設定前後回撥函式
 * @param 方法名
 * @param 回撥函式組 
 */
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;
    /*    
    HookedMethodCallbacks是一個hashMap的例項,儲存每個需要hook的method的回撥函式。    
    首先檢視hookedMethodCallbacks中是否有hookMethod對應的callbacks的集合,
    如果沒有,則建立一個TreeSet,將該callbacks加入到hookedMethodCallbacks中,同時將newMethod標誌設為true。
    接下來將傳入的callback新增到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");
        /**
        @function 呼叫Native方法hookMethodNative,通知Native層declaringClass中的某個method是被hook的
        @para declaringClass 要hook的目標類
        @para slot Method編號
        @description Native方法,定義在了xposed.cpp中
        */
        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();
// call "before method" callbacks
while (before.hasNext()) {
    try {
        before.next().beforeHookedMethod(param);
    } catch (Throwable t) {
        XposedBridge.log(t);
        
        // reset result (ignoring what the unexpectedly exiting callback did)
        param.setResult(null);
        param.returnEarly = false;
        continue;
    }
    
    if (param.returnEarly) {
        // skip remaining "before" callbacks and corresponding "after" callbacks
        while (before.hasNext() && after.hasNext()) {
            before.next();
            after.next();
        }
        break;
    }
}
// call original method if not requested otherwise
if (!param.returnEarly) {
    try {
        param.setResult(invokeOriginalMethod(method, param.thisObject, param.args));
    } catch (InvocationTargetException e) {
        param.setThrowable(e.getCause());
    }
}
// call "after method" callbacks
while (after.hasNext()) {
    Object lastResult =  param.getResult();
    Throwable lastThrowable = param.getThrowable();
    
    try {
        after.next().afterHookedMethod(param);
    } catch (Throwable t) {
        XposedBridge.log(t);
        
        // reset to last result (ignoring what the unexpectedly exiting callback did)
        if (lastThrowable == null)
            param.setResult(lastResult);
        else
            param.setThrowable(lastThrowable);
    }
}
// return
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

/**
@function 根據輸入的類名與方法名進行Hook操作。
@para className 輸入的類的名稱
@para classLoader 當前上下文中的類載入器
@para methodName 方法名稱
@para parameterTypesAndCallback 不定引數組,包括方法引數與回撥函式
*/
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) {
    /**
    @function 判斷不定引數組的最後一個引數是否為XC_MethodHook
    */
    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];
    
    /**
    @function 在一個類中查詢到指定方法並將該方法設定為Accessible
    @para clazz 類物件
    @para methodName 方法名稱
    @para parameterTypesAndCallback 方法引數
    @description 該類定義在了XposedHelper中
    @exception 如果沒有在指定的類中查詢到指定方法,則丟擲NoSuchMethodError這個異常
    */
    Method m = findMethodExact(clazz, methodName, parameterTypesAndCallback);
    
    /**
    @function 根據方法物件通知Native函式hookMethodNative執行Hook操作
    @para method 要Hook的方法物件
    @callback 回撥函式組
    @description 定義在XposedBridge中
    */
    return XposedBridge.hookMethod(m, callback);
}

2.5.2 findMethodExact

/**
@function findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes)的過載函式
*/
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);
        
        // ignore trailing callback
        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 there are no arguments for the method
    if (parameterClasses == null)
        parameterClasses = new Class<?>[0];
    
    return findMethodExact(clazz, methodName, parameterClasses);
}
/**
@function 在一個類中查詢到指定方法並將該方法設定為Accessible
@para clazz 類物件
@para methodName 方法名稱
@para parameterTypesAndCallback 方法引數
@description 該類定義在了XposedHelper中
@exception 如果沒有在指定的類中查詢到指定方法,則丟擲NoSuchMethodError這個異常
*/
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();
    
    /**
    @function 判斷當前Method是否已經被Hook
    */
    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);
        /**
         @annotation 1
       */
 
        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);
    }
    
    /**
     * Called before the invocation of the method.
     * <p>Can use {@link MethodHookParam#setResult(Object)} and {@link MethodHookParam#setThrowable(Throwable)}
     * to prevent the original method from being called.
     */
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
    
    /**
     * Called after the invocation of the method.
     * <p>Can use {@link MethodHookParam#setResult(Object)} and {@link MethodHookParam#setThrowable(Throwable)}
     * to modify the return value of the original method.
     */
    protected void afterHookedMethod(MethodHookParam param) throws Throwable  {}
    
    
    public static class MethodHookParam extends XCallback.Param {
        /** Description of the hooked method */
        public Member method;
        /** The <code>this</code> reference for an instance method, or null for static methods */
        public Object thisObject;
        /** Arguments to the method call */
        public Object[] args;
        
        private Object result = null;
        private Throwable throwable = null;
        /* package */ boolean returnEarly = false;
        
        /** Returns the result of the method call */
        public Object getResult() {
            return result;
        }
        
        /**
         * Modify the result of the method call. In a "before-method-call"
         * hook, prevents the call to the original method.
         * You still need to "return" from the hook handler if required.
         */
        public void setResult(Object result) {
            this.result = result;
            this.throwable = null;
            this.returnEarly = true;
        }
        
        /** Returns the <code>Throwable</code> thrown by the method, or null */
        public Throwable getThrowable() {
            return throwable;
        }
        
        /** Returns true if an exception was thrown by the method */
        public boolean hasThrowable() {
            return throwable != null;
        }
        
        /**
         * Modify the exception thrown of the method call. In a "before-method-call"
         * hook, prevents the call to the original method.
         * You still need to "return" from the hook handler if required.
         */
        public void setThrowable(Throwable throwable) {
            this.throwable = throwable;
            this.result = null;
            this.returnEarly = true;
        }
        
        /** 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;
        }
    }
    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);
        }
    }
}

--------------------- 
作者:王下邀月熊-WxChevalier 
來源:CSDN 
原文:https://blog.csdn.net/wxyyxc1992/article/details/17320911 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

相關文章