xposed源码分析

xposed原理

  • zygote进程是android系统第一个java进程,其他所有的apk进程都是通过zygote进程fork的。xposed是一个hook框架,其通过修改zygote进程的native层代码和java层代码在zygote进程启动时将xposed框架使用的jar包(XposedBridge.jar)加载到android虚拟机中,然后将所有xposed插件模块也加载进android虚拟机中,同时还会修改android的art虚拟机(libart.so), 最后xposed通过ArtHook修改指定java方法的ArtMethod入口为代理函数。
  • 因为zygote之后fork其他进程的时候会共享这些资源,所以所有的apk都会加载所有的xposed模块(这也是卡顿和费电的原因)。另外因为这些操作是在zygote进程启动的时候执行的,所以如果要更改或添加新的xposed模块就需要重新启动手机,因为zygote fork其他apk进程的时候并不会重新执行加载xposed模块的代码了。(可以使用一些技巧实现不重启系统更新xposed模块:https://www.jianshu.com/p/360edca021bb

xposed组件

  • Xposed:xposed部分主要就是对app_process可执行文件代码的修改,其会加载XposedBridge.jar和所有的xposed插件模块。最后修改zygote进程的java入口点为XposedBridge中的入口。
  • XposedBridge:xposed提供的开发jar包。
  • android_art:xposed对android art虚拟机的修改。
  • XposedInstaller:xposed插件模块的管理程序。

xposed源码分析(初始化过程)

app_process.main

zygote进程对应的native层代码的入口在app_process模块中,此模块对应的代码为app_process.cpp文件。查看xposed源码中也有app_process.cpp文件,来到入口点main函数处。

  • 其会调用xposed::initialize进行初始化,此函数通过设置环境变量CLASSPATH预先加载xposed的XposedBridge.jar,这样后续加载的xposed模块就可以使用此jar包中的api进行hook操作了。
  • 接着判断xposed(就是上一步的jar包)是否加载,如果加载了就通过反射调用de.robv.android.xposed.XposedBridge类的入口main函数。如果没有加载xposed就还调用原始zygote进程的java层代码com.android.internal.os.ZygoteInit类的入口main函数。

de.robv.android.xposed.XposedBridge.main

  • 调用loadModules加载所有的xposed插件模块。
  • 此函数会调用initForZygote函数进行初始化,initForZygote函数内部会hook一些系统框架层的函数:例如会hook handleBindApplicaton函数并在此函数调用之前调用回调函数。
  • 最后执行zygote进程原始的执行流程。

loadModules

  • loadModules函数会动态加载xposed插件apk,然后获取apk中的xposed_init文件每一行设置的hook接口名称并实例化类。
  • 因为这些xposed_init文件中的类都需要实现handleLoadPackage接口函数,调用hookLoadPackage函数将所有的handleLoadPackage接口函数都保存到一个全局的回调函数链表sLoadedPackageCallbacks中。

initForZygote

initForZygote函数会hook handleBindApplicaton()并设置回调函数。在handleBindApplication函数调用之前此回调函数被调用,回调函数会获取当前加载apk的包名packageName,进程名processName,classLoader等信息并作为参数调用sLoadedPackageCallbacks全局回调函数链表中的所有回调函数。sLoadedPackageCallbacks链表中的回调函数就是handleLoadPackage接口函数。所以在编写xposed模块时编写handleLoadPackage接口函数是在handleBindApplication函数调用前调用的。

因为加壳apk都是在handleBindApplication函数调用的Application.attachBaseContext函数中修正classloader为自定义的classloader,并利用此自定义的classloader动态加载原始apk的dex文件。而xposed编写的handleLoadPacked函数是在handleBindApplication函数调用之前调用的所以这个时候获取到的classloader是壳的并不是原始apk的,因此利用此classloader是无法hook原始apk中的java类的(双亲委派)。对于加壳的app需要通过反射获取修正后的classloader进行hook。

xposed源码分析(hook过程)

xposed在apk加载的时候调用handleBindApplication函数之前会调用所有的handleLoadPackage回调函数,在handleLoadPackage函数中调用xposed的findAndHookMethod等接口api进行hook。

findAndHookMethod

  • findAndHookMethod先调用findMethodExact函数,此函数通过反射获取到待hook java方法对应的Method对象。
  • 继续调用XposedBridge.hookMethod函数

XposedBridge.hookMethod

  • hookMethod通过AdditionalHookInfo保存待hook Method的参数,返回值类型等信息。
  • 继续调用 native函数hookMethodNative,在Xposed/libxposed_art.cpp中。

hookMethodNative

  • 首先会获取待hook方法在java层的Method对象对应在native层的ArtMethod对象
  • 紧接着调用ArtMethod类的EnableXposedHook方法,此方法是xposed修改libart.so时添加的函数。

ArtMethod::EnableXposedHook

  • 先备份原ArtMethod函数信息

  • 申请XposedHookInfo保存hook函数的 before,after,原函数信息。并将XposedHookInfo信息保存在entry_point_from_jni_处,原本这里存放的时native函数的入口地址。

  • 设置ArtMethod的entry_point_from_quick_complied_code_入口为artQuickProxyInvokeHandler,如果dex文件经过dex2oat优化则会执行这个入口函数。

  • 设置ArtMethod的entry_point_from_interpreter入口为artInterpreterToCompiledCodeBridge,如果dex文件没有经过dex2oat优化或者强制进行解释执行模式则会执行这个入口函数。

  • 因为一个方法如果是native类型则entry_point_from_jni_保存其函数入口,现在这个地方用来保存XposedHookInfo信息,所以需要去除ArtMethod函数的native属性。(所有被xposed hook的java函数都会去除native属性。不过在低版本的xposed的实现中好像是将所有的java函数设置设置为native函数,然后将XposedHookInfo相关信息保存在java函数的insns中,正好与高版本反过来)

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();
  ArtMethod* backup_method = cl->AllocArtMethodArray(soa.Self(), 1);
  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* reflect_method;
  if (IsConstructor()) {
    reflect_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
  } else {
    reflect_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
  }
  reflect_method->SetAccessible<false>(true);

  //申请XposedHookInfo保存hook函数的 before,after,origin函数信息。
  // Save extra information in a separate structure, stored instead of the native method
  XposedHookInfo* hookInfo = reinterpret_cast<XposedHookInfo*>(calloc(1, sizeof(XposedHookInfo)));
  hookInfo->reflectedMethod = soa.Vm()->AddGlobalRef(soa.Self(), reflect_method);
  hookInfo->additionalInfo = soa.Env()->NewGlobalRef(additional_info);
  hookInfo->originalMethod = backup_method;
  //entry_point_from_jni_本来保存native函数入口,现在用来保存XposedHookInfo信息指针。
  SetEntryPointFromJni(reinterpret_cast<uint8_t*>(hookInfo));

  ThreadList* tl = Runtime::Current()->GetThreadList();
  soa.Self()->TransitionFromRunnableToSuspended(kSuspended);
  tl->SuspendAll("Hooking method");
  {
    MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
    tl->ForEach(StackReplaceMethod, this);
  }
  tl->ResumeAll();
  soa.Self()->TransitionFromSuspendedToRunnable();

  //设置ArtMethod的entry_point_from_quick_complied_code_入口为artQuickProxyInvokeHandler
  SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
  //设置ArtMethod的entry_point_from_interpreter入口为artInterpreterToCompiledCodeBridge
  SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);

  // Adjust access flags清除标志位,所有被hook的方法都设置为不是native函数
  SetAccessFlags((GetAccessFlags() & ~kAccNative & ~kAccSynchronized) | kAccXposedHookedMethod);
}

artQuickProxyInvokeHandler

当一个java函数被调用时,android虚拟机libart.so会去执行ArtMethod::Invoke进一步调用ArtMethod函数入口,而ArtMethod函数入口已经被xposed修改为了代理函数artQuickProxyInvokeHandle。

  • artQuickProxyInvokeHandle函数直接去调用InvokeXposedHandleHookedMethod

InvokeXposedHandleHookedMethod

InvokeXposedHandleHookedMethod函数在获取到之前保存的XposedHookInfo信息后作为参数传递给XposedBridge.jar包中handleHookedMethod函数

XposedBridge的handleHookedMethod

回到java层中之后XposedBridge.jar中的handleHookedMethod函数会分别调用before函数,原函数和after函数。

posted @ 2022-12-30 16:05  怎么可以吃突突  阅读(677)  评论(3编辑  收藏  举报