美文网首页
Xposed 配置,使用以及原理介绍

Xposed 配置,使用以及原理介绍

作者: android小奉先 | 来源:发表于2022-06-05 07:55 被阅读0次

    本篇介绍

    在搞逆向的时候,Hook是个很必要的手段,通过Hook就可以了解内部的运行机制,甚至进行修改。对于Hook Java方法,用的比较多的就是Xposed,本篇就介绍下Xposed的配置,使用,原理。

    1 Xposed 配置

    1.1 刷入twrp

    twrp, TeamWin Recovery Project,是一个三方recovery,似乎于win PE,通过twrp,我们就可以刷入各种包,非常方便。
    笔者这边的设备是Pixel 4,下载链接,如果是其他设备,就可能需要再搜一搜,twrp针对不同设备都有专门的包,非常全。

    1.1.1 手机root

    手机root 最简单的方法就是在某宝上花个几块钱,root了就行,如果自己想尝试,就需要先解oem锁,fastboot 锁,再刷入root包。

    1.1.2 刷twrp

    Pixel 4 是A/B分区,不能一下子刷入twrp镜像,只能先刷入tmrp 镜像

    fastboot boot twrp.img
    

    这时候就会进入twrp的recovery界面,如下:


    image.png

    继续push twrp zip包到手机sdcard目录:

    adb push twrp.zip /sdcard
    

    然后点击install,选中twrp.zip 进行安装就好了。

    这个过程比较容易,可是笔者在这儿折腾了好长时间,主要原因是twrp目前在lineage 19 (android 12)上有点问题,现象就是无法进入twrp的recovery,看了下原因,是缺少libion.so。 刚开始也打算替换成lineage 18 (android 11),后来发现无法降级,只能先刷成 pixel的 factory镜像, 再刷 twrp,然后用twrp刷 lineage 18, 再用lineage 18刷twrp。

    1.1.3 安装magisk

    magsik 可以看成是一个特殊的文件系统,可以提供root能力的同时又隐藏已被root的事实,这样一些防root的应用也可以在设备上正常使用了,和xposed的关系如下:

    image.png
    magisk网站下载最新的magisk apk,重命名为zip,然后push到sdcard下。
    adb push magisk.zip /sdcard
    

    进入twrp 模式:

    adb reboot recovery
    

    选择magisk.zip 进行安装。
    安装好后,再打开magisk,效果如下:


    image.png

    需要注意的是我们直接下载的magisk.apk,如果真当apk 直接安装的话,Superuser和Modules部分是不能用的。简单点的操作就是用twrp刷。

    上面有2个install,可以直接点点,就装上了。

    1.1.4 安装riru和EdXposed

    由于Xposed只能支持到O版本,如果要用Xposed,只能安装EdXposed等扩展版本。
    先安装以下apk:
    riru Manager apk
    EdXposed Mananger apk
    再通过Magisk如下zip:
    riru core
    Edxposed YAHFA

    先把这两个zip push到sdcard下:

    adb push xxx.zip /sdcard
    

    然后安装:


    image.png

    记得先安装riru,安装完后重启下。
    再回到系统,点开那两个apk:


    image.png
    image.png
    能看到类似信息,那说明安装好了。

    1.1.5 安装总结

    这一个小节介绍下我们安装的几个模块,不涉及操作了。
    xposed: 通过替换zygote的进程app_process 提供hook能力
    magisk: 通过overlay方式避免修改app_process
    riru: 以动态的方式让app_process 拥有hook能力,本质上就是通过ro.dalvik.vm.native.bridge属性让zygote加载一个so,通过该so提供hook能力。
    YAHFA:android art上的一个hook框架
    看到这些介绍,现在就知道我们安装的那些zip,apk分别用来干啥了吧?

    2 Module演示

    接下来我们通过一个Demo演示下Xposed的能力,比如我们需要hook下微信,让微信显示我们自己定义的字符。

    1.1 环境准备

    首先创建一个工程,无Activity就行。
    接下来修改AndroidManifest.xml

        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.Hookdemo">
    
    //  声明这是一个xposed模块
            <meta-data
                android:name="xposedmodule"
                android:value="true" />
    // 模块描述
            <meta-data
                android:name="xposeddescription"
                android:value="微信hook" />
    // 支持的版本
            <meta-data
                android:name="xposedminversion"
                android:value="53" />
        </application>
    

    然后在app的gradle 里添加下依赖:

     compileOnly 'de.robv.android.xposed:api:82'
     compileOnly 'de.robv.android.xposed:api:82:sources'
    

    接下来新建一个类实现IXposedHookLoadPackage 接口,这儿就是hook的行为了,代码基本可以自解释:

    package com.example.hookdemo;
    
    import android.util.Log;
    
    import java.lang.reflect.Method;
    import java.util.Collections;
    import java.util.List;
    
    import de.robv.android.xposed.IXposedHookLoadPackage;
    import de.robv.android.xposed.XC_MethodHook;
    import de.robv.android.xposed.XposedHelpers;
    import de.robv.android.xposed.callbacks.XC_LoadPackage;
    
    public class MyHookClass implements IXposedHookLoadPackage {
        private static final String TAG = "shanks";
    
        @Override
        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
            if (!lpparam.packageName.contains("com.tencent.mm")) {
                return;
            }
            hookClassMethod(android.widget.TextView.class, Collections.singletonList("setText"), null, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    if (param.args.length == 0) {
                        Log.i(TAG, "length 0");
                        return;
                    }
                    Object value = param.args[0];
                    param.args[0] = "hooked";
                }
    
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }
    
        private void hookClassMethod(Class<?> clazz, List<String> includes, List<String> excludes, XC_MethodHook hook) {
            Method[] methods = clazz.getDeclaredMethods();
            if (methods == null || methods.length == 0) {
                return;
            }
            for (Method method : methods) {
                if (includes != null && !includes.isEmpty() && !includes.contains(method.getName())) {
                    continue;
                }
                if (excludes != null && excludes.contains(method.getName())) {
                    continue;
                }
                Class<?>[] paramTypes = method.getParameterTypes();
                Object[] types = new Object[paramTypes.length + 1];
                System.arraycopy(paramTypes, 0, types, 0, paramTypes.length);
                types[paramTypes.length] = hook;
                try {
                    XposedHelpers.findAndHookMethod(clazz, method.getName(), types);
                    Log.i(TAG, "findAndHookMethod " + clazz.getName() + " " + method.getName());
                } catch (Throwable e) {
                    Log.e(TAG, "findAndHookMethod " + method.getName() + " failed.", e);
                }
            }
        }
    }
    
    

    接下来就是最后一步,告诉xposed 那个类是hook的入口类,新建一个目录,操作如下:

    image.png
    目录名字就是assets,然后新建一个txt文件,名字是xposed_init,内容就是hook类,比如对于我们的demo,内容就是com.example.hookdemo.MyHookClass
    接下来点击run,这样就安装到手机里了,由于没有Activity,桌面上会看不到,不过没关系,打开xposed manager,选择modules,然后打开:
    image.png
    手机重启下。

    重启后,打开微信,接下来就可以见证效果了:


    image.png

    3 Xposed 原理

    接下来我们从代码层面看下Xposed的原理.
    代码路径:
    xposed framework: https://github.com/rovo89/XposedBridge
    xposed: https://github.com/rovo89/Xposed
    大致介绍下xposed之前的流程,android 按下开机键后,就会执行ROM上的一段代码,这段代码会加载bootloader,bootloader会读硬件参数,然后加载kernel,kernel负责系统的初始化,文件系统,内存管理,中断表,页表等都搞好后,就通过0号进程fork 出 1号进程,也就是init,init先挂载文件系统,各种分区,然后再解析rc,启动rc里面定义的服务,当然zygote也是其中一个,如果init是android 的祖先的话,zygote就是Android应用的祖先。zygote启动的时候会先fork一下,创建出systemserver,这里就会启动android系统服务,比如AMS,Servicemanager,WMS,PMS等等各种耳熟能详的服务都是这儿拉起来的,zygote 最后就会启动一个socket,然后静静等着就好了。
    如果我们点击一个桌面图标,这时候launch就会请求ams 启动这个app,ams 会请求zygote fork进程,然后在这个进程里面启动这个应用。

    xposed就是用zygote开始的,我们需要了解的是xposed如何支持我们hook java方法的,带着这个疑问就开始这段旅途吧:
    故事的开始是zygote的main函数:

    int main(int argc, char* const argv[])
    {
    //  解析xpose的参数,比如版本之类的
        if (xposed::handleOptions(argc, argv)) {
            return 0;
        }
    
        AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
        // Process command line arguments
        // ignore argv[0]
        argc--;
        argv++;
    
        // Everything up to '--' or first non '-' arg goes to the vm.
        //
        // The first argument after the VM args is the "parent dir", which
        // is currently unused.
        //
        // After the parent dir, we expect one or more the following internal
        // arguments :
        //
        // --zygote : Start in zygote mode
        // --start-system-server : Start the system server.
        // --application : Start in application (stand alone, non zygote) mode.
        // --nice-name : The nice name for this process.
        //
        // For non zygote starts, these arguments will be followed by
        // the main class name. All remaining arguments are passed to
        // the main method of this class.
        //
        // For zygote starts, all remaining arguments are passed to the zygote.
        // main function.
        //
        // Note that we must copy argument string values since we will rewrite the
        // entire argument block when we apply the nice name to argv0.
    
        int i;
        for (i = 0; i < argc; i++) {
            if (argv[i][0] != '-') {
                break;
            }
            if (argv[i][1] == '-' && argv[i][2] == 0) {
                ++i; // Skip --.
                break;
            }
            runtime.addOption(strdup(argv[i]));
        }
    
        // Parse runtime arguments.  Stop at first unrecognized option.
        bool zygote = false;
        bool startSystemServer = false;
        bool application = false;
        String8 niceName;
        String8 className;
    
        ++i;  // Skip unused "parent dir" argument.
        while (i < argc) {
            const char* arg = argv[i++];
            if (strcmp(arg, "--zygote") == 0) {
                zygote = true;
                niceName = ZYGOTE_NICE_NAME;
            } else if (strcmp(arg, "--start-system-server") == 0) {
                startSystemServer = true;
            } else if (strcmp(arg, "--application") == 0) {
                application = true;
            } else if (strncmp(arg, "--nice-name=", 12) == 0) {
                niceName.setTo(arg + 12);
            } else if (strncmp(arg, "--", 2) != 0) {
                className.setTo(arg);
                break;
            } else {
                --i;
                break;
            }
        }
    
        Vector<String8> args;
        if (!className.isEmpty()) {
            // We're not in zygote mode, the only argument we need to pass
            // to RuntimeInit is the application argument.
            //
            // The Remainder of args get passed to startup class main(). Make
            // copies of them before we overwrite them with the process name.
            args.add(application ? String8("application") : String8("tool"));
            runtime.setClassNameAndArgs(className, argc - i, argv + i);
        } else {
            // We're in zygote mode.
            maybeCreateDalvikCache();
    
            if (startSystemServer) {
                args.add(String8("start-system-server"));
            }
    
            char prop[PROP_VALUE_MAX];
            if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
                LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                    ABI_LIST_PROPERTY);
                return 11;
            }
    
            String8 abiFlag("--abi-list=");
            abiFlag.append(prop);
            args.add(abiFlag);
    
            // In zygote mode, pass all remaining arguments to the zygote
            // main() method.
            for (; i < argc; ++i) {
                args.add(String8(argv[i]));
            }
        }
    
        if (!niceName.isEmpty()) {
            runtime.setArgv0(niceName.string());
            set_process_name(niceName.string());
        }
    
        if (zygote) {
           // 走xposed的流程,初始化xposed的参数,
            isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv);
            runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote);
        } else if (className) {
         // fork app流程,也是走xposed的话入口会不一样
            isXposedLoaded = xposed::initialize(false, false, className, argc, argv);
            runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote);
        } 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;
        }
    }
    
    

    runtimeStart其实就是启动XPOSED_CLASS_DOTS_ZYGOTE,直接看下XPOSED_CLASS_DOTS_ZYGOTE (XposedBridge.java),执行的方法是main:

        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();
                                            // 通过hook 系统方法来加载模块自己定义的hook
                        XposedInit.initForZygote();
                    }
    
                    XposedInit.loadModules();
                } 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);
            }
        }
    

    这儿主要是初始化资源,看下loadModules:

        /**
         * 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();
        }
    

    这儿就是提前load modules.list中的module,其实也就是我们写好的module。看看如何加载module的:

        private static void loadModule(String apk, ClassLoader topClassLoader) {
            Log.i(TAG, "Loading modules from " + apk);
    
            if (!new File(apk).exists()) {
                Log.e(TAG, "  File does not exist");
                return;
            }
    
            DexFile dexFile;
            try {
                dexFile = new DexFile(apk);
            } catch (IOException e) {
                Log.e(TAG, "  Cannot load module", e);
                return;
            }
    
            if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
                Log.e(TAG, "  Cannot load module, please disable \"Instant Run\" in Android Studio.");
                closeSilently(dexFile);
                return;
            }
                   // 这就是我们在gradle里面用compileOnly被拦截的原因
            if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
                Log.e(TAG, "  Cannot load module:");
                Log.e(TAG, "  The Xposed API classes are compiled into the module's APK.");
                Log.e(TAG, "  This may cause strange issues and must be fixed by the module developer.");
                Log.e(TAG, "  For details, see: http://api.xposed.info/using.html");
                closeSilently(dexFile);
                return;
            }
    
            closeSilently(dexFile);
    
            ZipFile zipFile = null;
            InputStream is;
            try {
                zipFile = new ZipFile(apk);
                           // 这个就是我们写hook入口的文件
                ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
                if (zipEntry == null) {
                    Log.e(TAG, "  assets/xposed_init not found in the APK");
                    closeSilently(zipFile);
                    return;
                }
                is = zipFile.getInputStream(zipEntry);
            } catch (IOException e) {
                Log.e(TAG, "  Cannot read assets/xposed_init in the APK", e);
                closeSilently(zipFile);
                return;
            }
    
            ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);
            BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
            try {
                String moduleClassName;
                while ((moduleClassName = moduleClassesReader.readLine()) != null) {
                    moduleClassName = moduleClassName.trim();
                    if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
                        continue;
    
                    try {
                        Log.i(TAG, "  Loading class " + moduleClassName);
                        Class<?> moduleClass = mcl.loadClass(moduleClassName);
    
                        if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
                            Log.e(TAG, "    This class doesn't implement any sub-interface of IXposedMod, skipping it");
                            continue;
                        } else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
                            Log.e(TAG, "    This class requires resource-related hooks (which are disabled), skipping it.");
                            continue;
                        }
    
                        final Object moduleInstance = moduleClass.newInstance();
                        if (XposedBridge.isZygote) {
                            if (moduleInstance instanceof IXposedHookZygoteInit) {
                                IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
                                param.modulePath = apk;
                                param.startsSystemServer = startsSystemServer;
                                ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
                            }
    
                            if (moduleInstance instanceof IXposedHookLoadPackage)
                                XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
    
                            if (moduleInstance instanceof IXposedHookInitPackageResources)
                                XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
                        } else {
                            if (moduleInstance instanceof IXposedHookCmdInit) {
                                IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
                                param.modulePath = apk;
                                param.startClassName = startClassName;
                                ((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
                            }
                        }
                    } catch (Throwable t) {
                        Log.e(TAG, "    Failed to load class " + moduleClassName, t);
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "  Failed to load module from " + apk, e);
            } finally {
                closeSilently(is);
                closeSilently(zipFile);
            }
        }
    }
    

    这儿就是从xposed_init中读取我们写的hook类 ,然后加载,看下如何加载的:

        public static void hookLoadPackage(XC_LoadPackage callback) {
            synchronized (sLoadedPackageCallbacks) {
                sLoadedPackageCallbacks.add(callback);
            }
        }
    

    这儿就是将我们实现的接口当成回调保存起来了。现在还没看到我们写的模块hook是如何被加载的,继续看下initForZygote:

        /**
         * Hook some methods which we want to create an easier interface for developers.
         */
        /*package*/ static void initForZygote() throws Throwable {
            if (needsToCloseFilesForFork()) {
                XC_MethodHook callback = new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.closeFilesBeforeForkNative();
                    }
    
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.reopenFilesAfterForkNative();
                    }
                };
    
                Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
                hookAllMethods(zygote, "nativeForkAndSpecialize", callback);
                hookAllMethods(zygote, "nativeForkSystemServer", callback);
            }
    
            final HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
    
            // normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
            findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    ActivityThread activityThread = (ActivityThread) param.thisObject;
                    ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo");
                    String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName;
                    SELinuxHelper.initForProcess(reportedPackageName);
                    ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName");
                    if (instrumentationName != null) {
                        Log.w(TAG, "Instrumentation detected, disabling framework for " + reportedPackageName);
                        XposedBridge.disableHooks = true;
                        return;
                    }
                    CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo");
                    if (appInfo.sourceDir == null)
                        return;
    
                    setObjectField(activityThread, "mBoundApplication", param.args[0]);
                    loadedPackagesInProcess.add(reportedPackageName);
                    LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
                    XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
    
                    XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); // 读取hook 回调
                    lpparam.packageName = reportedPackageName;
                    lpparam.processName = (String) getObjectField(param.args[0], "processName");
                    lpparam.classLoader = loadedApk.getClassLoader();
                    lpparam.appInfo = appInfo;
                    lpparam.isFirstApplication = true;
                    XC_LoadPackage.callAll(lpparam); // 加载
    
                    if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME))
                        hookXposedInstaller(lpparam.classLoader);
                }
            });
                 // ...
    
    

    这儿注册了handleBindApplication的hook,这样在应用启动的时候会attach到AMS,然后ams就会发一个BindApplication调用过来,这时候应用就会触发handleBindApplication,然后将我们注册的回调再加载下,加载操作就是通过XC_LoadPackage.callAll完成的, 这里会调用每个Hook的call方法,直接看Hook的call

        protected void call(Param param) throws Throwable {
            if (param instanceof LoadPackageParam)
                handleLoadPackage((LoadPackageParam) param);
        }
    

    终于看到了我们实现的方法了。
    继续看下onVmCreated,因为AndroidRuntime其实就是创建并启动虚拟机,然后执行给定类的main方法,那创建虚拟机后,native就会收到回调:

    virtual void onVmCreated(JNIEnv* env)
        {
        // 调用xposed的初始化方法
            if (isXposedLoaded)
                xposed::onVmCreated(env);
    
            if (mClassName.isEmpty()) {
                return; // Zygote. Nothing to do here.
            }
    
            /*
             * This is a little awkward because the JNI FindClass call uses the
             * class loader associated with the native method we're executing in.
             * If called in onStarted (from RuntimeInit.finishInit because we're
             * launching "am", for example), FindClass would see that we're calling
             * from a boot class' native method, and so wouldn't look for the class
             * we're trying to look up in CLASSPATH. Unfortunately it needs to,
             * because the "am" classes are not boot classes.
             *
             * The easiest fix is to call FindClass here, early on before we start
             * executing boot class Java code and thereby deny ourselves access to
             * non-boot classes.
             */
            char* slashClassName = toSlashClassName(mClassName.string());
            mClass = env->FindClass(slashClassName);
            if (mClass == NULL) {
                ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
                env->ExceptionDescribe();
            }
            free(slashClassName);
    
            mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
        }
    
    

    继续看下xposed的onVmCreate方法:

    /** Load the libxposed_*.so library for the currently active runtime. */
    void onVmCreated(JNIEnv* env) {
        // Determine the currently active runtime
        const char* xposedLibPath = NULL;
      // 根据是art还是dalvik加载不同的so
        if (!determineRuntime(&xposedLibPath)) {
            ALOGE("Could not determine runtime, not loading Xposed");
            return;
        }
    
        // Load the suitable libxposed_*.so for it
        void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
        if (!xposedLibHandle) {
            ALOGE("Could not load libxposed: %s", dlerror());
            return;
        }
    
        // Clear previous errors
        dlerror();
    
        // Initialize the library
        bool (*xposedInitLib)(XposedShared* shared) = NULL;
        *(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
        if (!xposedInitLib)  {
            ALOGE("Could not find function xposedInitLib");
            return;
        }
    
    #if XPOSED_WITH_SELINUX
        xposed->zygoteservice_accessFile = &service::membased::accessFile;
        xposed->zygoteservice_statFile   = &service::membased::statFile;
        xposed->zygoteservice_readFile   = &service::membased::readFile;
    #endif  // XPOSED_WITH_SELINUX
    
    // 初始化so
        if (xposedInitLib(xposed)) {
            xposed->onVmCreated(env);
        }
    }
    

    目前大部分都是art虚拟机了,我们看下art的xposedInitLib:

    /** Called by Xposed's app_process replacement. */
    bool xposedInitLib(XposedShared* shared) {
        xposed = shared;
        xposed->onVmCreated = &onVmCreatedCommon;
        return true;
    }
    

    那接下来就是调用onVmCreatedCommon:

    
    void onVmCreatedCommon(JNIEnv* env) {
        if (!initXposedBridge(env) || !initZygoteService(env)) {
            return;
        }
    
     // 这儿就是将上面两个init获取的method id保存起来
        if (!onVmCreated(env)) {
            return;
        }
    
        xposedLoadedSuccessfully = true;
        return;
    }
    

    终于看到初始化操作了,先看下initXposedBridge:

    bool initXposedBridge(JNIEnv* env) {
        classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
        if (classXposedBridge == NULL) {
            ALOGE("Error while loading Xposed class '%s':", CLASS_XPOSED_BRIDGE);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
        classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
    
      // 注册jni 方法
        ALOGI("Found Xposed class '%s', now initializing", CLASS_XPOSED_BRIDGE);
        if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) {
            ALOGE("Could not register natives for '%s'", CLASS_XPOSED_BRIDGE);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
      
     // 保存handleHookedMethod的method id
        methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
            "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
        if (methodXposedBridgeHandleHookedMethod == NULL) {
            ALOGE("ERROR: could not find method %s.handleHookedMethod(Member, int, Object, Object, Object[])", CLASS_XPOSED_BRIDGE);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
    
        return true;
    }
    

    注册了哪些方法呢?

    int register_natives_XposedBridge(JNIEnv* env, jclass clazz) {
        const JNINativeMethod methods[] = {
            NATIVE_METHOD(XposedBridge, hadInitErrors, "()Z"),
            NATIVE_METHOD(XposedBridge, getStartClassName, "()Ljava/lang/String;"),
            NATIVE_METHOD(XposedBridge, getRuntime, "()I"),
            NATIVE_METHOD(XposedBridge, startsSystemServer, "()Z"),
            NATIVE_METHOD(XposedBridge, getXposedVersion, "()I"),
            NATIVE_METHOD(XposedBridge, initXResourcesNative, "()Z"),
            NATIVE_METHOD(XposedBridge, hookMethodNative, "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"),
            NATIVE_METHOD(XposedBridge, setObjectClassNative, "(Ljava/lang/Object;Ljava/lang/Class;)V"),
            NATIVE_METHOD(XposedBridge, dumpObjectNative, "(Ljava/lang/Object;)V"),
            NATIVE_METHOD(XposedBridge, cloneToSubclassNative, "(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;"),
            NATIVE_METHOD(XposedBridge, removeFinalFlagNative, "(Ljava/lang/Class;)V"),
    #if PLATFORM_SDK_VERSION >= 21
            NATIVE_METHOD(XposedBridge, invokeOriginalMethodNative,
                "!(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
            NATIVE_METHOD(XposedBridge, closeFilesBeforeForkNative, "()V"),
            NATIVE_METHOD(XposedBridge, reopenFilesAfterForkNative, "()V"),
    #endif
    #if PLATFORM_SDK_VERSION >= 24
            NATIVE_METHOD(XposedBridge, invalidateCallersNative, "([Ljava/lang/reflect/Member;)V"),
    #endif
        };
        return env->RegisterNatives(clazz, methods, NELEM(methods));
    }
    

    这几个就是我们hook的时候会调用的几个方法,尤其是hookMethodNative
    继续看下initZygoteService:

    bool initZygoteService(JNIEnv* env) {
      // de/robv/android/xposed/services/ZygoteService
        jclass zygoteServiceClass = env->FindClass(CLASS_ZYGOTE_SERVICE);
        if (zygoteServiceClass == NULL) {
            ALOGE("Error while loading ZygoteService class '%s':", CLASS_ZYGOTE_SERVICE);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
      // 还是注册方法,不过不影响主要流程,可以简单带过
        if (register_natives_ZygoteService(env, zygoteServiceClass) != JNI_OK) {
            ALOGE("Could not register natives for '%s'", CLASS_ZYGOTE_SERVICE);
            env->ExceptionClear();
            return false;
        }
    
        classFileResult = env->FindClass(CLASS_FILE_RESULT);
        if (classFileResult == NULL) {
            ALOGE("Error while loading FileResult class '%s':", CLASS_FILE_RESULT);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
        classFileResult = reinterpret_cast<jclass>(env->NewGlobalRef(classFileResult));
    
        constructorFileResult = env->GetMethodID(classFileResult, "<init>", "(JJ)V");
        if (constructorFileResult == NULL) {
            ALOGE("ERROR: could not find constructor %s(long, long)", CLASS_FILE_RESULT);
            logExceptionStackTrace();
            env->ExceptionClear();
            return false;
        }
    
        return true;
    }
    

    到了现在,应用启动是完成了。
    我们再看下调用的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, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
    
            return XposedBridge.hookMethod(m, callback);
        }
    
    

    找到方法,然后执行hookMathod, 只要有classloader,找类用反射就可以了。看下hookMethod:

    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: " + hookMethod.toString());
            } else if (hookMethod.getDeclaringClass().isInterface()) {
                throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
            } else if (Modifier.isAbstract(hookMethod.getModifiers())) {
                throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
            }
    
            boolean newMethod = false;
            CopyOnWriteSortedSet<XC_MethodHook> callbacks;
            synchronized (sHookedMethodCallbacks) {
                callbacks = sHookedMethodCallbacks.get(hookMethod);
                if (callbacks == null) {
                    callbacks = new CopyOnWriteSortedSet<>();
                    sHookedMethodCallbacks.put(hookMethod, callbacks);
                    newMethod = true;
                }
            }
            callbacks.add(callback);
    
            if (newMethod) {
                Class<?> declaringClass = hookMethod.getDeclaringClass();
                int slot;
                Class<?>[] parameterTypes;
                Class<?> returnType;
                if (runtime == RUNTIME_ART) {
                    slot = 0;
                    parameterTypes = null;
                    returnType = null;
                } else if (hookMethod instanceof Method) {
                    slot = getIntField(hookMethod, "slot");
                    parameterTypes = ((Method) hookMethod).getParameterTypes();
                    returnType = ((Method) hookMethod).getReturnType();
                } else {
                    slot = getIntField(hookMethod, "slot");
                    parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
                    returnType = null;
                }
    
                AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
                hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
            }
    
            return callback.new Unhook(hookMethod);
        }
    

    这儿就是获取这个method的类,参数等信息,然后跳到native:

    void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
                jobject, jint, jobject javaAdditionalInfo) {
        // Detect usage errors.
        ScopedObjectAccess soa(env);
        if (javaReflectedMethod == nullptr) {
    #if PLATFORM_SDK_VERSION >= 23
            ThrowIllegalArgumentException("method must not be null");
    #else
            ThrowIllegalArgumentException(nullptr, "method must not be null");
    #endif
            return;
        }
    
        // Get the ArtMethod of the method to be hooked.
        ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
    
        // Hook the method
        artMethod->EnableXposedHook(soa, javaAdditionalInfo);
    }
    

    这儿就是直接将回调设置给art虚拟机了。

    void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
      if (UNLIKELY(IsXposedHookedMethod())) {
        // Already hooked
        return;
      } else if (UNLIKELY(IsXposedOriginalMethod())) {
        // This should never happen
        ThrowIllegalArgumentException(StringPrintf("Cannot hook the method backup: %s", PrettyMethod(this).c_str()).c_str());
        return;
      }
    
      // Create a backup of the ArtMethod object
      auto* cl = Runtime::Current()->GetClassLinker();
      auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
      ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
      backup_method->CopyFrom(this, cl->GetImagePointerSize());
      backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);
    
      // Create a Method/Constructor object for the backup ArtMethod object
      mirror::AbstractMethod* reflected_method;
      if (IsConstructor()) {
        reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
      } else {
        reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
      }
      reflected_method->SetAccessible<false>(true);
    
      // Save extra information in a separate structure, stored instead of the native method
      XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
      hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
      hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
      hook_info->original_method = backup_method;
    
      ScopedThreadSuspension sts(soa.Self(), kSuspended);
      jit::ScopedJitSuspend sjs;
      gc::ScopedGCCriticalSection gcs(soa.Self(),
                                      gc::kGcCauseXposed,
                                      gc::kCollectorTypeXposed);
      ScopedSuspendAll ssa(__FUNCTION__);
    
      cl->InvalidateCallersForMethod(soa.Self(), this);
    
      jit::Jit* jit = art::Runtime::Current()->GetJit();
      if (jit != nullptr) {
        jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
      }
    
      SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
      SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
      SetCodeItemOffset(0);
    
      // Adjust access flags.
      const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
      SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);
    
      MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
      Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);
    }
    

    这儿就是告诉虚拟机在解释这个方法的时候,走我们自己定义的方法。

    到了这儿基本流程就介绍完了。

    相关文章

      网友评论

          本文标题:Xposed 配置,使用以及原理介绍

          本文链接:https://www.haomeiwen.com/subject/gpacmrtx.html