Art模式下Xposed实现原理
2020-03-11 18:58:34 Author: mp.weixin.qq.com(查看原文) 阅读量:163 收藏

本文为看雪论坛优秀文章
看雪论坛作者ID:珍惜Any

算是作为一个 Art 学习的切入点吧,网上找了很多资料,大多数基于dvm的资料。

(Dvm可以 参考 邓凡平先生的 博客 :https://blog.csdn.net/Innost/article/details/50461783

今天也是自我总结一下Art模式下的,在github 搜索 可得如下三个项目,分别介绍一下:

XposedInstaller ,这是 Xposed 的插件管理和功能控制 APP,也就是说 Xposed 整体管控功能就是由这个 APP 来完成的,它包括启用 Xposed 插件功能,下载和启用指定插件 APP,还可以禁用 Xposed 插件功能等。注意,这个 app 要正常无误的运行必须能拿到 root 权限。

Xposed,这个项目属于 Xposed 框架,其实它就是单独搞了一套 xposed 版的 zygote。这个 zygote 会替换系统原生的 zygote。所以,它需要由 XposedInstaller 在 root 之后放到 /system/bin 下。

XposedBridge,这个项目也是 Xposed 框架,它属于 Xposed 框架的 Java 部分,编译出来是一个 XposedBridge.jar 包。

问题1:当我们获取Root以后安装Xposed会发生什么?

下载XposedInstaller 导入 AndroidStudio,先看看安装之前 :从页面初始化开始入手。

StatusInstallerFragment-》onCreateView

第一次见到user_de目录 使用如下:

安装完毕之后 ,当我们点击:

获取手机的各种信息,拼接成下载的URL:

我们以 安卓 7.1 arm64的为标准,下载完毕的 zip内容如下 主要包含 system 和 META-INT两个文件夹。

下载完毕会跳转到 InstallationActivity

onCreate 函数 -》创建 InstallationFragment

-》调用 startInstallation解压 zip文件-》执行done回调 -》

替换系统system对应目录下的文件-》执行刷机命令-》重启手机

问题2:Xposed 如何注入到zygote 进程中的?

首先复习一下Art虚拟机启动流程。

主要大致流程:


①Linux init进程解析配置脚本->②app_process(zygote进程对应的程序)->③ZygoteInit

① 解析配置脚本

service zygote:它告诉init进程,现在我们要配置一个名为zygote的服务。

/system/bin/app_process:声明zygote进程对应的文件路径。init创建服务的处理逻辑很简单,就是启动(fork)一个子进程来运行指定的程序。对zygote服务而言这个程序就是/system/bin/app_process。

-Xzygote/system/bin--zygote--start-system-server:传递给app_process的启动参数。

②app_process 创建


frameworks\base\cmds\app_process.cpp-》main函数

frameworks\base\core\jni\AndroidRuntime.cpp-》start函数

核心函数为:init,startVm

三个函数主要功能:


1. JNI_GetDefaultJavaVMInitArgs -- 获取虚拟机的默认初始化参数


2. JNI_CreateJavaVM -- 在进程中创建虚拟机实例


3. JNI_GetCreatedJavaVMs -- 获取进程中创建的虚拟机实例

ART像Dalvik一样,都实现Java虚拟机接口,这三个接口也是ART虚拟机核心接口。

startVm函数很复杂牵扯逻辑也很多,不 逐一描述了。

③ZygoteInit

继续查看 frameworks\base\core\jni\AndroidRuntime.cpp-》start函数

参数className的值等于“com.android.internal.os.ZygoteInit”,本地变量env是从调用另外一个成员函数startVm创建的ART虚拟机获得的JNI接口。

函数的目标就是要找到一个名称为com.android.internal.os.ZygoteInit的类,以及它的静态成员函数main,然后就以这个函数为入口,开始运行ART虚拟机。为此,函数执行了以下步骤:

① 调用JNI接口FindClass加载com.android.internal.os.ZygoteInit类。

② 调用JNI接口GetStaticMethodID找到com.android.internal.os.ZygoteInit类的静态成员函数main。

③ 调用JNI接口CallStaticVoidMethod开始执行com.android.internal.os.ZygoteInit类的静态成员函数main。

下面看看 Xposed是如何做拦截的。


开打 Xposed项目:

大于21编译走的是app_main2.cpp,看看具体改动了哪些。经过查阅,被修改的main函数,一共有两个地方。

其一,红框的地方 是判断是否是Xposed版本的虚拟机

在解析开启启动init脚本的时候 添加了--xposedversion 版本号的命令,这块启动的已经是自定义的虚拟机了。

handleOptions函数

第二个地方在 start函数这块,先看看原函数。

原函数

xposed zygote函数

也是在这个地方 进行的初始化 判断是否初始化成功 。


initialize 函数返回的是否加载成功的 一个全局变量 isXposedLoaded。

initialize函数

xposed自定义的数据结构体

初始化完毕以后开始调用真正的start函数,下面看 runtimeStart 函数。


这块很有趣,在libart.so里面根据符号表信息尝试拿到Android::start函数,上面这些只要有一步失败了,在刷入的时候就可能变砖。


如果获取到了,则可以直接通过函数指针调用,主要是针对一些特殊的安卓版本号。


如果都没有找到 可以看到 Log会打印 。

“app_process: could not locate AndroidRuntime::start() method.”

 runtimeStart函数

(这个地方有个小技巧,可以对so文件里面的全部函数名字进行逐一字符判断,比如可以对这个字符串 判断 是否含有 R u n t i m e s t a r t这几个字符,来绕过因为编译优化字符串不同问题)

这样一来完美替换了原虚拟机。


在新的虚拟机里面 会 将 XposedBridge.jar 进行注入,这么一来,所有被Xposed fork的进程都具备了 XposedBridge.jar 的代码 。

问题3:当我们findAndHookMethod一个函数以后Xposed是怎么处理的?

打开XposedBridge项目,找到 findAndHookMethod

findAndHookMethod

跟入XposedBridge.hookMethod

参数1 是一个接口,可能传入的是一个 Constructor (构造方法的反射实例)也可能是 Method。

Member 类型是Constructor 和Method都已经实现的,因为Xposed支持 Hook构造和Method。

hookMethod

最终 走到HookMethodNative方法,注册地方在Xposed里面libXposed_common.cpp中,slot 是 Method在类中的偏移位置。

重点分析一下实现过程,返回到Xposed 项目。

libxposed_art.cpp-》XposedBridge_hookMethodNative函数

XposedBridge_hookMethodNative

ScopedObjectAccess soa(env);
(SOA,就是约定的调用,包装env,出了函数范围自动释放)

FromReflectedMethod是ArtMethod里面的方法:

FromReflectedMethod

也很简单 就是调用里面的GetArtMethod,在art虚拟机中,每一个加载的类方法都有一个对应的ArtMethod对象。

返回去,继续看 EnableXposedHook 函数,EnableXposedHook 在art_method.cc里面。

文件地址 :https://github.com/rovo89/android_art/blob/b23f49623aa41ff4acc9b18fcd8b45cdb8493eb6/runtime/art_method.cc

EnableXposedHook①

(PrettyMethod函数有个小技巧 当我们分析被So中注册函数的时候 ,可以直接用ArtMethod的this指针调用 PrettyMethod 函数拿到签名信息)

继续查看 backup_method表示其为Hook方法的原方法,然后为备份的ArtMethod创建对应的Method对象。

EnableXposedHook②

fast_jin模式科普

下文参考资料(《深入理解ART虚拟机》

安卓函数执行分为两条线:第一种是 Java层,第二种JNI层,也就是 so层
当函数调用Java层进入到JNI层的是时候,虚拟机会将执行线程的状态从Runnable转换为Native。


如果JNI层又调用Java层相关函数的时候,执行线程的状态又得从Native层转换为Runnable。

线程的切换需要浪费时间,所以,对于某个特别强调执行速度的JNI函数可以设置成 fast jni模式。

这种模式下执行这个native函数 将不会进行 状态切换,即执行线程的状态 始终为Runnable。

当然,这种模式的使用对GC有一些影响,所以最好在那些本身函数执行时间段的,又不会阻塞的情况下使用。


另外,这种模式目前在art虚拟机内部,很多java native都有使用。

为了和其他Native函数 进行区分,当使用fast jni模式的函数的签名信息 必须以 “!”开头。

EnableXposedHook③

把Method对象,方法额外信息和原始方法保存至XposedHookInfo结构体中,并调用SetEntryPointFromJni()把这个结构体变量的内存地址保存在ArtMethod对象中。

这个方法原本是用来保存native方法的入口地址的,既然使用了这个位置,那么就必须把对应的标志位清除,代码实现的最后调用SetAccessFlags((GetAccessFlags() & ~kAccNative & ~kAccSynchronized) | kAccXposedHookedMethod)来完成标志位的清除(设置Fast_jni模式),此时这个ArtMethod对象对应是Hook后的方法,这个方法的实现不是native的。

EnableXposedHook④

这么一来完成了整体Hook流程

总结

执行流程:
XposedBridge.hookMethod-》XposedBridge.hookMethodNative-》EnableXposedHook
核心代码So层里面的 hookMethodNative 和 XposedBridge_hookMethodNative 里面。

1. hookMethodNative 先将java层传入的 被Hook的信息转换成 ArtMethod,方便调用方法进行Hook,调用EnableXposedHook 方法。

2. 在 EnableXposedHook 进行简单的判断 是否是被Hook的方法,以及是否已经被Hook过。


准备一个备份的 ArtMethod 存放 原方法的信息,将备份的ArtMethod 设置信息,所属类,告诉虚拟机这个方法不需要JIT编译,并将其设置成Native,准备一个简单的结构体XposedHookInfo保存,保存被Hook方法的信息,包括原方法的信息,地址,最后将入口设置成 XposedHookInfo,设置机械码执行的首地址,将原方法的 CodeItem偏移设置0。

参考

https://blog.csdn.net/Innost/article/details/50461783

https://bbs.meizu.cn/thread-8328245-1-1.html

邓凡平--- 《深入理解安卓虚拟机Art》
https://www.kancloud.cn/alex_wsc/androids/473621
https://blog.csdn.net/a314131070/article/details/81092526
https://blog.csdn.net/zjx839524906/article/details/81046844

- End -

看雪ID:珍惜Any

https://bbs.pediy.com/user-819934.htm 

*本文由看雪论坛 珍惜Any 原创,转载请注明来自看雪社区。

推荐文章++++

攻防世界fakebook关卡攻略

捆绑包驱动锁首病毒分析

**游戏逆向分析笔记

对宝马车载apps协议的逆向分析研究

x86_64架构下的函数调用及栈帧原理

好书推荐


公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]
“阅读原文”一起来充电吧!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458304001&idx=1&sn=5c8cab757ea9acb2c92db30d8b9578fb&chksm=b1818e8b86f6079d037cc50b1321d1aa4ce7d7fb72a9047d7ee33b6364a21731d89f0e2eabb3#rd
如有侵权请联系:admin#unsafe.sh