Art模式下Xposed实现原理

算是作为一个 Art 学习的切入点吧
网上找了很多资料,大多数基于dvm的资料。
(Dvm可以 参考 邓凡平先生的 博客 :https://blog.csdn.net/Innost/article/details/50461783

今天也是自我总结一下 Art模式下的

在github 搜索 可得如下三个项目,分别介绍一下


image.png

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

image.png

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

image.png

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


image.png

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

image.png

我们以 安卓 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


image.png

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

image.png

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

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

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

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

① 解析配置脚本

image.png

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函数


image.png

image.png

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

核心函数为: init,startVm


image.png

三个函数主要功能:
1. JNI_GetDefaultJavaVMInitArgs -- 获取虚拟机的默认初始化参数
2. JNI_CreateJavaVM -- 在进程中创建虚拟机实例
3. JNI_GetCreatedJavaVMs -- 获取进程中创建的虚拟机实例

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

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

③ZygoteInit

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

image.png

参数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项目

image.png

大于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在类中的偏移位置


image.png

重点分析一下实现过程

返回到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://github.com/w296488320/XposedProjectDoc


喜欢文章的话 可以点个关注,如果对 逆向,脱壳,新技术 感兴趣的 同学 可以加 我Q群 欢迎各位能人志士 一起讨论

欢迎加入故事,群聊号码:773642813 也可以加入笔者的 小密圈,各种安卓新技术,源码分享等

image.png

参考:
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容