Xposed原理簡介及其精簡化
Xposed是一個很強大的Android平臺上的HOOK工具,而且作者為了方便開發者使用開發了一個APP(Xposed Installer,下文稱為Installer) 來使用開發者自己開發的模組。開發者安裝自己的模組後需要在Installer中勾選自己的模組然後重啟手機自己的模組才會起作用。但是這樣有點不利於開發者測試,每次都要點開Installer操作幾下尤其是還要重啟就顯得有點麻煩了。
讀過Xposed的原始碼後會發現僅通過更改 XposedBridge.jar 的原始碼就可以更簡便一些:
1. 不需重啟手機
2. 不需操作Installer這個App,且不用安裝hook模組,只需push到手機即可
首先需要下載原始碼, rovo89 連結裡面有Xposed所有原始碼,我們只需要下載XposedBridge就好。原版的Xposed並不適用於三星手機, wanam 改過的Xposed可以用,所以如果是三星手機的話就下載wanam的。
1. Xposed原理簡介
現在安裝Xposed比較方便,因為Xposed作者開發了一個Xposed Installer App,下載後按照提示傻瓜式安裝(前提是root手機)。其實它的安裝過程是這個樣子的:首先探測手機型號,然後按照手機版本下載不同的刷機包,最後把Xposed刷機包刷入手機重啟就好。 刷機包下載 裡面有所有版本的刷機包。
刷機包解壓開啟裡面的問件構成是這個樣子的:
META-INF/裡面有檔案配置指令碼 flash-script.sh 配置各個檔案安裝位置。 system/bin/替換zygote程序等檔案 system/framework/XposedBridge.jar jar包位置 system/lib system/lib64 一些so檔案所在位置 xposed.prop xposed版本說明檔案
所以安裝Xposed的過程就上把上面這些檔案放到手機裡相同檔案路徑下。
通過檢視檔案安裝指令碼發現:
system/bin/下面的檔案替換了app_process等檔案,app_process就是zygote程序檔案。所以Xposed通過替換zygote程序實現了控制手機上所有app程序。因為所有app程序都是由Zygote fork出來的。Xposed的基本原理是修改了ART/Davilk虛擬機器,將需要hook的函式註冊為Native層函式。當執行到這一函式是虛擬機器會優先執行Native層函式,然後再去執行Java層函式,這樣完成函式的hook。如下圖:

Xposed HOOK 原理
通過讀Xposed原始碼發現其啟動過程:
- 手機啟動時init程序會啟動zygote這個程序。由於zygote程序檔案app_process已被替換,所以啟動的時Xposed版的zygote程序。
- Xposed_zygote程序啟動後會初始化一些so檔案(system/lib system/lib64),然後進入XposedBridge.jar中的XposedBridge.main中初始化jar包完成對一些關鍵Android系統函式的hook。
- Hook則是利用修改過的虛擬機器將函式註冊為native函式。
- 然後再返回zygote中完成原本zygote需要做的工作。
這只是在巨集觀層面稍微介紹了下Xposed,要想詳細瞭解需要讀它的原始碼了。下面兩篇寫的挺好,要想深入理解的可以看看。
Android Hook框架Xposed原理與原始碼分析
深入理解Android之Xposed詳解
2. Xposed精簡化
上面稍微介紹了下它的原理,下面就介紹如何精簡化Xposed。下面只修改了XposedBridge.jar包中的XposedBridge.java這個檔案,修改完重新Build apk然後把apk重新命名為XposedBridge.jar然後替換刷機包中的jar包,刷入手機即可。
2.1 取消重啟手機
看下XposedBridge.jar的原始碼
程式碼檔案de.robv.android.xposed.XposedBridge.java
protected static void main(String[] args) { // Initialize the Xposed framework and modules try { if (!hadInitErrors()) { initXResources(); SELinuxHelper.initOnce(); SELinuxHelper.initForProcess(null); runtime = getRuntime(); XPOSED_BRIDGE_VERSION = getXposedVersion(); if (isZygote) { XposedInit.hookResources(); XposedInit.initForZygote(); } //修改時需註釋下面這行程式碼 XposedInit.loadModules();//*********load hook 模組******************* } else { Log.e(TAG, "Not initializing Xposed because of previous errors"); } } catch (Throwable t) { Log.e(TAG, "Errors during Xposed initialization", t); disableHooks = true; } // Call the original startup code if (isZygote) {//****程式碼修改位置**** ZygoteInit.main(args); } else { RuntimeInit.main(args); } }
注意上面的XposedInit.loadModules()這個函式,這個函式的作用就是load hook模組到程序中。
因為zygote啟動時先跑到java層XposeBridge.main中,在main裡面有一步操作是將hook模組load進來,模組載入到zygote程序中,zygote fork所有的app程序裡面也有這個hook模組,所以這個模組可以hook任意app。(編寫hook模組的第一步就是判斷當前的程序名字,如果是要hook的程序就hook,不是則返回)。
所以修改模組後, 要將模組重新load zygote裡面必須重啟zygote,要想zygote重啟就要重啟手機了。
所以修改的邏輯是不把模組load到zygote裡面,而是load到自己想要hook的程序裡面,這樣修改模組後只需重啟該程序即可。
在上面程式碼的 程式碼修改位置 新增如下程式碼,並將 上面XposedInit.loadModules()註釋掉即可 。
if (isZygote) { XposedHelpers.findAndHookMethod("com.android.internal.os.ZygoteConnection", BOOTCLASSLOADER, "handleChildProc", "com.android.internal.os.ZygoteConnection.Arguments",FileDescriptor[].class,FileDescriptor.class, PrintStream.class,new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // TODO Auto-generated method stub super.afterHookedMethod(param); String processName = (String) XposedHelpers.getObjectField(param.args[0], "niceName"); String coperationAppName = "指定程序名稱如:com.android.settings"; if(processName != null){ if(processName.startsWith(coperationAppName)){ log("--------Begin Load Module-------"); XposedInit.loadModules() } } } }); ZygoteInit.main(args); } else { RuntimeInit.main(args); }
2.2 取消操作Installer APP
通過讀Install App的原始碼發現其實勾選hook模組其實app就是把模組的apk位置寫到一個檔案裡,等load模組時會讀取這個檔案,從這個檔案中的apk路徑下把apk load到程序中。
看下loadmodules的原始碼
XposedInit.java /** * Try to load all modules defined in <code>BASE_DIR/conf/modules.list</code> */ /*package*/ static void loadModules() throws IOException { final String filename = BASE_DIR + "conf/modules.list"; BaseService service = SELinuxHelper.getAppDataFileService(); if (!service.checkFileExists(filename)) { Log.e(TAG, "Cannot load any modules because " + filename + " was not found"); return; } ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER; ClassLoader parent; while ((parent = topClassLoader.getParent()) != null) { topClassLoader = parent; } InputStream stream = service.getFileInputStream(filename); BufferedReader apks = new BufferedReader(new InputStreamReader(stream)); String apk; while ((apk = apks.readLine()) != null) { loadModule(apk, topClassLoader); } apks.close(); }
apk配置檔案就是installer app檔案路徑下的conf/modules.list這個檔案data/data/de.robv.android.xposed.installer/conf/modules.list
或者data/user_de/0/de.robv.android.xposed.installer/conf/modules.list(不同手機版本位置不同)
所以我們改下,直接給他個確定的apk路徑及名稱。就定為/data/local/tmp/module.apk。
所以再把之前的程式碼改成如下的樣子就好了
if (isZygote) { XposedHelpers.findAndHookMethod("com.android.internal.os.ZygoteConnection", BOOTCLASSLOADER, "handleChildProc", "com.android.internal.os.ZygoteConnection.Arguments",FileDescriptor[].class,FileDescriptor.class, PrintStream.class,new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // TODO Auto-generated method stub super.afterHookedMethod(param); String processName = (String) XposedHelpers.getObjectField(param.args[0], "niceName"); String coperationAppName = "指定程序名稱如:com.android.settings"; if(processName != null){ if(processName.startsWith(coperationAppName)){ log("--------Begin Load Module-------"); String path = "/data/local/tmp/module.apk"; //注意由loadModules換成了loadModule,記得改 XposedInit.loadModule(path, BOOTCLASSLOADER); } } } }); ZygoteInit.main(args); } else { RuntimeInit.main(args); }
所以每次修改模組後直接修改名字為module.apk然後push到/data/local/tmp/下,然後重啟app就好。