1. 程式人生 > >Android Hook框架Xposed詳解

Android Hook框架Xposed詳解

1.2 Mechanism :原理

1.2.1 Zygote

在 Android 系統中,應用程式程序都是由 Zygote 程序孵化出來的,而 Zygote 程序是由 Init 程序啟動的。 Zygote 程序在啟動時會建立一個 Dalvik 虛擬機器例項,每當它孵化一個新的應用程式程序時,都會將這個 Dalvik 虛擬機器例項複製到新的應用程式程序裡面去,從而使得每一個應用程式程序都有一個獨立的 Dalvik 虛擬機器例項。

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  原始碼分析

2.1 Cpp 模組

  • app_main.cpp :類似 AOSP 中的 frameworks/base/cmds/app_process/app_main.cpp,即/system/bin/app_process 這個 zygote 真實身份的應用程式的原始碼。關於 zygote 程序的分析可以參照 Android:AOSP&Core 中的 Zygote 程序詳解。
  • xposed.cpp :提供給 app_main.cpp 的呼叫函式以及 XposedBridge 的 JNI 方法的實現。主要完成初始化工作以及 Framework 層的 Method 的 Hook 操作。
  • xposed.h , xposed_offsets.h :標頭檔案

  Xposed 框架中的 app_main.cpp 相對於 AOSP 的 app_main.cpp 中修改之處主要為區分了呼叫 runtime.start() 函式的邏輯。 Xposed 框架中的 app_main.cpp 在此處會根據情況選擇是載入 XposedBridge 類還是 ZygoteInit 或者 RuntimeInit 類。而實際的載入 XposedBridge 以及註冊 JNI 方法的操作發生在第四步: xposedOnVmCreated中。

1.包含 cutils/properties.h ,主要用於獲取、設定環境變數, xposed.cpp 中需要將 XposedBridge 設定到 ClassPath 中。

2.包含了 dlfcn.h ,用於對動態連結庫的操作。

3.包含了 xposed.h ,需要呼叫 xposed.cpp 中的函式,譬如在虛擬機器建立時註冊JNI 函式。

4.增加了 initTypePointers 函式,對於 Android SDK 大於等於 18 的會獲取到 atrace_set_tracing_enabled 函式指標,在 Zygote 啟動時呼叫。

5.AppRuntime 類中的 onVmCreated 函式中增加 xposedOnVmCreated 函式呼叫。

6.原始碼中的 Log* 全部重新命名為 ALog*, 所以 Logv 替換為 Alogv ,但是功能不變。

7.Main 函式開始處增加了大量的程式碼,但是對於 SDK 版本小於 16 的可以不用考慮。

2.1.1 Main 函式: zygote 入口

int main(int argc, char* const argv[])
{
  ...
  initTypePointers();
    /*該函式對於SDK>=18的會獲取到atrace_set_tracing_enabled的函式指標,獲取到的指標會在Zygote初始化過程中呼叫,函式定義見程式碼段下方*/
  ...
  xposedInfo();
    /*xposedInfo函式定義在xposed.cpp中,該函式主要獲取一些屬性值譬如SDK版本,裝置廠商,裝置型號等資訊並且列印到Log檔案中*/
    xposedEnforceDalvik();
    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) {
        // 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;
    }
}
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);
        }
    }
}

上述程式碼中的keepLoadingXposed 變數主要用於判斷是否需要繼續載入Xposed 框架,其中 isXposedDisabled 、 xposedShouldIgnoreCommand 以及 addXposedToClasspath 都定義在 xposed.cpp 中。

2.1.2 keepLoadingXposed :判斷是否需要載入 XposedBridge  

bool isXposedDisabled() {
    // is the blocker file present?
    if (access(XPOSED_LOAD_BLOCKER, F_OK) == 0) {
        ALOGE("found %s, not loading Xposed\n", XPOSED_LOAD_BLOCKER);
        return true;
    }
    return false;
}

該函式通過讀取 /data/data/de.robv.android.xposed.installer/conf/disabled 檔案(Xposed 框架通過 XposedInstaller 管理,需要安裝此 APK 檔案),來判斷 Xposed 框架是否被禁用,如果該檔案存在,則表示禁用 Xposed 。

2.xposedShouldIgnoreCommand

為了避免Superuser類似工具濫用Xposed的log檔案,此函式會判斷是否是SuperUser等工具的啟動請求。

// ignore the broadcasts by various Superuser implementations to avoid spamming the Xposed log
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;
  }

3.addXposedToClasspath

若有新版本的XposedBridge,重新命名為XposedBridge.jar並返回false;判斷XposedBridge.jar檔案是否存在,若不存在,返回false,否則將XposedBridge.jar新增到CLASSPATH環境變數中,返回true。

2.1.3 runtime.start() :初始化 Dalvik 虛擬機器

一般情況下keepLoadingXposed值為true,以啟動Zygote為例(zygote==true),分析接下來的程式碼。

runtime . start ( keepLoadingXposed  ?  XPOSED_CLASS_DOTS  :   "com.android.internal.os.RuntimeInit" ,

                application  ?   "application"   :   "tool" );

這一行程式碼是根據keepLoadingXposed 的值來判斷是載入Xposed 框架還是正常的ZygoteInit 類。 keepLoadingXposed 值為 true, 則會載入 XPOSED_CLASS_DOTS 類, XPOSED_CLASS_DOTS 值為 de.robv.android.xposed.XposedBridge ,即 XposedBridge 類。

runtime 是 AppRuntime 的例項, AppRuntime 繼承自 AndroidRuntime 。

......
static AndroidRuntime* gCurRuntime = NULL;
......
AndroidRuntime::AndroidRuntime()
{
  ......
  assert(gCurRuntime == NULL);        // one per process
  gCurRuntime = this;
}

AndroidRuntime::start(const char* className, const char* options) 函式完成 Dalvik 虛擬機器的初始化和啟動以及執行引數 className 指定的類中的 main 方法。當啟動完虛擬機器後,會呼叫 onVmCreated(JNIEnv* env) 函式。該函式在 AppRuntime 類中被覆蓋。因此直接看 AppRuntime::onVmCreated(JNIEnv* env) 。

virtual void onVmCreated(JNIEnv* env)
    {
        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));
    }

Xposed 相對於 AOSP 就增加了如下程式碼:

keepLoadingXposed  =  xposedOnVmCreated ( env ,  mClassName );

呼叫了 xposed.cpp 中的 xposedOnVmCreated(JNIEnv* env, const char* className) 函式。

2.1.4 xposedOnVmCreated:載入Xposedbridge

該函式的主要作用如下:

1. 根據 JIT 是否存在對部分結構體中的成員偏移進行初始化。

xposedInitMemberOffsets ();

即時編譯( Just-in-time Compilation , JIT ),又稱動態轉譯( Dynamic Translation ),是一種通過在執行時將 位元組碼 翻譯為機器碼,從而改善位元組碼 編譯語言 效能的技術。

2. 禁用部分訪問檢查

// disable some access checks
    patchReturnTrue((uintptr_t) &dvmCheckClassAccess);
    patchReturnTrue((uintptr_t) &dvmCheckFieldAccess);
    patchReturnTrue((uintptr_t) &dvmInSamePackage);
    if (access(XPOSED_DIR "conf/do_not_hook_dvmCheckMethodAccess", F_OK) != 0)
        patchReturnTrue((uintptr_t) &dvmCheckMethodAccess);

3. 針對 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 ;

         }

     }

4. 獲取XposeBridge類並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 ;

     }

5. 註冊JNI函式,xposed.cpp中定義了供XposedBridge類使用的JNI方法,此處進行註冊,這樣當XposeBridge中的main函式執行時,就可以呼叫xposed.cpp中定義的JNI方法

  if   ( register_de_robv_android_xposed_XposedBridge ( env )   !=  JNI_OK )   {

        ALOGE ( "Could not register natives for '%s'\n" ,  XPOSED_CLASS );

         return   false ;

     }

Xposed 中 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));
}

註冊的 JNI 方法見 xposedMethods 陣列。

至於這些 JNI 方法的用處,在後續 XposedBridge 的 main 函式呼叫中會繼續分析。

如果對 Zygote 啟動過程熟悉的話,對後續 XposedBridge 的 main 函式是如何被呼叫的應該會很清楚。 AndroidRuntime.cpp 的 start(const char* className, const char* options) 函式完成環境變數的設定, Dalvik 虛擬機器的初始化和啟動,同時 Xposed在 onVmCreated(JNIEnv* env) 中完成了自身 JNI 方法的註冊。此後 start() 函式會註冊Android 系統的 JNI 方法,呼叫傳入的 className 指定類的 main 方法,進入 Java 世界。

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);
      ......
    }
  }
  ......
}

由於此時引數 className 為 de.robv.android.xposed.XposedBridge ,因此會呼叫XposedBridge 類的 main 方法, XposedBridge 原始碼 https://github.com/rovo89/XposedBridge 。

2.2 Java 模組

2.2.1 Main 函式:獲取所啟動的類名

進入 XposedBridge 的 main 函式,首先獲取所啟動的類名。

String startClassName  =  getStartClassName ();

getStartClassName() 是一個 JNI 方法,定義在 xposed.cpp 中。

static  jobject de_robv_android_xposed_XposedBridge_getStartClassName ( JNIEnv *  env ,  jclass clazz )   {

     return  env -> NewStringUTF ( startClassName );

}

startClassName 變數在 xposedOnVmCreated 函式中被賦予需啟動的類的名稱。

bool xposedOnVmCreated ( JNIEnv *  env ,   const   char *  className )   {

    startClassName  =  className ;

...

}

但是需要注意的是,若是啟動 Zygote ,則此時 startClassName 值為 null 。如下程式碼( app_main.cpp )所示,當 zygote 為 true ,即啟動 Zygote 時,並沒有給 AppRuntime 例項 runtime 的 mClassName 成員賦值。

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" );

}

startClassName 的賦值過程為: AppRuntime 中 mClassName 成員初始值為 NULL;在 app_main.cpp 中的 main 函式中根據 arg 引數解析獲得 className ,若是啟動Zygote ,則 className 值為 NULL ,否則若 className 有值,則賦值給 AppRuntime 例項 runtime 的 mClassName 成員變數;呼叫 runtime.start(…) ,進一步呼叫 onVmCreated(…) ,在 onVmCreated 函式中呼叫 xposedOnVmCreated(…) ,並傳入 mClassName 值, xposedOnVmCreated 函式將 mClassName 賦值給全域性變數 startClassName;

jobject de_robv_android_xposed_XposedBridge_getStartClassName(…) 將此全域性變數 startClassName 轉換為 Java 字串返回。 

2.2.2 初始化 log 檔案

XposedBridge 會在 XposedInstaller 的目錄下生成 log 檔案,該 log 檔案的路徑為: /data/data/de.robv.android.xposed.installer/log/debug.log 。 log 檔案的初始化程式碼如下:

  // initialize the Xposed framework and modules

try   {

// initialize log file

try   {

File logFile  =   new  File ( BASE_DIR  +   "log/debug.log" );

if   ( startClassName  ==   null   &&  logFile . length ()   >  MAX_LOGFILE_SIZE )

logFile . renameTo ( new  File ( BASE_DIR  +   "log/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 ());

determineXposedVersion ();

log ( "-----------------\n"   +  date  +   " UTC\n"

+   "Loading Xposed v"   +  XPOSED_BRIDGE_VERSION

+   " (for "   +   ( startClassName  ==   null   ?   "Zygote"   :  startClassName )   +   ")..." );

if   ( initNative ())   {

if   ( startClassName  ==   null )   {

// Initializations for Zygote

initXbridgeZygote ();

}

loadModules ( startClassName );

}   else   {

log ( "Errors during native Xposed initialization" );

}

}   catch   ( Throwable t )   {

log ( "Errors during Xposed initialization" );

log ( t );

disableHooks  =   true ;

}

若 startClassName==null 並且 log 檔案的長度超過閾值,會將 debug.log 重新命名為debug.log.old 。呼叫 determineXposedVersion() 獲取 XposedBridge 的版本資訊。版本資訊儲存在 XposedBridge 專案的 assets/VERSION 中。由於 XposedBridge 在 Android 裝置上以 Jar 包的形式存在於 XposedInstaller 目錄下,因此 determineXposedVersion 以讀取 zip 檔案的形式獲取 VERSION 中的資料,並解析出其中的版本號,賦值給靜態成員變數 XPOSED_BRIDGE_VERSION 。

ZipInputStream is  =   new  ZipInputStream ( new  FileInputStream ( BASE_DIR  +  "bin/XposedBridge.jar" ));

ZipEntry entry ;

try   {

while   (( entry  =  is . getNextEntry ())   !=   null )   {

if   (! entry . getName (). equals ( "assets/VERSION" ))

continue ;

BufferedReader br  =   new  BufferedReader ( new  InputStreamReader ( is ));

String version  =  br . readLine ();

br . close ();

XPOSED_BRIDGE_VERSION  =  extractIntPart ( version );

if   ( XPOSED_BRIDGE_VERSION  ==   0 )

throw   new  RuntimeException ( "could not parse XposedBridge version from \""  +  version  +   "\"" );

return ;

}

throw   new  RuntimeException ( "could not find assets/VERSION in "   +  BASE_DIR  +   "bin/XposedBridge.jar" );

}   finally   {

try   {

is . close ();

}   catch   ( Exception e )   {   }

}

}

2.2.3 獲取對 Java 層函式的引用

Xposed 在進入 XposedBridge.main 函式之前,註冊了 4 個 JNI 方法,其中一個是 initNative() ,這個函式負責獲取 XposedBridge 中 Java 函式的引用。在完成 log 檔案的初始化後, XposedBridge.main 呼叫 initNative 函式。

if   ( initNative ())   {

if   ( startClassName  ==   null )   {

// Initializations for Zygote

initXbridgeZygote ();

}

loadModules ( startClassName );

}   else   {

log ( "Errors during native Xposed initialization" );

}

現在回到 xposed.cpp 中,看下 initNative 這個 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 ;

}

該函式主要完成對 XposedBridge 類中函式的引用,這樣可以實現在 Native 層對 Java 層函式的呼叫。 譬如獲取 XposedBridge 類中的 handlHookedMethod 函式的 method id ,同時賦值給全域性變數 xposedHandleHookedMethod 。另外, initNative 函式還會獲取 android.content.res.XResources 類中的方法,完成對資原始檔的處理;呼叫 register_android_content_res_XResources 註冊 rewriteXmlReferencesNative 這個JNI 方法。

2.2.4 Hook : Java 層獲取 hooked method 與 hook func

在完成對 Java 層函式的引用賦值後,如果是啟動 Zygote ,會接著執行對某些函式的 hook 處理。個人認為這部分是 Xposed 框架實現對函式 hook 的核心。程式碼如下:

if   ( startClassName  ==   null )   {

// Initializations for Zygote

initXbridgeZygote ();

}

initXbridgeZygote 完成對一些函式的 hook 操作,主要是呼叫 XposedHelpers 類中的 findAndHookMethod 完成。

private   static   void  initXbridgeZygote ()   throws  Exception  {

final  HashSet < String >  loadedPackagesInProcess  =   new  HashSet < String >(1 );

// normal process initialization (for new Activity, Service, BroadcastReceiver etc.) 

findAndHookMethod ( ActivityThread . class ,   "handleBindApplication" ,   "android.app.ActivityThread.AppBindData" ,   new  XC_MethodHook ()   {

protected   void  beforeHookedMethod ( MethodHookParam param )   throws  Throwable  {

...

}

}

}

以 hook ActivityThread 類的 handleBindApplication 函式為例來分析整個 hook 的過程。 ActivityThread 類定義在 frameworks/base/core/java/android/app/ActivityThread.java 檔案中。 ActivityThread 的 main 函式是應用程式啟動的入口。 findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)(另一個過載的 findAndHookMethod 最終也會呼叫前述 findAndHookMethod 函式 ) 程式碼如下:

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 );

}

findAndHookMethod函式引數的意義分別為:

1. clazz: 需要hook的函式所在的類;

2. methodName: 需要hook的函式名;

3. parameterTypesAndCallback: 不定引數,包括methodName所指函式的引數,以及回撥函式,主要是在執行methodName函式之前和之後呼叫的回撥函式。

XC_MethodHook callback  =   ( XC_MethodHook )  parameterTypesAndCallback [parameterTypesAndCallback . length - 1 ];

這段程式碼是用來parameterTypesAndCallback 值為 [“android.app.ActivityThread.AppBindData”, XC_MethodHook 例項 ] ,因此 callback 值為其中的 XC_MethodHook例項。 XC_MethodHook 類關係圖如下:

XC_MethodHook 類中的 beforeHookedMethod 函式會在被 hook 的函式呼叫之前呼叫,而 afterHookedMethod 函式會在被 hook 的函式呼叫之後呼叫。這兩個函式的方法體為空,需要在例項化 XC_MethodHook 時根據情況填充方法體。 XC_MethodHook 的內部類 MethodHookParam 儲存了相應的資訊,如呼叫方法的引數, this 物件,函式的返回值等。

Method m  =  findMethodExact ( clazz ,  methodName ,  parameterTypesAndCallback );

本行程式碼根據需要 hook 的函式的類資訊、函式名以及引數資訊獲取對應的 Method例項,同時將其設定為可訪問。第 5 行呼叫的 findMethodExact 函式的簽名為 Method  findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes),該函式最終會呼叫 Method findMethodExact(Class<?> clazz, String methodName, Class<?>.parameterTypes) 。在 findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) 函式中,首先將類名、方法名以及引數資訊構建成一個鍵值,以該鍵值從 methodCache 中查詢是否存在 Method 例項, methodCache相當於快取了對應的 Method 例項。如果沒有找到,會呼叫 Class 類的 getDeclaredMethod(String name,Class<?>... parameterTypes) 方法獲取 Method 例項,同時將該 Method 設定為可訪問,加入到 methodCache 中。

接下來呼叫 XposedBridge 類的靜態方法 hookMethod 實現對函式的 hook 和回撥函式的註冊,程式碼如下:

相關推薦

Android Hook框架Xposed

1.2 Mechanism :原理 1.2.1 Zygote 在 Android 系統中,應用程式程序都是由 Zygote 程序孵化出來的,而 Zygote 程序是由 Init 程序啟動的。 Zygote 程序在啟動時會建立一個 Dalvik 虛擬機器例項,每當它孵化一個新的應用程式程序時,都會將

Android Hook框架Xposed入門

轉:http://chendd.com/blog/2016/05/15/learn_xposed/ 一.基礎知識        Xposed是Android平臺上較為出名的一個開源框架。在這個框架下,我們可以載入很多外掛App,這些外掛App可以直

Android Volley框架使用

Volley瞭解 Volley的中文翻譯為“齊射、併發”,是在2013年的Google大會上釋出的一款Android平臺網路通訊庫,具有網路請求的處理、小圖片的非同步載入和快取等功能,能夠幫助 Android APP 更方便地執行網路操作,而且更快速高效。

android多媒體框架學習 最新版本

八、流媒體     從這篇開始我們將進入流媒體的環節,流媒體在android中有nuplayer來實現的,在開始講解android流媒體前,我們先來講講流媒體傳輸協議,瞭解了基本協議,我們在看程式碼的過程中,就會有事半功倍的效果。我們將主要講解RTSP,HTTP,HTTPS, SDP四種

Android元件化框架專案

簡介 什麼是元件化? 專案發展到一定階段時,隨著需求的增加以及頻繁地變更,專案會越來越大,程式碼變得越來越臃腫,耦合會越來越多,開發效率也會降低,這個時候我們就需要對舊專案進行重構即模組的拆分,官方的說法就是元件化。 元件化帶來的好處 那麼,採用元件化能帶來什麼好處呢?主要有以下兩點: 1、

深入理解AndroidXposed

一、背景Xposed,大名鼎鼎得Xposed,是Android平臺上最負盛名的一個框架。在這個框架下,我們可以載入很多外掛App,這些外掛App可以直接或間接操縱系統層面的東西,比如操縱一些本來只對系統廠商才open的功能(實際上是因為Android系統很多API是不公開的,

Android BLE基礎框架使用

前言 研究了一段時間的藍芽使用,發現網上相關的資料比較貧乏,不像其他Android相關資料那麼齊全,基本上大部分資料都是在藍芽聯盟SIG提供的官網https://www.bluetooth.com/zh-cn/specifications上查詢得到,也沒有一個

android hook技術-Xposed框架 幫你輕鬆應對支付寶2016晒賬單

一、支付寶2016年賬單。 最近幾天微信朋友圈充斥著各式的支付寶2016賬單,對於程式設計師屌絲來說打開發現年度消費9W+,和他們動輒十幾萬的消費沒得比,細看更有80%的消費還都是還信用卡,頓時萬念俱灰啊!!!  有木有!!! 突然看到有人發出了負數的賬單,也是醉了。。。看

Android Collections集合框架使用

Collection在Android中的用法最近在做Android專案的時候,遇到了一些集合排序的問題,之前也使用過Collections的一些方法,例如Collections.sort(),Collections.swap()...,於是就統一整理了一下Collection

Android自定義view

this boolean mar 處理 都是 並且 jdk text 命名 從繼承開始 懂點面向對象語言知識的都知道:封裝,繼承和多態,這是面向對象的三個基本特征,所以在自定義View的時候,最簡單的方法就是繼承現有的View 通過上面這段代碼,我定義了一個Ske

iOS自動布局框架-Masonry

asc github上 區分 block line 優先級 關鍵字 二次 con 目前iOS開發中大多數頁面都已經開始使用Interface Builder的方式進行UI開發了,但是在一些變化比較復雜的頁面,還是需要通過代碼來進行UI開發的。而且有很多比較老的項目,本身就還

【轉】Quartz.net開源作業調度框架使用

詳解 des 關於 utc ogg knowledge imp common dem 轉自:http://www.cnblogs.com/knowledgesea/p/4930469.html 前言 quartz.net作業調度框架是偉大組織OpenSymphony開發的q

【轉】Android Camera 相機開發

exc troy start 當前 container rac google getconf 對比度 在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架構上做了巨大的變動, 但是基於眾所周知的原因

Android Parcel對象

行數 ado state on() 直接 之一 一起 sharp mar 關於Parcel的使用 在分析Parcel之前,首先按照分析流程,介紹下關於Parcel的相關常規使用。 首先是關於Parcel的獲取: Parcel parcle = Parcel.Obtain()

Spring框架Aop

urn 項目 aop mil execution 有一個 dal 例如 cati 一.前言 在以前的項目中,很少去關註spring aop的具體實現與理論,只是簡單了解了一下什麽是aop具體怎麽用,看到了一篇博文寫得還不錯,就轉載來學習一下,博文地址:http://

Android EventBus3.x 使用(一)

led AC target 分解 感覺 div activit .org android事 ?(^∇^*) 五一假期在家無事,新項目中用的是RxJava2+EventBus感覺還不錯,趁這閑暇總結下EventBus 一、概要簡述   EventBus

python3開發進階-Django框架

本質 負責 log 生命周期 target 了解 定制 基本 del 一、MVC框架和MTV框架 MVC,全名是Model View Controller,是軟件工程中的一種軟件架構模式,把軟件系統分為三個基本部分: 模型(Model)、視圖(View)和控制器(Con

JAVA常用集合框架用法基礎篇一之Colletion介面

首先,在學習集合之前我們能夠使用的可以儲存多個元素的容器就是陣列。 下面舉幾個例子主要是引出集合類的: 1、8,4,5,6,7,55,7,8  像這樣的型別相同的可以使用陣列來儲存,本例可以用int[] arr來儲存。 2、”zhnagsan”,true,68 像這樣的可以使

JAVA常用集合框架用法基礎篇三之Colletion子介面Set

這一篇我們來介紹Collection介面的另一個子介面,Set介面。Set是個介面,元素不可以重複,是無序的。Set介面中的方法和Collection的一致。 A、Set的子類: 1、HashSet:此類實現的Set介面,由雜湊表(實際上是一個HashMap)例項支援,它不保證Set的迭代順

JAVA常用集合框架用法基礎篇二之Colletion子介面List

接著上一篇,接著講講集合的知識。上一篇講了Collection介面。它可以說是集合的祖先了,我們這一篇就說說它的子孫們。 一、Collection的子介面 List:有序(存入和取出的順序一致),元素都有索引(即角標),元素可以重複。 Set:元素不能重複,無序的。 首先講講L