1 Introductionphp
Xposed 是 GitHUB 上 rovo89 大大設計的一個針對 Android 平臺的動態劫持項目,經過替換 /system/bin/app_process 程序控制 zygote 進程,使得 app_process 在啓動過程當中會加載 XposedBridge.jar 這個 jar 包,從而完成對系統應用的劫持。html
Xposed 框架的基本運行環境以下:java
由於 Xposed 工做原理是在 /system/bin 目錄下替換文件,在 install 的時候須要 root 權限,可是運行時不須要 root 權限。android |
|
須要在 Android 4.0 以上版本的機器中git |
https://github.com/rovo89github
2. GitHub 上的 Xposed 資源梳理一下,能夠這麼分類:數組
在 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 源代碼分析。app
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
該部分的源代碼地址是: https://github.com/rovo89/Xposed ,其文件分類以下:
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 的能夠不用考慮。
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 中。
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。
通常狀況下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) 函數。
該函數的主要做用以下:
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 。
進入 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 字符串返回。
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 ) { }
}
}
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 方法。
在完成對 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 和回調函數的註冊,代碼以下:
/**
* Hook any method with the specified callback
*
* @param hookMethod The method to be hooked
* @param callback
*/
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 ;
CopyOnWriteSortedSet < XC_MethodHook > callbacks ;
synchronized ( hookedMethodCallbacks ) {
callbacks = hookedMethodCallbacks . get ( hookMethod );
if ( callbacks == null ) {
callbacks = new CopyOnWriteSortedSet < XC_MethodHook >();
hookedMethodCallbacks . put ( hookMethod , callbacks );
newMethod = true ;
}
}
callbacks . add ( callback );
if ( newMethod ) {
Class <?> declaringClass = hookMethod . getDeclaringClass ();
int slot = ( int ) getIntField ( hookMethod , "slot" );
Class <?>[] parameterTypes ;
Class <?> returnType ;
if ( hookMethod instanceof Method ) {
parameterTypes = (( Method ) hookMethod ). getParameterTypes ();
returnType = (( Method ) hookMethod ). getReturnType ();
} else {
parameterTypes = (( Constructor <?>) hookMethod ). getParameterTypes ();
returnType = null ;
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo ( callbacks , parameterTypes , returnType );
hookMethodNative ( hookMethod , declaringClass , slot , additionalInfo );
}
return callback . new Unhook ( hookMethod );
}
HookedMethodCallbacks 是一個 hashMap 的實例,存儲每一個須要 hook 的 method的回調函數。首先查看 hookedMethodCallbacks 中是否有 hookMethod 對應的 callbacks 的集合,若是沒有,則建立一個 TreeSet ,將該 callbacks 加入到 hookedMethodCallbacks 中,同時將 newMethod 標誌設爲 true 。 接下來將傳入的 callback 添加到callbacks 集合中。若是是個新的須要 hook 的 Method ,則獲取對應的 Class 對象,獲得 hookMethod 的 slot 值,而後調用 hookMethodNative 這個 JNI 方法。 最後實例化 Unhook 類,便於在後期須要對 hook 的方法進行 unhook 的操做。
進入 hookMethodNative 這個 JNI 方法( xposed.cpp 中),看下這個方法具體作了哪些操做。
static void de_robv_android_xposed_XposedBridge_hookMethodNative ( JNIEnv* env , jclass clazz ,
jobject reflectedMethodIndirect ,
jobject declaredClassIndirect , jint slot , jobject additionalInfoIndirect ) {
if ( declaredClassIndirect == NULL || reflectedMethodIndirect == NULL ) {
dvmThrowIllegalArgumentException ( "method and 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 ( xposedIsHooked ( method )) {
// already hooked
return ;
}
// Save a copy of the original method and other hook info
XposedHookInfo * hookInfo = ( XposedHookInfo *) calloc ( 1 , sizeof ( XposedHookInfo ));
memcpy ( hookInfo , method , sizeof ( hookInfo -> originalMethodStruct ));
hookInfo -> reflectedMethod = dvmDecodeIndirectRef ( dvmThreadSelf (), env-> NewGlobalRef ( reflectedMethodIndirect ));
hookInfo -> additionalInfo = dvmDecodeIndirectRef ( dvmThreadSelf (), env -> NewGlobalRef ( additionalInfoIndirect ));
// Replace method with our own code
SET_METHOD_FLAG ( method , ACC_NATIVE );
method -> nativeFunc = & xposedCallHandler ;
method -> insns = ( const u2 *) hookInfo ;
method -> registersSize = method -> insSize ;
method -> outsSize = 0 ;
if ( PTR_gDvmJit != NULL ) {
// reset JIT cache
MEMBER_VAL ( PTR_gDvmJit , DvmJitGlobals , codeCacheFull ) = true ;
}
}
代碼中首先得到 Dalvik 中對應的 ClassObject 以及 Method ,接下來判斷須要 hook的 Method 是否已經被 hook 處理過,若處理過,則直接返回。全部被 hook 的 Method 都會保存在 xposedOriginalMethods 這個 list 中。對於新的須要 hook 的函數,首先將其添加到 xposedOriginalMethods 的列表中。
SET_METHOD_FLAG ( method , ACC_NATIVE );
method -> nativeFunc = & xposedCallHandler ;
method -> insns = ( const u2 *) hookInfo ;
method -> registersSize = method -> insSize ;
method -> outsSize = 0 ;
如上幾行代碼是 Xposed 框架實現 hook 的關鍵。 Dalvik 中 Method 結構體定義在AOSP 中 /dalvik/vm/oo/Object.h 中。 首先將 ACC_NATIVE 添加到 Method 的 accessFlags 標誌位中 , 接下來將 Method 的 nativeFunc 設置爲 xposedCallHandler 函數地址, 而後將 Method 的 registerSize 設置爲 insSize , 最後將 outsSize 設置爲 0 。可參考 Dalvik 虛擬機的運行過程分析一文。 Dalvik 虛擬機在解釋執行函數時,會調用 dvmIsNativeMethod(const Method* method)( 定義於 /dalvik/vm/oo/Object.h) 判斷 Method 是否爲 Native 方法,如果,則直接調用 Method->nativeFunc 指向的函數。那 dvmIsNativeMethod 又是如何判斷一個 Method 是否爲 Native 方法呢?代碼以下:
INLINE bool dvmIsNativeMethod ( const Method * method )
{
return ( method -> accessFlags & ACC_NATIVE ) != 0 ;
}
正是經過比較 Method 的 accessFlags 與 ACC_NATIVE 來判斷的,這也就是爲何14 行調用 SET_METHOD_FLAG 將 ACC_NATIVE 添加到 Method 的 accessFlags 中。 SET_METHOD_FLAG 代碼以下:
#define SET_METHOD_FLAG(method,flag) \
do {( method ) _ > accessFlags |= ( flag );} while ( 0 )
進入 xposed.cpp 中的 xposedCallHandler 函數,該函數會做爲被 hook 的函數的 Native 方法調用,代碼以下:
static void xposedCallHandler ( const u4 * args , JValue * pResult , const Method * method , :: Thread * self ) {
if (! xposedIsHooked ( method )) {
dvmThrowNoSuchMethodError ( "could not find Xposed original method - how did you even get here?" );
return ;
}
XposedHookInfo * hookInfo = ( XposedHookInfo *) method -> insns ;
Method * original = ( Method *) hookInfo ;
Object * originalReflected = hookInfo -> reflectedMethod ;
Object * additionalInfo = hookInfo -> additionalInfo ;
// 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 *) args [ 0 ];
srcIndex ++;
}
ArrayObject * argsArray = dvmAllocArrayByClass ( objectArrayClass , strlen (method -> shorty ) - 1 , ALLOC_DEFAULT );
if ( argsArray == NULL ) {
return ;
}
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 , self );
break ;
case 'D' :
case 'J' :
value . j = dvmGetArgLong ( args , srcIndex );
srcIndex += 2 ;
obj = ( Object *) dvmBoxPrimitive ( value , dvmFindPrimitiveClass ( descChar ));
dvmReleaseTrackedAlloc ( obj , self );
break ;
case '[' :
case 'L' :
obj = ( Object *) args [ srcIndex ++];
break ;
default :
ALOGE ( "Unknown method signature description character: %c\n" , descChar );
obj = NULL ;
srcIndex ++;
}
xposedSetObjectArrayElement ( argsArray , dstIndex ++, obj );
}
// call the Java handler function
JValue result ;
dvmCallMethod ( self , xposedHandleHookedMethod , NULL , & result ,
originalReflected , ( int ) original , additionalInfo , thisObject , argsArray );
dvmReleaseTrackedAlloc ( argsArray , self );
// exceptions are thrown to the caller
if ( dvmCheckException ( self )) {
return ;
}
// return result with proper type
ClassObject * returnType = dvmGetBoxedReturnType ( method );
if ( returnType -> primitiveType == PRIM_VOID ) {
// ignored
} else if ( result . l == NULL ) {
if ( dvmIsPrimitiveClass ( returnType )) {
dvmThrowNullPointerException ( "null result when primitive expected" );
}
pResult -> l = NULL ;
} else {
if (! dvmUnboxPrimitive ( result . l , returnType , pResult )) {
dvmThrowClassCastException ( result . l -> clazz , returnType );
}
}
}
第 4-7 得到被 hook 函數的 java.lang.reflect.Method 對象實例。第 9-52 行完成 Java 本地類型到 Java 類型的轉換,也就是將被 hook 函數的參數轉換爲 Java 類型,爲後續在 C++ 層調用 Java 層代碼作準備。 55 行調用 XposedBridge 類中 handleHookedMethod 函數,參數分別爲被 hook 的原始函數 Method 實例, this 對象以及參數信息。這也就是爲何有 9-52 行的參數轉換操做。 58-72 行完成對返回值的進一步處理,主要是將 Java 層的返回值類型轉換爲 C++ 層的類型。 74 行將線程狀態設置回調用 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 的調用,這兩類回調函數之間,會調用原始函數,完成正常的功能。
繼續回到 XposedBridge 的 main 函數中,在處理完對 hook 函數的處理後會調用 loadModules(String startClassName) 加載基於 Xposed 框架的模塊。
if ( initNative ()) {
if ( startClassName == null ) {
// Initializations for Zygote
initXbridgeZygote ();
}
loadModules ( startClassName );
} else {
log ( "Errors during native Xposed initialization" );
}
loadModules 讀取 /data/data/de.robv.android.xposed.installer/conf/modules.list文件,得到 Android 設備上安裝的模塊的 APK 具體路徑,若設備上安裝了 XPrivacy,則 modules.list 文件內容爲: /data/app/biz.bokhorst.xprivacy-1.apk 。 loadModules 對每一個模塊調用 loadMoudle , loadModule 會根據提供的 APK 路徑,實例化類,並根據實例的類型,進行一些初始化工做,主要的類型包括 IXposedHookZygoteInit, IXposedHookLoadPackage , IXposedHookInitPackageResources ,和 IXposedHookCmdInit 。以 XPrivacy 模塊爲例, XPrivacy 類實現了 IXposedHookLoadPackage 和 IXposedHookZygoteInit 接口。若是是 IXposedHookZygoteInit , loadModule會調用 initZygote(StartupParam startupParam) 函數。所以在分析基於 Xposed 框架的模塊時,須要注意這點。
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 ();
}
在 Xposed 的 app_main.cpp 中, runtime.start 調用了 XposedBridge 的 main 函數,對於 Zygote 啓動過程來講,還必須完成對 ZygoteInit.main 函數的調用,完成類、資源的預加載以及對應用程序運行請求的處理。因此在 XposedBridge 完成自身的初始化以後,還須要完成對 ZygoteInit.main 的調用,以下代碼所示。
// call the original startup code
if ( startClassName == null )
ZygoteInit . main ( args );
else
RuntimeInit . main ( args );
一個 XposedModule 本質上是設定了部分特殊元數據標誌位的普通應用程序,須要在 AndroidManifest.xml 文件中添加以下設置:
AndroidManifest.xml => Application => Application Nodes (at the bottom) => Add => Meta Data
添加節點: name = xposedmodule , value = true 。 name = xposedminiversion, value = API level 。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android= "http://schemas.android.com/apk/res/android"
package= "de.robv.android.xposed.mods.tutorial"
android:versionCode= "1"
android:versionName= "1.0" >
<uses-sdk android:minSdkVersion= "15" />
<application
android:icon= "@drawable/ic_launcher"
android:label= "@string/app_name" >
<meta-data android:value= "true" android:name= "xposedmodule" />
<meta-data android:value= "2.0*" android:name= "xposedminversion" />
<meta-data android:value= "Demonstration of the Xposed framework.\nMakes the status bar clock red." android:name= "xposeddescription" />
</application>
</manifest>
而後,將 XposedBridge.jar 這個引用導入到工程中,加入到 reference path 中。
下面開始建立一個新的工程:
package com . kevin . myxposed ;
import android . util . Log ;
import de . robv . android . xposed . IXposedHookLoadPackage ;
import de . robv . android . xposed . XposedBridge ;
import de . robv . android . xposed . callbacks . XC_LoadPackage . LoadPackageParam ;
public class XposedInterface implements IXposedHookLoadPackage {
public void handleLoadPackage ( final LoadPackageParam lpparam ) throws Throwable {
XposedBridge . log ( "Kevin-Loaded app: " + lpparam . packageName );
}
}
而後在 assets 目錄下新建一個 xposed_init 文件,這個文件聲明瞭須要加載到 XposedInstaller 的入口類:
com.kevin.myxposed.XposedInterface
運行程序並在 XposedInstaller 的 Module 選項中激活,重啓機器後能夠獲得以下數據:
在上一步中咱們已經定位了須要 Hook 的方法以及所在的類,譬如:
com.android.systemui.statusbar.policy.Clock 類
中的 updateClock 方法。
package de . robv . android . xposed . mods . tutorial ;
import static de . robv . android . xposed . XposedHelpers . findAndHookMethod;
import de . robv . android . xposed . IXposedHookLoadPackage ;
import de . robv . android . xposed . XC_MethodHook ;
import de . robv . android . xposed . callbacks . XC_LoadPackage . LoadPackageParam ;
public class Tutorial implements IXposedHookLoadPackage {
public void handleLoadPackage ( final LoadPackageParam lpparam ) throws Throwable {
if (! lpparam . packageName . equals ( "com.android.systemui" ))
return ;
findAndHookMethod ( "com.android.systemui.statusbar.policy.Clock" , lpparam . classLoader , "handleUpdateClock" , new XC_MethodHook () {
protected void beforeHookedMethod ( MethodHookParam param ) throws Throwable {
// this will be called before the clock was updated by the original method
}
protected void afterHookedMethod ( MethodHookParam param ) throws Throwable {
// this will be called after the clock was updated by the original method
}
});
}
}
關於findAndHookMethod 方法的說明見下面的 API Reference 。
下面所使用的方法能夠適用於 Boolean 、 Color 、 Integer 、 int[] 、 String 與 String[] 。
其中,對於 Android 框架層的資源(全部的 APP 都須要調用的資源)應該在 initZygote 這個方法中完成替換。而對於屬於應用程序的資源,應該在 hookInitPackageResources 這個方法中完成替換。
public void initZygote ( IXposedHookZygoteInit . StartupParam startupParam ) throws Throwable {
XResources . setSystemWideReplacement ( "android" , "bool" , "config_unplugTurnsOnScreen" , false );
}
public void handleInitPackageResources ( InitPackageResourcesParam resparam ) throws Throwable {
// replacements only for SystemUI
if (! resparam . packageName . equals ( "com.android.systemui" ))
return ;
// different ways to specify the resources to be replaced
resparam . res . setReplacement ( 0x7f080083 , "YEAH!" ); // WLAN toggle text. You should not do this because the id is not fixed. Only for framework resources, you could use android.R.string.something
resparam . res . setReplacement ( "com.android.systemui:string/quickpanel_bluetooth_text" , "WOO!" );
resparam . res . setReplacement ( "com.android.systemui" , "string" , "quickpanel_gps_text" , "HOO!" );
resparam . res . setReplacement ( "com.android.systemui" , "integer" , "config_maxLevelOfSignalStrengthIndicator" , 6 );
resparam . res . setReplacement ( "com.android.systemui" ,
"drawable" , "status_bar_background" ,
new XResources . DrawableLoader () {
public Drawable newDrawable ( XResources res , int id ) throws Throwable {
return new ColorDrawable ( Color . WHITE );
}
});
}
package de . robv . android . xposed . mods . coloredcirclebattery ;
import android . content . res . XModuleResources ;
import de . robv . android . xposed . IXposedHookInitPackageResources ;
import de . robv . android . xposed . IXposedHookZygoteInit ;
import de . robv . android . xposed . callbacks . XC_InitPackageResources . InitPackageResourcesParam ;
public class ColoredCircleBattery implements IXposedHookZygoteInit , IXposedHookInitPackageResources {
private static String MODULE_PATH = null ;
public void initZygote ( StartupParam startupParam ) throws Throwable {
MODULE_PATH = startupParam . modulePath ;
}
public void handleInitPackageResources ( InitPackageResourcesParam resparam ) throws Throwable {
if (! resparam . packageName . equals ( "com.android.systemui" ))
return ;
XModuleResources modRes = XModuleResources . createInstance ( MODULE_PATH , resparam . res );
resparam . res . setReplacement ( "com.android.systemui" , "drawable" , "stat_sys_battery" , modRes . fwd ( R . drawable . battery_icon ));
resparam . res . setReplacement ( "com.android.systemui" , "drawable" , "stat_sys_battery_charge" , modRes . fwd ( R . drawable . battery_icon_charge ));
}
}
public void handleInitPackageResources ( InitPackageResourcesParam resparam ) throws Throwable {
if (! resparam . packageName . equals ( "com.android.systemui" ))
return ;
resparam . res . hookLayout ( "com.android.systemui" , "layout" , "status_bar" , new XC_LayoutInflated () {
public void handleLayoutInflated ( LayoutInflatedParam liparam ) throws Throwable {
TextView clock = ( TextView ) liparam . view . findViewById (
liparam . res . getIdentifier ( "clock" , "id" , "com.android.systemui" ));
clock . setTextColor ( Color . RED );
}
});
}
這個方法用於在加載應用程序的包的時候執行用戶的操做。 public class XposedInterface implements IXposedHookLoadPackage { public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { XposedBridge.log("Kevin-Loaded app: " + lpparam.packageName); final LoadPackageParam lpparam這個參數包含了加載的應用程序的一些基本信息。 |
|
這是一個輔助方法,能夠經過以下方式靜態導入: import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "handleUpdateClock", new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // this will be called before the clock was updated by the original method protected void afterHookedMethod(MethodHookParam param) throws Throwable { // this will be called after the clock was updated by the original method v findAndHookMethod(Class<?> clazz, // 須要 Hook 的類名 ClassLoader, // 類加載器,能夠設置爲 null String methodName, // 須要 Hook 的方法名 Object... parameterTypesAndCallback 該函數的最後一個參數集,包含了: (1)Hook 的目標方法的參數 , 譬如: "com.android.internal.policy.impl.PhoneWindow.DecorView" 是方法的參數的類。 ( 2 )回調方法: b.XC_MethodReplacement |
|
Xposed 框架也爲咱們提供了不少的輔助項來幫助咱們快速開發 XposedModule 。
該方法能夠將 log 信息以及 Throwable 拋出的異常信息輸出到標準的 logcat 以及 /data/xposed/debug.log這個文件中。 |
|
hookAllMethods / hookAllConstructors |
該方法能夠用來hook 某個類中的全部方法或者構造函數,可是不一樣的 Rom (非 Android 原生 Rom )會有不一樣的變種。 |
這個類用的也是比較多,可使用
Window => Preferences => Java => Editor => Content Assist => Favorites => New Type, enter de.robv.android.xposed.XposedHelpers
這種方式將XposedHelpers 這個類加入到 Eclipse 靜態調用中方便查閱。
findMethod / findConstructor / findField |
這是一組用於檢索方法的方法。 |
callMethod / callStaticMethod / newInstance |
|
以字節數組的形式返回 asset ,能夠以以下方式調用: public class XposedTweakbox { private static final String MODULE_PATH = null; // injected by XposedBridge public static void init(String startClassName) throws Exception { if (startClassName != null) Resources tweakboxRes = XModuleResources.createInstance(MODULE_PATH, null); byte[] crtPatch = assetAsByteArray(tweakboxRes, "crtfix_samsung_d506192d5049a4042fb84c0265edfe42.bsdiff"); |
|
返回對於一個文件的 MD5 校驗值,須要 root權限。 |
|
獲取一個進程的 PID 值,輸入參數爲 /proc/[pid]/cmdline |
from:https://www.cnblogs.com/lkislam/p/4859959.html