首页
社区
课程
招聘
Xposed ART原理分析笔记
2021-4-15 18:00 13429

Xposed ART原理分析笔记

2021-4-15 18:00
13429

目录


Xposed ART原理分析笔记

0. 写在前面

这篇文章是我在看Xposed源码时所做的一些笔记内容,中间也参考了一些前人的分析文章这里都在最后列出了,除此之外还有一些内容纯属个人理解,如果有错误欢迎各位大佬斧正,感谢!

1. Xposed结构

Xposed 相关源码: https://github.com/rovo89

  1. XposedBridge: Xposed 提供的 jar 文件,app_process 启动过程中会加载该 jar 包,其 他的 Modules 的开发都是基于该 jar 包;
  2. Xposed: Xposed 的 C++ 部 分 , 主 要 是 用 来 替 换 /system/bin/app_process , 并 为 XposedBridge 提供 JNI 方法;
  3. XposedInstaller: Xposed 的安装包,提供对基于 Xposed 框架的 Modules 的管理;
  4. android_art: Xposed修改的art部分源码

2. Xposed 注入进程

Xposed注入进程的方式是通过zygote进程在forkAPP进程时使之加载XposedBridge.jar

 

根据XposedAndroid.mk文件可知在5.0以上的手机上编译的是app_main2.cpp

1
2
3
4
5
6
7
8
9
ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) # if Android_Version >= 5.0
  LOCAL_SRC_FILES := app_main2.cpp
  LOCAL_MULTILIB := both
  LOCAL_MODULE_STEM_32 := app_process32_xposed
  LOCAL_MODULE_STEM_64 := app_process64_xposed
else
  LOCAL_SRC_FILES := app_main.cpp
  LOCAL_MODULE_STEM := app_process_xposed
endif

这里选取Android 7.1.1_r6app_main.cpp文件与app_main2.cpp做对比

 

main函数看起

 

图片描述

 

从文件的对比可以发现,app_main2.cpp中多出了对于 Xposed::handleOptions的调用,而这个函数主要是对--xposedversion的处理以及是否测试模式的启用,真正运行时无需理会

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
bool handleOptions(int argc, char* const argv[]) {
    // version 信息
    parseXposedProp();
 
    if (argc == 2 && strcmp(argv[1], "--xposedversion") == 0) {
        printf("Xposed version: %s\n", xposedVersion);
        return true;
    }
 
    if (argc == 2 && strcmp(argv[1], "--xposedtestsafemode") == 0) {
        printf("Testing Xposed safemode trigger\n");
 
        if (detectSafemodeTrigger(shouldSkipSafemodeDelay())) {
            printf("Safemode triggered\n");
        } else {
            printf("Safemode not triggered\n");
        }
        return true;
    }
 
    // From Lollipop coding, used to override the process name
    argBlockStart = argv[0];
    uintptr_t start = reinterpret_cast<uintptr_t>(argv[0]);
    uintptr_t end = reinterpret_cast<uintptr_t>(argv[argc - 1]);
    end += strlen(argv[argc - 1]) + 1;
    argBlockLength = end - start;
 
    return false;
}

main函数的最后,增加了对Xposed的加载与runtimeStart函数的调用。

 

图片描述

 

initialize函数最终返回XposedBridge.jar是否成功加入环境变量的标志和XposedInstaller是否成功运行的标志

 

首先对xposed这个结构体变量进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct XposedShared {
    // Global variables
    bool zygote;
    bool startSystemServer;
    const char* startClassName;
    uint32_t xposedVersionInt;
    bool isSELinuxEnabled;
    bool isSELinuxEnforcing;
    uid_t installer_uid;
    gid_t installer_gid;
 
    // Provided by runtime-specific library, used by executable
    void (*onVmCreated)(JNIEnv* env);
 
#if XPOSED_WITH_SELINUX
    // Provided by the executable, used by runtime-specific library
    int (*zygoteservice_accessFile)(const char* path, int mode);
    int (*zygoteservice_statFile)(const char* path, struct stat* st);
    char* (*zygoteservice_readFile)(const char* path, int* bytesRead);
#endif
};
XposedShared* xposed = new XposedShared;   
xposed->zygote = zygote;
xposed->startSystemServer = startSystemServer;
xposed->startClassName = className;
xposed->xposedVersionInt = xposedVersionInt;
 
#if XPOSED_WITH_SELINUX
    xposed->isSELinuxEnabled   = is_selinux_enabled() == 1;
    xposed->isSELinuxEnforcing = xposed->isSELinuxEnabled && security_getenforce() == 1;
#else
    xposed->isSELinuxEnabled   = false;
    xposed->isSELinuxEnforcing = false;
#endif  // XPOSED_WITH_SELINUX

然后在logcat中通过printStartupMarker函数和printRomInfo函数打印一些信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void printStartupMarker() {
    sprintf(marker, "Current time: %d, PID: %d", (int) time(NULL), getpid());
    ALOG(LOG_DEBUG, "XposedStartupMarker", marker, NULL);
}
void printRomInfo() {
    char release[PROPERTY_VALUE_MAX];
    char sdk[PROPERTY_VALUE_MAX];
    char manufacturer[PROPERTY_VALUE_MAX];
    char model[PROPERTY_VALUE_MAX];
    char rom[PROPERTY_VALUE_MAX];
    char fingerprint[PROPERTY_VALUE_MAX];
    char platform[PROPERTY_VALUE_MAX];
#if defined(__LP64__)
    const int bit = 64;
#else
    const int bit = 32;
#endif
 
    property_get("ro.build.version.release", release, "n/a");
    property_get("ro.build.version.sdk", sdk, "n/a");
    property_get("ro.product.manufacturer", manufacturer, "n/a");
    property_get("ro.product.model", model, "n/a");
    property_get("ro.build.display.id", rom, "n/a");
    property_get("ro.build.fingerprint", fingerprint, "n/a");
    property_get("ro.product.cpu.abi", platform, "n/a");
 
    ALOGI("-----------------");
    ALOGI("Starting Xposed version %s, compiled for SDK %d", xposedVersion, PLATFORM_SDK_VERSION);
    ALOGI("Device: %s (%s), Android version %s (SDK %s)", model, manufacturer, release, sdk);
    ALOGI("ROM: %s", rom);
    ALOGI("Build fingerprint: %s", fingerprint);
    ALOGI("Platform: %s, %d-bit binary, system server: %s", platform, bit, xposed->startSystemServer ? "yes" : "no");
    if (!xposed->zygote) {
        ALOGI("Class name: %s", xposed->startClassName);
    }
    ALOGI("SELinux enabled: %s, enforcing: %s",
            xposed->isSELinuxEnabled ? "yes" : "no",
            xposed->isSELinuxEnforcing ? "yes" : "no");
}

然后以下函数通过启动XposedInstaller和相应Xposed服务

1
2
3
if (!determineXposedInstallerUidGid() || !xposed::service::startAll()) {
            return false;
}

最后如果XPosed能够正常加载,那么就通过函数addJarToClasspathXposedBridge.jar文件加入环境变量

 

这里如果想要禁用Xposed只需要在XposedInstaller的私有目录下创建一个conf/disabled文件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** Create a flag file to disable Xposed. */
#if PLATFORM_SDK_VERSION >= 24
#define XPOSED_DIR "/data/user_de/0/de.robv.android.xposed.installer/"
#else
#define XPOSED_DIR "/data/data/de.robv.android.xposed.installer/"
#endif
 
#define XPOSED_LOAD_BLOCKER      XPOSED_DIR "conf/disabled"
void disableXposed() {
    int fd;
    // FIXME add a "touch" operation to xposed::service::membased
    fd = open(XPOSED_LOAD_BLOCKER, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd >= 0)
        close(fd);
}

如果成功加载则调用runtimeStart函数对调用AndroidRuntime::start()函数对de.robv.android.xposed.XposedBridge类进行启动

1
2
// XPOSED_CLASS_DOTS_ZYGOTE =  de.robv.android.xposed.XposedBridge
runtimeStart(runtime, isXposedLoaded ? "de.robv.android.xposed.XposedBridge" : "com.android.internal.os.ZygoteInit", args, zygote);

AndroidRuntime::start()函数经过观察发现实际上完成了几个工作

 

1.startVm函数,启动虚拟机

1
2
3
4
5
6
7
8
/* start the virtual machine */
  JniInvocation jni_invocation;
  jni_invocation.Init(NULL);
  JNIEnv* env;
  if (startVm(&mJavaVM, &env, zygote) != 0) {
      return;
  }
  onVmCreated(env);

2.反射调用传入的类的main函数作为虚拟机的入口点

 

图片描述

 

Xposed就是通过替换这个className来达到加载自己的art虚拟机的效果。这样就成功注入了XposedBridge.jar文件,达到任意APP在加载时内存空间中总会有XposedBridge.jar

 

XposedBridge.jar文件的main函数如下,其实就是一个对SELinux的处理以及对Xposed模块的加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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();
            } 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);
        }
    }

对模块的加载是通过对XposedInstaller私有目录下的conf/modules.list文件进行读取,并分别是通过BOOTCLASSLOADER对模块进行加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*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();
    }

在最终的loadModule函数中实际上是通过DexFile的构造函数将模块加载并通过dexFile.loadClass函数的方式对入口类进行加载。最终达到模块注入的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void loadModule(String apk, ClassLoader topClassLoader) {       
  ...
        DexFile dexFile;
        try {
            dexFile = new DexFile(apk);
        } catch (IOException e) {
            Log.e(TAG, "  Cannot load module", e);
            return;
        }
        // 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;
        }
 
        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;
        }
  ...
}

3. Xposed hook函数

我们知道Xposed hook函数的方式是大致是通过以下模版完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class XposedHook implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
            Class clasz = loadPackageParam.classLoader.loadClass("xxxx"); //要hook的方法所在的类名
 
            XposedHelpers.findAndHookMethod(clazz, "xxx",String.class,String.class,String.class, new XC_MethodHook() { //要hook的方法名和参数类型,此处为三个String类型
 
                @Override //重写XC_MethodHook()的回调方法
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
 
 
                }
 
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Log.i("hook after result:",param.getResult().toString()); //打印返回值(String类型)
                }
            });
        }
    }
}

其中最关键的执行hook逻辑的函数实际上是XposedHelpers.findAndHookMethod()函数

 

我们从这个函数跟踪起

 

图片描述

 

观察这个函数分为两个部分

  1. 找到回调CallBackhook的函数
  2. 调用XposedBridge.hookMethod()函数完成hook

第一部分,用于获取回调的部分实际上就是获取参数的最后一个值,而用于寻找hook对应函数的findMethodExact函数,如下所示实际上就是通过反射获取,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
        // 缓存机制
        if (methodCache.containsKey(fullMethodName)) {
            Method method = methodCache.get(fullMethodName);
            if (method == null)
                throw new NoSuchMethodError(fullMethodName);
            return method;
        }
 
        try {
      // 真实逻辑
            Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
            method.setAccessible(true);
            methodCache.put(fullMethodName, method);
            return method;
        } catch (NoSuchMethodException e) {
            methodCache.put(fullMethodName, null);
            throw new NoSuchMethodError(fullMethodName);
        }
    }

第二部分,执行hook逻辑的函数其内容主要分为三个部分

 

1.检查要hook的函数是否合法,必须同时满足三个条件:第一,是普通函数或者构造函数;第二,所在类不是接口Interface;第三,函数不是abstract抽象函数。

 

图片描述
2.从缓存中确认函数未被hook并将新函数加入缓存。

 

图片描述

 

3.执行真实hook逻辑,具体代码如下。可以发现真实执行hook逻辑的函数交给了hookMethodNative函数,而这个函数实际上是一个native属性的函数。

 

图片描述

 

而这个函数的C++实现在libxposed_art.cpp文件中。观察其函数发现,实际上就是将Java层的Method转换成了ArtMethod,然后通过ArtMethod类的EnableXposedHook函数执行hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
            jobject, jint, jobject javaAdditionalInfo) {
    // Detect usage errors.
    // ScopedObjectAccess:访问管理对象时候调用,一是将jobject加入LocalReference二是线程切换为kRunnable状态,即离开安全区。
    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);
}

EnableXposedHook函数的实现就在Xposed修改的art代码中了,打开android_art工程,找到对应实现runtime/art_method.cc文件。

 

具体实现分为几步

 

1.备份原先的Method并添加标记kAccXposedOriginalMethod

1
2
3
4
5
6
// 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);

2.创建一个备份方法对应的反射对象

1
2
3
4
5
6
7
8
// 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);

3.将所有相关内容保存为一个结构体存储

1
2
3
4
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.准备工作,处理函数的JIT即时编译以及其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// suspend线程
 ScopedThreadSuspension sts(soa.Self(), kSuspended);
 // 停止JIT即时编译
 jit::ScopedJitSuspend sjs;
// 防止死锁
 gc::ScopedGCCriticalSection gcs(soa.Self(),
                                 gc::kGcCauseXposed,
                                 gc::kCollectorTypeXposed);
// 用来暂停占用函数的所有线程
 ScopedSuspendAll ssa(__FUNCTION__);
 // 取消所有调用
 cl->InvalidateCallersForMethod(soa.Self(), this);
 
 // 去除JIT编译的结果
 jit::Jit* jit = art::Runtime::Current()->GetJit();
 if (jit != nullptr) {
   jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
 }

5.设置被hook函数的入口点(关键的hook逻辑)

1
2
3
4
5
6
7
8
9
10
11
// 将hook信息存到entry_point_from_jni这个指针
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
// 设替换函数入口点entry_point_from_quick_compiled_code_为自己的art_quick_proxy_invoke_handler
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
// 设置函数在CodeItem偏移
SetCodeItemOffset(0);
 
// Adjust access flags.
// 更改属性并添加kAccXposedHookedMethod标记
const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);

6.恢复环境

1
2
3
// 恢复线程
  MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
  Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);

这样就执行完成了函数的hook

4. 被hook的函数执行流程分析

当被hook的函数执行时,我们直接从ArtMethod->Invoke函数入手。

 

由于被hook的函数属性是一个native属性,观察函数中重要逻辑。由于在设置函数时已经设置过函数入口点为entry_point_from_quick_compiled_code_不为false则会进入art_quick_invoke_stub或者art_quick_invoke_static_stub跳板函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
                       const char* shorty) {
  ...
      bool have_quick_code = GetEntryPointFromQuickCompiledCode() != nullptr;
    if (LIKELY(have_quick_code)) {
      if (kLogInvocationStartAndReturn) {
        LOG(INFO) << StringPrintf(
            "Invoking '%s' quick code=%p static=%d", PrettyMethod(this).c_str(),
            GetEntryPointFromQuickCompiledCode(), static_cast<int>(IsStatic() ? 1 : 0));
      }
 
      // Ensure that we won't be accidentally calling quick compiled code when -Xint.
      if (kIsDebugBuild && runtime->GetInstrumentation()->IsForcedInterpretOnly()) {
        CHECK(!runtime->UseJitCompilation());
        const void* oat_quick_code = runtime->GetClassLinker()->GetOatMethodQuickCodeFor(this);
        CHECK(oat_quick_code == nullptr || oat_quick_code != GetEntryPointFromQuickCompiledCode())
            << "Don't call compiled code when -Xint " << PrettyMethod(this);
      }
 
      if (!IsStatic()) {
        (*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
      } else {
        (*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
      }
      ....
    }
    ...
}

这里以art_quick_invoke_stub非静态函数为例,最终不管是arm还是arm64都是以汇编实现的这个函数,只是arm在真实执行函数时有一些中间的跳板,最终实现函数为art_quick_invoke_stub_internal

 

arm为例,其实现函数所在文件为android_art/runtime/arch/arm/quick_entrypoints_arm.S,汇编中存在着一个ART_METHOD_QUICK_CODE_OFFSET_32这个函数的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ENTRY art_quick_invoke_stub_internal
    ....
 
#ifdef ARM_R4_SUSPEND_FLAG
    mov    r4, #SUSPEND_CHECK_INTERVAL     @ reset r4 to suspend check interval
#endif
 
    ldr    ip, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32]  @ get pointer to the code
    blx    ip                              @ call the method
 
    mov    sp, r11                         @ restore the stack pointer
    .cfi_def_cfa_register sp
 
    ldr    r4, [sp, #40]                   @ load result_is_float
    ldr    r9, [sp, #36]                   @ load the result pointer
    cmp    r4, #0
    ite    eq
    strdeq r0, [r9]                        @ store r0/r1 into result pointer
    vstrne d0, [r9]                        @ store s0-s1/d0 into result pointer
 
    pop    {r4, r5, r6, r7, r8, r9, r10, r11, pc}               @ restore spill regs
END art_quick_invoke_stub_internal

ART_METHOD_QUICK_CODE_OFFSET_32函数定义在runtime/asm_support.h文件中实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct PACKED(4) PtrSizedFields {
    // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
    ArtMethod** dex_cache_resolved_methods_;
 
    // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
    GcRoot<mirror::Class>* dex_cache_resolved_types_;
 
    // Pointer to JNI function registered to this method, or a function to resolve the JNI function,
    // or the profiling data for non-native methods, or an ImtConflictTable.
    void* entry_point_from_jni_;
 
    // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
    // the interpreter.
    void* entry_point_from_quick_compiled_code_;
 } ptr_sized_fields_;
static MemberOffset EntryPointFromQuickCompiledCodeOffset(size_t pointer_size) {
    return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER(
        PtrSizedFields, entry_point_from_quick_compiled_code_) / sizeof(void*) * pointer_size);
 }
#define ART_METHOD_QUICK_CODE_OFFSET_32 32
ADD_TEST_EQ(ART_METHOD_QUICK_CODE_OFFSET_32,
            art::ArtMethod::EntryPointFromQuickCompiledCodeOffset(4).Int32Value())

EntryPointFromQuickCompiledCodeOffset函数是一个获取PtrSizedFields结构体固定偏移的函数,这里就是获取的entry_point_from_quick_compiled_code_变量的值。

 

这里由于Xposed对这个函数进行了hook,实际上就是获取的art_quick_proxy_invoke_handler函数的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ENTRY art_quick_proxy_invoke_handler
    SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_R0
    mov     r2, r9                 @ pass Thread::Current
    mov     r3, sp                 @ pass SP
    @ 调用函数
    blx     artQuickProxyInvokeHandler  @ (Method* proxy method, receiver, Thread*, SP)
    ldr     r2, [r9, #THREAD_EXCEPTION_OFFSET]  @ load Thread::Current()->exception_
    // Tear down the callee-save frame. Skip arg registers.
    add     sp, #(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)
    .cfi_adjust_cfa_offset -(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)
    RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME
    cbnz    r2, 1f                 @ success if no exception is pending
    vmov    d0, r0, r1             @ store into fpr, for when it's a fpr return...
    bx      lr                     @ return on success
1:
    DELIVER_PENDING_EXCEPTION
END art_quick_proxy_invoke_handler

故最终在art_quick_invoke_stub_internal也就是调用的art_quick_proxy_invoke_handler函数。而在这个art_quick_proxy_invoke_handler函数中,又再次调用了artQuickProxyInvokeHandler函数。

 

artQuickProxyInvokeHandler函数中又看到了一堆xposed相关的信息,其具体实现在android_art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc

 

这里我梳理了一下Xposed相关的重要代码列出来,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
extern "C" uint64_t artQuickProxyInvokeHandler(
    ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp)
    SHARED_REQUIRES(Locks::mutator_lock_) {
  const bool is_xposed = proxy_method->IsXposedHookedMethod();
  ...
  if (is_xposed) {
    jmethodID proxy_methodid = soa.EncodeMethod(proxy_method);
    self->EndAssertNoThreadSuspension(old_cause);
    JValue result = InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args);
    local_ref_visitor.FixupReferences();
    return result.GetJ();
  }
}

观察发现实际上最重要的就是InvokeXposedHandleHookedMethod函数的调用,其具体内容主要分成三部分。

 

1.处理参数信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (args.size() > 0 || (target_sdk_version > 0 && target_sdk_version <= 21)) {
    args_jobj = soa.Env()->NewObjectArray(args.size(), WellKnownClasses::java_lang_Object, nullptr);
    if (args_jobj == nullptr) {
      CHECK(soa.Self()->IsExceptionPending());
      return zero;
    }
    for (size_t i = 0; i < args.size(); ++i) {
      if (shorty[i + 1] == 'L') {
        jobject val = args.at(i).l;
        soa.Env()->SetObjectArrayElement(args_jobj, i, val);
      } else {
        JValue jv;
        jv.SetJ(args.at(i).j);
        mirror::Object* val = BoxPrimitive(Primitive::GetType(shorty[i + 1]), jv);
        if (val == nullptr) {
          CHECK(soa.Self()->IsExceptionPending());
          return zero;
        }
        soa.Decode<mirror::ObjectArray<mirror::Object>* >(args_jobj)->Set<false>(i, val);
      }
    }
  }

2.调用XposedBridge.handleHookedMethod()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    const XposedHookInfo* hook_info = soa.DecodeMethod(method)->GetXposedHookInfo();
 
  // Call XposedBridge.handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
  //                                      Object thisObject, Object[] args)
// 利用jvalue传递参数
  jvalue invocation_args[5];
  invocation_args[0].l = hook_info->reflected_method;
  invocation_args[1].i = 1;
  invocation_args[2].l = hook_info->additional_info;
  invocation_args[3].l = rcvr_jobj;
  invocation_args[4].l = args_jobj;
  /*
   #define CLASS_XPOSED_BRIDGE  "de/robv/android/xposed/XposedBridge"
    classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
    ArtMethod::xposed_callback_class = classXposedBridge;
     methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
       "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
    ArtMethod::xposed_callback_method = methodXposedBridgeHandleHookedMethod;
  */
  jobject result =
      soa.Env()->CallStaticObjectMethodA(ArtMethod::xposed_callback_class,
                                         ArtMethod::xposed_callback_method,
                                         invocation_args);

3.处理结果并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Unbox the result if necessary and return it.
  if (UNLIKELY(soa.Self()->IsExceptionPending())) {
    return zero;
  } else {
    if (shorty[0] == 'V' || (shorty[0] == 'L' && result == nullptr)) {
      return zero;
    }
    // This can cause thread suspension.
    size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
    mirror::Class* result_type = soa.DecodeMethod(method)->GetReturnType(true /* resolve */, pointer_size);
    mirror::Object* result_ref = soa.Decode<mirror::Object*>(result);
    JValue result_unboxed;
    if (!UnboxPrimitiveForResult(result_ref, result_type, &result_unboxed)) {
      DCHECK(soa.Self()->IsExceptionPending());
      return zero;
    }
    return result_unboxed;
  }

在这三个部分中最最重要的实际上是第二部分。

 

其中首先通过GetXposedHookInfo()函数调用GetEntryPointFromJniPtrSize()函数获取Xposed在设置函数hook时保存在entry_point_from_jni_中的XposedHookInfo对象信息。

1
2
3
4
5
const XposedHookInfo* GetXposedHookInfo() {
    DCHECK(IsXposedHookedMethod());
    return reinterpret_cast<const XposedHookInfo*>(GetEntryPointFromJniPtrSize(sizeof(void*)));
}
const XposedHookInfo* hook_info = soa.DecodeMethod(method)->GetXposedHookInfo();

然后拼接参数并通过CallStaticObjectMethodA()函数JNI调用XposedBridge.handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,Object thisObject, Object[] args)函数这样就再次回到Java层中。

 

此时再次回到XposedBridge的源码,找到对应handleHookedMethod函数的实现会发现

  1. 先调用了CallBack中的beforeHookedMethod函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// call "before method" callbacks
        int beforeIdx = 0;
        do {
            try {
                ((XC_MethodHook) callbacksSnapshot[beforeIdx]).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
                beforeIdx++;
                break;
            }
        } while (++beforeIdx < callbacksLength);

2.然后通过invokeOriginalMethodNative调用原函数

1
2
3
4
5
6
7
8
9
// call original method if not requested otherwise
        if (!param.returnEarly) {
            try {
                param.setResult(invokeOriginalMethodNative(method, originalMethodId,
                        additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
            } catch (InvocationTargetException e) {
                param.setThrowable(e.getCause());
            }
        }

3.最后执行所有的afterHookedMethod回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// call "after method" callbacks
        int afterIdx = beforeIdx - 1;
        do {
            Object lastResult =  param.getResult();
            Throwable lastThrowable = param.getThrowable();
 
            try {
                ((XC_MethodHook) callbacksSnapshot[afterIdx]).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);
            }
        } while (--afterIdx >= 0);

其中invokeOriginalMethodNative函数则是通过保存的reflected_method反射对象对原函数进行反射调用,也就是通过原art函数InvokeMethod()函数进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod,
            jint isResolved, jobjectArray, jclass, jobject javaReceiver, jobjectArray javaArgs) {
    ScopedFastNativeObjectAccess soa(env);
    if (UNLIKELY(!isResolved)) {
        ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaMethod);
        if (LIKELY(artMethod->IsXposedHookedMethod())) {
            javaMethod = artMethod->GetXposedHookInfo()->reflected_method;
        }
    }
#if PLATFORM_SDK_VERSION >= 23
    return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);
#else
    return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs, true);
#endif
}

5. 参考资料

https://blog.csdn.net/weixin_47883636/article/details/109018440

 

https://egguncle.github.io/2018/02/04/xposed-art-hook-%E6%B5%85%E6%9E%90/

 

https://bbs.pediy.com/thread-257844.htm


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-4-15 18:08 被Simp1er编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (7)
雪    币: 1929
活跃值: (12840)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2021-4-16 10:24
2
0
雪    币: 1886
活跃值: (4905)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
Simp1er 2021-4-16 21:22
3
0
珍惜Any [em_63]
还要感谢珍惜大佬的帖子
雪    币: 62
活跃值: (556)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
万里星河 2021-4-17 00:51
4
0
雪    币: 224
活跃值: (962)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
樊辉 2021-4-21 09:47
5
0
收藏
雪    币: 121
活跃值: (1517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xxRea 2021-4-27 15:57
6
0
雪    币: 11
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_倾尽天下 2021-5-3 10:23
7
0
雪    币: 78
活跃值: (1523)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lookaside 2023-9-25 15:00
8
0
SetCodeItemOffset(0);
请问这个有什么特殊的用意吗?
游客
登录 | 注册 方可回帖
返回