算是作为一个 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
看雪ID:珍惜Any
https://bbs.pediy.com/user-819934.htm
推荐文章++++
好书推荐