算是作为一个 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两个文件夹
system->
---lib64->
------libart.so
------libart-compiler.so
------libart-disassembler.so
------libsigchain.so
------libxposed_art.so
---lib->
------libart.so
------libart-compiler.so
------libsigchain.so
------libxposed_art.so
---framework->
------XposedBridge.jar(注入到对应app进程里面的jar包)
---bin->
------app_process32_xposed
------app_process64_xposed
------dex2oat
------patchoat
xposed.prop->
xposed版本说明文件
META-INT->
---里面有文件配置脚本 flash-script.sh 配置各个文件安装位置
下载完毕会跳转到 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 版本号的命令
这块启动的已经是自定义的虚拟机了
第二个地方在 start函数这块,先看看 原函数。
也是在这个地方 进行的初始化 判断是否初始化成功 。
initialize 函数返回的是否加载成功的 一个全局变量 isXposedLoaded
初始化完毕以后开始调用真正的start函数
下面看 runtimeStart 函数
这块很有趣 在libart.so里面根据符号表信息尝试拿到Android::start函数
上面这些只要有一步失败了,在刷入的时候就可能变砖。
如果获取到了,则可以直接通过函数指针调用,主要是针对一些特殊的安卓版本号。
如果都没有找到 可以看到 Log会打印 。
“app_process: could not locate AndroidRuntime::start() method.”
(这个地方有个小技巧,可以对so文件里面的全部函数名字进行逐一字符判断,
比如可以对这个字符串 判断 是否含有 R u n t i m e s t a r t这几个字符,来绕过因为编译优化字符串不同问题)
这样一来完美替换了原虚拟机。
在新的虚拟机里面 会 将 XposedBridge.jar 进行注入,这么一来,所有被Xposed fork的进程都具备了 XposedBridge.jar 的代码 。
问题3:
当我们findAndHookMethod一个函数以后Xposed是怎么处理的?
打开XposedBridge项目
找到 findAndHookMethod
跟入XposedBridge.hookMethod
参数1 是一个 接口 可能传入的是一个 Constructor (构造方法的反射实例)也可能是 Method
Member 类型是Constructor 和Method都已经实现的,因为Xposed支持 Hook构造和Method。
最终 走到HookMethodNative方法,注册地方在Xposed里面的libXposed_common.cpp中
slot 是 Method在类中的偏移位置
重点分析一下实现过程
返回到Xposed 项目
libxposed_art.cpp-》XposedBridge_hookMethodNative函数
ScopedObjectAccess soa(env);
(SOA,就是约定的调用,包装env,出了函数范围自动释放)
FromReflectedMethod是ArtMethod里面的方法
也很简单 就是调用里面的GetArtMethod,在art虚拟机中,每一个加载的类方法都有一个对应的ArtMethod对象。
返回去 继续看 EnableXposedHook 函数
EnableXposedHook 在art_method.cc里面
(PrettyMethod函数有个小技巧 当我们分析被So中注册函数的时候 ,可以直接用ArtMethod的this指针调用 PrettyMethod 函数拿到签名信息)
继续查看 backup_method表示其为Hook方法的原方法,然后为备份的ArtMethod创建对应的Method对象。
(
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模式的函数的签名信息 必须以 “!”开头
)
把Method对象,方法额外信息和原始方法保存至XposedHookInfo结构体中,并调用SetEntryPointFromJni()把这个结构体变量的内存地址保存在ArtMethod对象中。这个方法原本是用来保存native方法的入口地址的,既然使用了这个位置,那么就必须把对应的标志位清除,代码实现的最后调用SetAccessFlags((GetAccessFlags() & ~kAccNative & ~kAccSynchronized) | kAccXposedHookedMethod)来完成标志位的清除(设置Fast_jni模式),此时这个ArtMethod对象对应是Hook后的方法,这个方法的实现不是native的。
这么一来完成了整体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://github.com/w296488320/XposedProjectDoc
喜欢文章的话 可以点个关注,如果对 逆向,脱壳,新技术 感兴趣的 同学 可以加 我Q群 欢迎各位能人志士 一起讨论
欢迎加入故事,群聊号码:773642813 也可以加入笔者的 小密圈,各种安卓新技术,源码分享等
参考:
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