您当前的位置: 首页 >  android

Kevin-Dev

暂无认证

  • 0浏览

    0关注

    544博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Android -- 技术周刊】第 021 期

Kevin-Dev 发布时间:2022-05-21 08:15:00 ,浏览量:0

在这里插入图片描述

APP 提早发出去的包,如果出现客户端的问题,实在是干着急,覆水难收。因此线上修复方案迫在眉睫。

概述

基于 Xposed 中的思想,通过修改 c 层的 Method 实例描述,来实现更改与之对应的 java 方法的行为,从而达到修复的目的。

Xposed

诞生于 XDA 论坛,类似一个应用平台,不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed 需要以越狱为前提,像是 iOS 中的 cydia 。

Xposed 可以修改任何程序的任何 java 方法(需root),github 上提供了 XposedInstaller ,是一个 android app。提供很多 framework 层,应用层级的程序。开发者可以为其开发一些系统或应用方面的插件,自定义 android 系统,它甚至可以做动态权限管理(XposedMods)。

Android 系统启动与应用启动

Zygote 进程是 Android 手机系统启动后,常驻的一个名为‘受精卵’的进程。

  • zygote 的启动实现脚本在 /init.rc 文件中
  • 启动过程中执行的二进制文件在 /system/bin/app_process

任何应用程序启动时,会从 zygote 进程 fork 出一个新的进程。并装载一些必要的 class,invoke一些初始化方法。这其中包括像:

  • ActivityThread
  • ServiceThread
  • ApplicationPackageManager

等应用启动中必要的类,触发必要的方法,比如:handleBindApplication,将此进程与对应的应用绑定的初始化方法;同时,会将 zygote 进程中的 dalvik 虚拟机实例复制一份,因此每个应用程序进程都有自己的 dalvik 虚拟机实例;会将已有 Java 运行时加载到进程中;会注册一些 android 核心类的 jni 方法到虚拟机中,支撑从 c 到 java 的启动过程。

Xposed做了手脚

Xposed在这个过程改写了app_process(源码在Xposed : a modified app_process binary),替换/system/bin/app_process这个二进制文件。然后做了两个事:

  1. 通过Xposed的hook技术,在上述过程中,对上面提到的那些加载的类的方法hook。
  2. 加载XposedBridge.jar

这时hook必要的方法是为了方便开发者为它开发插件,加载XposedBridge.jar是为动态hook提供了基础。在这个时候加载它意味着,所有的程序在启动时,都可以加载这个jar(因为上面提到的fork过程)。结合hook技术,从而达到了控制所有程序的所有方法。

为获得 /system/bin/ 目录的读写权限,因而需要以 root 为前提。

Xposed 的 hook 思想

那么Xposed是怎么hook java方法的呢?要从XposedBridge看起,重点在 XposedBridge.hookmethod(原方法的Member对象,含有新方法的XC_MethodHook对象);,这里会调到

private native synchronized static void hookMethodNative(Member method, Class declaringClass, int slot, Object additionalInfo);

这个native的方法,通过这个方法,可以让所hook的方法,转向native层的一个c方法。如何做到?

When a transmit from java to native occurs, dvm sets up a native stack.
In dvmCallJNIMethod(), dvmPlatformInvoke is used to call the native method(signature in Method.insns).

在jni这个中间世界里,类型数据由jni表来沟通java和c的世界;方法由c++指针结合DVM*系(如dvmSlotToMethod,dvmDecodeIndirectRef等方法)的api方法,操作虚拟机,从而实现java方法与c方法的世界。

那么hook的过程是这样:首先通过dexclassload来load所要hook的方法,分析类后,进c层,见代码XposedBridge_hookMethodNative方法,拿到要hook的Method类,然后通过dvmslotTomethod方法获取Method*指针,

Method* method = dvmSlotToMethod(declaredClass, slot);

declaredClass就是所hook方法所在的类,对应的jobject。slot是Method类中,描述此java对象在vm中的索引;那么通过这个方法,我们就获取了c层的Method指针,通过

SET_METHOD_FLAG(method, ACC_NATIVE);

将该方法标记为一个native方法,然后通过

method->nativeFunc = &hookedMethodCallback;

定向c层方法到hookedMethodCallback,这样当被hook的java方法执行时,就会调到c层的hookedMethodCallback方法。

通过meth->nativeFunc重定向MethodCallBridge到hookedMethodCallback这个方法上,控制这个c++指针是无视java的private的。

另外,在method结构体中有

method->insns = (const u2*) hookInfo;

用insns指向替换成为的方法,以便hookedMethodCallback可以获取真正期望执行的java方法。

现在所有被hook的方法,都指向了hookedMethodCallbackc方法中,然后在此方法中实现调用替换成为的java方法。

从Xposed提炼精髓

回顾 Xposed,以root为必要条件,在app_process加载XposedBidge.jar,从而实现有hook所有应用的所有方法的能力;而后续动态hook应用内的方法,其实只是load了从zypote进程复制出来的运行时的这个XposedBidge.jar,然后hook而已。因此,若在一个应用范围内的hook,root不是必须的,只是单纯的加载hook的实现方法,即可修改本应用的方法。

业界内也不乏通过「修改BaseDexClassLoader中的pathList,来动态加载dex」方式实现热修复。后者纯java实现,但需要hack类的优化流程,将打CLASS_ISPREVERIFIED标签的类,去除此标签,以解决类与类引用不在一个dex中的异常问题。这会放弃dex optimize对启动运行速度的优化。原则上,这对于方法数没有大到需要multidex的应用,损失更明显。而前者不触犯原有的优化流程,只点杀需要hook的方法,更为纯粹、有效。

关注
打赏
1658837700
查看更多评论
立即登录/注册

微信扫码登录

0.0388s