Xposed原理簡介及其精簡化

weixin_34320159發表於2018-09-09

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。如下圖:

13364326-d5d0d8280a6c6cfa.png
Xposed HOOK 原理

通過讀Xposed原始碼發現其啟動過程:

  1. 手機啟動時init程式會啟動zygote這個程式。由於zygote程式檔案app_process已被替換,所以啟動的時Xposed版的zygote程式。
  2. Xposed_zygote程式啟動後會初始化一些so檔案(system/lib system/lib64),然後進入XposedBridge.jar中的XposedBridge.main中初始化jar包完成對一些關鍵Android系統函式的hook。
  3. Hook則是利用修改過的虛擬機器將函式註冊為native函式。
  4. 然後再返回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就好。

相關文章