聊聊 Xposed

前言

本文涉及的 Android 源碼部分基于 Android 10

Xposed

在聊 Xposed 前我們先簡單在腦海里回顧下 Zygote 進(jìn)程的啟動(dòng)流程,如果記不清的可以先看一看 Android Framework 之 Zygote

入口函數(shù)的差異

Zygote 進(jìn)程 native 層入口函數(shù)為 frameworks/base/cmds/app_process/app_main.cpp,而在 Xposed 項(xiàng)目中也有 app_main.cpp病曾。

image.png

Xposed 的這個(gè)文件有什么用呢?

其實(shí)在 recovery 模式下刷 Xposed 時(shí)漾根,它會(huì)用 app_main.cppapp_main2.cpp 替換系統(tǒng)的 app_process/app_main.cpp泰涂,在 Android.mk 中有如下代碼。

// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
  LOCAL_SRC_FILES := app_main2.cpp
else
  LOCAL_SRC_FILES := app_main.cpp
endif

即版本高于 21 時(shí)使用 app_main2.cpp辐怕,否則用 app_main.cpp逼蒙。

來看看 app_main2.cpp 有哪些主要的改動(dòng)。

// app_process/app_main.cpp
if (zygote) {
    runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
    runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
}

// app_main2.cpp
if (zygote) {
    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) {
    isXposedLoaded = xposed::initialize(false, false, className, argc, argv);
    runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote);
}

可以看到寄疏,主要有如下差異:

  1. 在啟動(dòng) Zygote 進(jìn)程或孵化應(yīng)用進(jìn)程時(shí)會(huì)先執(zhí)行 xposed::initialize() 進(jìn)行初始化是牢;
  2. 執(zhí)行 runtimeStart(),注意這里的 XPOSED_CLASS_DOTS_ZYGOTEXPOSED_CLASS_DOTS_TOOLS陕截。

xposed::initalize()

先看看 xposed::initialize()驳棱,源碼在 xposed.hxposed.cpp

// xposed.h
#define XPOSED_JAR               "/system/framework/XposedBridge.jar"

// xposed.cpp
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
    // ...
    return addJarToClasspath();
}

bool addJarToClasspath() {
    //...
    if (access(XPOSED_JAR, R_OK) == 0) {
        // 把 XposedBridge.jar 添加到 classPath
        if (!addPathToEnv("CLASSPATH", XPOSED_JAR))
            return false;
        return true;
    }
}

總的來說 xposed::initialize() 會(huì)把 XposedBridge.jar 添加到 classpath农曲,這樣 Zygote 進(jìn)程孵化的應(yīng)用進(jìn)程就都具備了 XposedBridge.jar 的代碼社搅。

執(zhí)行 runtimeStart()

來看看 runtimeStart()

// app_main2.cpp
static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector<String8>& options, bool zygote)
{
#if PLATFORM_SDK_VERSION >= 23
  runtime.start(classname, options, zygote);
#else
  void (*ptr1)(AppRuntime&, const char*, const Vector<String8>&, bool);
  *(void **) (&ptr1) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb");

  if (ptr1 != NULL) {
    ptr1(runtime, classname, options, zygote);
    return;
  }

  void (*ptr2)(AppRuntime&, const char*, const Vector<String8>&);
  *(void **) (&ptr2) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE");

  if (ptr2 != NULL) {
    ptr2(runtime, classname, options);
    return;
  }
#endif
}

做了版本判斷乳规,這里最終還是會(huì)調(diào)用 AndroidRuntimestart()形葬,了解 Zygote 進(jìn)程啟動(dòng)流程的應(yīng)該知道這里主要做了三件事,創(chuàng)建虛擬機(jī)暮的,注冊(cè) JNI荷并,通過反射調(diào)用 classnamemain()

創(chuàng)建虛擬機(jī)

Xposed 在創(chuàng)建虛擬機(jī)之后做了一些修改青扔,先看看 AndroidRuntimeonVmCreated()源织。

// app_main2.cpp
class AppRuntime : public AndroidRuntime
{
    virtual void onVmCreated(JNIEnv* env)
    {
        if (isXposedLoaded)
            xposed::onVmCreated(env);
    }
}

這里調(diào)用 xposed::onVmCreated(),其源碼在 xposed.cpp微猖。

// xposed.cpp
void onVmCreated(JNIEnv* env) {
    // 加載 libxposed_art.so
    void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);

    // 初始化 xposed 相關(guān) library
    bool (*xposedInitLib)(XposedShared* shared) = NULL;
    *(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
    if (xposedInitLib(xposed)) {
        // 執(zhí)行 onVmCreated(env)
        xposed->onVmCreated(env);
    }
}

這里通過 dlopen() 函數(shù)加載 libxposed_art.so谈息,在 Android.mk 中有如下定義。

// Android.mk
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21)))
  include frameworks/base/cmds/xposed/ART.mk
endif

使用 ART.mk 生成 libxposed_art.so凛剥。

// ART.mk
LOCAL_SRC_FILES += \
  libxposed_common.cpp \
  libxposed_art.cpp

LOCAL_MODULE := libxposed_art

libxposed_art.so 加載后侠仇,接著會(huì)初始化 xposed 相關(guān) library,然后調(diào)用 xposedInitLib(),其源碼在 libxposed_art.cpp逻炊。

// libxposed_art.cpp
bool xposedInitLib(XposedShared* shared) {
    xposed = shared;
    xposed->onVmCreated = &onVmCreatedCommon;
    return true;
}

這里進(jìn)行賦值互亮,即 xposed->onVmCreated(env) 會(huì)執(zhí)行 onVmCreatedCommon(),其源碼在 libxposed_common.cpp余素。

// libxposed_common.cpp
void onVmCreatedCommon(JNIEnv* env) {
    if (!initXposedBridge(env) || !initZygoteService(env)) {
        return;
    }

    if (!onVmCreated(env)) {
        return;
    }

    xposedLoadedSuccessfully = true;
    return;
}

initXposedBridge() 會(huì)初始化 XposedBridge.java豹休,initZygoteService() 會(huì)初始化 ZygoteService.java,主要看看 initXposedBridge()桨吊。

bool initXposedBridge(JNIEnv* env) {
    classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
    classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
    // 這里會(huì)注冊(cè) native 函數(shù)
    if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) {
        return false;
    }

    // 這里會(huì)獲取到 XposedBridge 的 handleHookedMethod
    methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
        "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
    return true;
}

// 注冊(cè) native 函數(shù)
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));
}

這里主要是注冊(cè) native 函數(shù)威根,并獲取 handleHookedMethod() 賦值給 methodXposedBridgeHandleHookedMethod

總的來說這里主要是加載 libxposed_art.so视乐,并初始化一些 library洛搀,然后注冊(cè) native 函數(shù)。

反射調(diào)用 XPOSED_CLASS_DOTS_ZYGOTE 的 main()

最終會(huì)通過反射調(diào)用 XPOSED_CLASS_DOTS_ZYGOTEmain()佑淀,其定義位于 xposed.h留美。

#define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge"

即最終會(huì)執(zhí)行 de.robv.android.xposed.XposedBridge.main()。它是 XposedBridge.jar 中的類伸刃,源碼位于 XposedBridge 項(xiàng)目独榴,來看看其 main()

protected static void main(String[] args) {
    // 1. 初始化 Xposed framework 和 Xposed module(也就是我們平常寫的 Xposed 插件)
    try {
        if (!hadInitErrors()) {
            initXResources();

            SELinuxHelper.initOnce();
            SELinuxHelper.initForProcess(null);

            runtime = getRuntime();
            XPOSED_BRIDGE_VERSION = getXposedVersion();

            if (isZygote) {
                // hook 各版本資源獲取與創(chuàng)建
                XposedInit.hookResources();
                // hook 系統(tǒng)關(guān)鍵方法
                XposedInit.initForZygote();
            }
            // 加載 Xposed Modules
            XposedInit.loadModules();
        }
    } catch (Throwable t) {
        disableHooks = true;
    }

    // 2. 原 Zygote 和 應(yīng)用進(jìn)程 啟動(dòng)流程
    if (isZygote) {
        ZygoteInit.main(args);
    } else {
        RuntimeInit.main(args);
    }
}

這里主要做了如下事情:

  1. 調(diào)用 XposedInit.hookResources()hook 系統(tǒng)資源相關(guān)方法奕枝;
  2. 調(diào)用 XposedInit.initForZygote()hook Zygote 相關(guān)方法;
  3. 調(diào)用 XposedInit.loadModules() 來加載 Xposed Module瓶堕。

這里說下 loadModules()隘道,來看看 loadModules() 源碼。

private static final String INSTALLER_PACKAGE_NAME = "de.robv.android.xposed.installer";
private static final String BASE_DIR = Build.VERSION.SDK_INT >= 24
        ? "/data/user_de/0/" + INSTALLER_PACKAGE_NAME + "/"
        : "/data/data/" + INSTALLER_PACKAGE_NAME + "/";

static void loadModules() throws IOException {
    final String filename = BASE_DIR + "conf/modules.list";

    ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
    ClassLoader parent;
    while ((parent = topClassLoader.getParent()) != null) {
        topClassLoader = parent;
    }

    // 讀安裝目錄的 conf/modules.list 文件
    InputStream stream = service.getFileInputStream(filename);
    BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
    String apk;
    while ((apk = apks.readLine()) != null) {
        // 調(diào)用 loadModule() 加載 xposed module 
        loadModule(apk, topClassLoader);
    }
    apks.close();
}

這里會(huì)加載安裝目錄下的 conf/modules.list 文件郎笆,然后對(duì)每個(gè) xposed module 調(diào)用 loadModule() 來進(jìn)行注冊(cè)谭梗。

private static void loadModule(String apk, ClassLoader topClassLoader) {
    DexFile dexFile;
    try {
        dexFile = new DexFile(apk);
    } catch (IOException e) {
        return;
    }

    // 禁止 Android Studio 的 instant run
    if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
        Log.e(TAG, "  Cannot load module, please disable \"Instant Run\" in Android Studio.");
        closeSilently(dexFile);
        return;
    }

    // 注意 Xposed Module 對(duì) xposed 庫的依賴是 compileOnly,不會(huì)被打入到 apk 中
    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);

    // 讀取 assets/xposed_init 配置文件
    ZipFile zipFile = null;
    InputStream is;
    try {
        zipFile = new ZipFile(apk);
        ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
        // 找不到 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) {
        closeSilently(zipFile);
        return;
    }

    // 找注冊(cè)類
    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 {
                Class<?> moduleClass = mcl.loadClass(moduleClassName);

                if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
                    continue;
                } else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
                    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));
                }
            } catch (Throwable t) {
            }
        }
    } catch (IOException e) {
    } finally {
        closeSilently(is);
        closeSilently(zipFile);
    }
}

這里會(huì)解析 assets/xposed_init 配置文件宛蚓,然后對(duì)配置中聲明的類進(jìn)行注冊(cè)激捏。

方法如何被 hook

Xposed Module 中,想要 hook 某個(gè)方法可以通過 XposedHelpers.findAndHookMethod()凄吏,來看看其源碼远舅。

public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
    return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}

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");

    // 就是 findAndHookMethod() 的最后一個(gè)參數(shù)
    XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
    // 反射找方法
    Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
    // 調(diào)用 XposedBridge.hookMethod()
    return XposedBridge.hookMethod(m, callback);
}

來看看 XposedBridge.hookMethod()

public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
        // 方法是否滿足 hook 條件
    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<XC_MethodHook>();
            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);
        // 調(diào)用 native 方法 
        hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
    }

    return callback.new Unhook(hookMethod);
}

從這里可以看到痕钢,一個(gè)方法能否被 hook图柏,必須滿足:

  1. 是普通的方法或構(gòu)造器;
  2. 不是接口方法任连;
  3. 不是抽象方法蚤吹。

最終執(zhí)行 hook 操作的是 native 方法 hookMethodNative(),其源碼位于 libxposed_art.cpp

void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
            jobject, jint, jobject javaAdditionalInfo) {
    // 把 java method 轉(zhuǎn)換成 ArtMethod
    ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);

    // 這里是真正的 hook 邏輯
    artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}

這里將 JavaMethod 轉(zhuǎn)為 ArtMethod,然后調(diào)用 EnableXposedHook()裁着,這里是最終做 hook 的地方繁涂,其源碼位于 android_artart_method.cc

void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
  // 1. 備份
  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);

  // 2. 創(chuàng)建備份方法的反射對(duì)象
  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);

  // 3. 將信息存放到結(jié)構(gòu)體中
  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;

  // 4. 準(zhǔn)備工作二驰,處理函數(shù)的JIT即時(shí)編譯以及其他
  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);
  }

  // 5. 設(shè)置被 hook 函數(shù)的入口點(diǎn)(關(guān)鍵的 hook 邏輯)
  // 將 hook 信息存到 entry_point_from_jni 這個(gè)指針
  SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
  // 設(shè)替換函數(shù)入口點(diǎn) entry_point_from_quick_compiled_code_ 為自己的 art_quick_proxy_invoke_handler
  SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
  // 設(shè)置函數(shù)在 CodeItem 偏移
  SetCodeItemOffset(0);

  // 更改屬性并添加 kAccXposedHookedMethod 標(biāo)記
  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);
}

這里面涉及太多 art 相關(guān)知識(shí)扔罪,暫時(shí)沒能力細(xì)究,簡單說一下诸蚕,其實(shí)就是找到被 hook 函數(shù)的地址值步势,然后替換成另外一個(gè)函數(shù),這樣當(dāng)函數(shù)執(zhí)行時(shí)會(huì)先執(zhí)行 callbackbeforeHookedMethod()背犯,然后執(zhí)行被 hook 函數(shù)坏瘩,最后執(zhí)行 callbackafterHookedMethod()

Xposed Module

下面再說說如何編寫一個(gè) Xposed Module漠魏,編寫 Xposed Module 其實(shí)只需要四步即可倔矾。

添加對(duì) Xposed 庫的依賴

app/build.gradle 中添加對(duì) Xposed 庫的依賴。

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

注意這里依賴的方式為 compileOnly柱锹,不知道為什么的往前看 loadModule()哪自。

增加 Xposed 配置

AndroidManifest.xml 中添加 Xposed 相關(guān)配置。

<!--  標(biāo)志該 apk 為一個(gè) Xposed 模塊禁熏,供 Xposed 框架識(shí)別-->
<meta-data
    android:name="xposedmodule"
    android:value="true" />

<!--模塊說明壤巷,一般為模塊的功能描述-->
<meta-data
    android:name="xposeddescription"
    android:value="這個(gè)模塊是用來檢測(cè)用戶隱私合規(guī)的,在用戶未授權(quán)同意前瞧毙,調(diào)用接口獲取信息屬于違規(guī)" />

<!--模塊兼容版本-->
<meta-data
    android:name="xposedminversion"
    android:value="54" />

編寫 hook 插件

一般情況下會(huì)編寫類實(shí)現(xiàn) IXposedHookLoadPackage胧华,并在 handleLoadPackage() 做相關(guān)操作。

public class HookTrack implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
        // ... 在這里進(jìn)行相關(guān)操作
    }

增加 xposed_init 配置

assets 目錄下新增 xposed_init 配置文件宙彪,并添加插件類的全路徑聲明矩动。

image.png

這樣 Xposed Module 就編寫完了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末释漆,一起剝皮案震驚了整個(gè)濱河市悲没,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌男图,老刑警劉巖示姿,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逊笆,居然都是意外死亡峻凫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門览露,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荧琼,“玉大人,你說我怎么就攤上這事∶” “怎么了堰乔?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脐恩。 經(jīng)常有香客問我镐侯,道長,這世上最難降的妖魔是什么驶冒? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任苟翻,我火速辦了婚禮,結(jié)果婚禮上骗污,老公的妹妹穿的比我還像新娘崇猫。我一直安慰自己,他們只是感情好需忿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布诅炉。 她就那樣靜靜地躺著,像睡著了一般屋厘。 火紅的嫁衣襯著肌膚如雪涕烧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天汗洒,我揣著相機(jī)與錄音议纯,去河邊找鬼。 笑死溢谤,一個(gè)胖子當(dāng)著我的面吹牛瞻凤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溯香,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浓恶!你這毒婦竟也來了玫坛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤包晰,失蹤者是張志新(化名)和其女友劉穎湿镀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伐憾,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勉痴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了树肃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒸矛。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雏掠,到底是詐尸還是另有隱情斩祭,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布乡话,位于F島的核電站摧玫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绑青。R本人自食惡果不足惜诬像,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闸婴。 院中可真熱鬧坏挠,春花似錦、人聲如沸掠拳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溺欧。三九已至喊熟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姐刁,已是汗流浹背芥牌。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聂使,地道東北人壁拉。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像柏靶,于是被迫代替她去往敵國和親弃理。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355