1 Introduction
1.1 概述
Xposed 是 GitHUB 上 rovo89 大大設計的一個針對 Android 平台的動態劫持項目,通過替換 /system/bin/app_process 程序控制 zygote 進程,使得 app_process 在啟動過程中會加載 XposedBridge.jar 這個 jar 包,從而完成對系統應用的劫持。
Xposed 框架的基本運行環境如下:
因為 Xposed 工作原理是在 /system/bin 目錄下替換文件,在 install 的時候需要 root 權限,但是運行時不需要 root 權限。 |
|
需要在 Android 4.0 以上版本的機器中 |
- Xposed 在線資源可以參照: http://forum.xda-developers.com/showthread.php?t=1574401
2. GitHub 上的 Xposed 資源梳理一下,可以這么分類:
- XposedBridge.jar : XposedBridge.jar 是 Xposed 提供的 jar 文件,負責在 Native層與 FrameWork 層進行交互。 /system/bin/app_process 進程啟動過程中會加載該 jar 包,其它的 Modules 的開發與運行都是基於該 jar 包的。
- Xposed : Xposed 的 C++ 部分,主要是用來替換 /system/bin/app_process ,並為 XposedBridge 提供 JNI 方法。
- XposedInstaller : Xposed 的安裝包,負責配置 Xposed 工作的環境並且提供對基於 Xposed 框架的 Modules 的管理。
- XposedMods :使用 Xposed 開發的一些 Modules ,其中 AppSettings 是一個可以進行權限動態管理的應用
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 模塊
該部分的源代碼地址是: https://github.com/rovo89/Xposed ,其文件分類如下:
- 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 和回調函數的注冊,代碼如下:
/**
* 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 的操作。
2.2.5 Hook : Native 層 hookMethodNative預處理Hook
進入 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 )
2.2.6 Native Hook : Native Function 的 dispatcher xposedCallHandler
進入 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 之前的狀態繼續運行。
2.2.7 XposedBridge : 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.2.8 加載基於 Xposed 模塊
繼續回到 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 ();
}
2.2.9 調用 ZygoteInit.main 或 RuntimeInit.main
在 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 );
3 Developer Wiki
3.1 創建一個 Xposed Module
一個 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 選項中激活,重啟機器后可以得到如下數據:
3.2 HookedMethod :定位你要 hook 的方法
在上一步中我們已經定位了需要 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 。
3.3 進行資源替換
3.3.1 簡易資源替換
下面所使用的方法可以適用於 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 );
}
});
}
3.3.2 復雜資源
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 ));
}
}
3.3.3 修改 layouts
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 );
}
});
}
4 API Reference
關鍵類 /API 說明
IXposedHookLoadPackage
這個方法用於在加載應用程序的包的時候執行用戶的操作。 public class XposedInterface implements IXposedHookLoadPackage { public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { XposedBridge.log("Kevin-Loaded app: " + lpparam.packageName); final LoadPackageParam lpparam這個參數包含了加載的應用程序的一些基本信息。 |
|
XposedHelpers
這是一個輔助方法,可以通過如下方式靜態導入: 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 |
|
輔助項 API
Xposed 框架也為我們提供了很多的輔助項來幫助我們快速開發 XposedModule 。
XposedBridge 類
該方法可以將 log 信息以及 Throwable 拋出的異常信息輸出到標准的 logcat 以及 /data/xposed/debug.log這個文件中。 |
|
hookAllMethods / hookAllConstructors |
該方法可以用來hook 某個類中的所有方法或者構造函數,但是不同的 Rom (非 Android 原生 Rom )會有不同的變種。 |
XposedHelpers 類
這個類用的也是比較多,可以使用
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