专栏首页difcareer的技术笔记Dalvik虚拟机原理及Xposed hook原理

Dalvik虚拟机原理及Xposed hook原理

这块知识本身是挺多的,网上有对应的源码分析,本文尽量从不分析代码的角度来把原理阐述清楚。

Xposed是一个在andoid平台上比较成熟的hook框架,可以完美的在dalvik虚拟机上做到hook任意java方法。在art虚拟机上仍然处在beta阶段,相信以后也会稳定支持。

Xposed在dalvik上的hook原理值得好好学习,这样才能改造它,或者开发类似的hook框架。

Dalvik虚拟机的代码分析参考这篇文章: http://blog.csdn.net/innost/article/details/50377905 dex文件格式参考这篇文章: http://www.blogfshare.com/dex-format.html 以上两篇文章写得非常好,当你对着源码来学习就会发现。

这里做一个总结,顺便说一些这些文章里面没有写的内容:

java源码经过编译后,得到很多个class文件, 考虑到手机的内存较小,google改进了字节码的组织形式,将一个app中的所有class文件合到了一起构成dex文件,当然并不是简单的拼接在一起,而是遵从dex的格式(参见上)重新组织。

dex文件最终会和资源文件等一起打包成为apk,签名后安装到手机上。

PackageManager在安装apk的时候,做了一件事:优化dex文件为odex,存放在/data/dalvik-cache目录下。

dex文件是遵从于dalvik虚拟机标准的文件,它具有跨dalvik虚拟机的特点,而odex是在特定dalvik虚拟机上优化得到的,通常不能跨dalvik虚拟机运行。

程序执行体现在方法的执行上,因为我们重点关注下方法的组织形式。

在dex文件中,方法体里面的内容最终存储在classData区域,方法体里面存储的是二进制的字节码。

在说字节码之前,先来说说什么是dalvik虚拟机,dalvik虚拟机说白了就是用c/c++写的一套复杂的程序,它定义了一堆的smali指令(256个),这些字节码指令高度抽象,组合这些指令可以完成我们想要的功能。你可以等同于汇编指令理解,但它们在语言级别要高于汇编(在虚拟机里面执行的,虚拟机又是c/c++写的,自然看出高于汇编)。

字节码的定义参考: http://code.metager.de/source/xref/android/4.0.3/dalvik/libdex/DexOpcodes.h 字节码是用简单的二进制数字表示的,与可阅读的smali指令存在对应关系,具体对应方法参考google的官方文档: http://source.android.com/devices/tech/dalvik/instruction-formats.htmlhttp://source.android.com/devices/tech/dalvik/dalvik-bytecode.html

我们只要知道有这个对应关系就好了, 已经有人写了工具来做这个转化:smali/baksmali,这个工具可以分析dex文件,解析字节码为对应的smali语法(反汇编),同时也可以将smali语法的文件重新转换为字节码生成dex文件(汇编)。

dex优化过程,其实是将一些字节码替换为dalvik相关的, 优化后的等价字节码。smali/baksmali同时也支持将odex转化为dex,不过你需要从odex产生的手机上dump出对应的文件(framework.jar等),传递给它,才能正确转化。

虚拟机又是如何执行这些字节码的呢? 答案在AOSP中(开源就是好),原来虚拟机对于每一个字节码,都写了一段代码来解释执行(你可以等价理解为API调用一样,调用某个API,后面一堆逻辑来实现这个API),只是不同cpu结构,实现方式不一样,比如arm使用了arm汇编来实现,x86使用了x86的汇编来实现,google提供了一个c/c++的实现方式来兼容一些未知的cpu结构,见: https://android.googlesource.com/platform/dalvik2/+/master/vm/mterp/out/InterpC-portable.cpp

虚拟机在加载了odex(虚拟机总是使用odex文件,第一次使用时会先生成odex), 会将整个odex文件的内容mmap到内存中,之后就和odex文件没有关系了。

虚拟机在load一个Class的时候(参见DexClassLoader源码),根据类的描述符,在内存中的odex区域,查询到对应的数据,构建出ClassObject对象,以及这个ClassObject关联的Method。

Method分为两种,dalvik虚拟机在处理的时候有区别,一种是directMethod,即Java世界里面实现的方法,一种是nativeMethod,即在c/c++里面实现的方法。

ClassObject里面有两个集合,分别存放了这个Class下定义的directMethods和nativeMethods。

Method中,有两个非常重要的指针:

const u2* insns; 
DalvikBridgeFunc nativeFunc;

对于directMethod,insns存放了该方法的字节码指针(还记得odex被mmap到内存中了么,这个指针就是这段内存里面指向code区域的开始处的指针)。 虚拟机在调用directMethod时,在构建好方法栈以后,pc指针指向了insns,于是可以从内存中取得字节码,然后解释执行。

一些apk加固厂商就是在这块做的手脚,这里衍生开来说一下: 梆梆加固的实现方式为:将原始dex中的内容加密处理,在app运行时,解密出dex,mmap到内存,还原了内存结构。 爱加密的方法则是,将方法体里面的字节码从dex中抠出来,加密到了自己的so中,在app运行时,从so中解密出方法体,然后修改mmap对应的内存,还原内存结构。

这些加固方法说白了就是将原始dex做加固处理,在运行时还原内存结构。所以光从静态分析反编译加固后的dex文件,将得不到有用信息。

但有一个基于xposed的zjdroid脱壳工具,可以在运行时dump出内存(odex结构的内存),保存为本地odex文件,再利用smali/baksmali还原出原始dex文件。

虚拟机在处理native方法时,走的是另外一套逻辑。

我们在使用native方法时,首先得使用System.loadLibrary对so进行加载,其最终是使用dlopen函数加载了指定的so文件。

之后在我们调用nativeMehtod的时候,会根据方法描述符,通过特定的映射关系(是否主动进行了注册会有不同)得到一个native层的函数名,再从之前dlopen获得的句柄中使用dlsys去查找对应的函数,得到了函数指针后,将这个指针赋值给 insns。在nativeFunc这个桥接函数中,将insns解析为函数指针,然后进行调用。

Xposed hook原理

有前面这些知识后,再理解Xposed的hook原理就不难了。 前面已经知道,一个java方法在虚拟机里面对应的Method为directMethod,其insns指向了字节码位置。

Xposed在对java方法进行hook时,先将虚拟机里面这个方法的Method改为nativeMethod(其实就是一个标识字段),然后将该方法的nativeFunc指向自己实现的一个native方法,这样方法在调用时,就会调用到这个native方法,接管了控制权。

在这个native方法中,xposed直接调用了一个java方法,这个java方法里面对原方法进行了调用,并在调用前后插入了钩子,于是就hook住了这个方法。

Xposed的hook原理就是这么简单,但它有其他的问题要解决:如何将hook的代码注入到目标app的进程中?

Xposed的实现是依赖与root,重写android的zygote代码,加入自身的加载逻辑。zygote是android 系统最最初运行的程序,之后的进程都是通过它fork(你把它理解为复制吧)出来的。 于是zygote中加载的代码,在所有fork出来的子进程都含有(app进程也是fork出来的)。 所以xposed是一个可以hook android系统中任意一个java方法的 hook框架。

而淘宝根据xposed改造出来的dexposed,仅仅是注入hook代码的方式不同而已,hook逻辑完全一致。 dexposed不依赖与root,但需要开发者主动集成进来(我们集合了别人的广告sdk,其实也是让别人的程序跑到我们的进程里面,所以得小心点,给我一个入口,我也能hook住你的任何方法)。所以其hook的范围仅仅是被集成的应用(对于淘宝的AOP框架定义,这个效果刚刚好)。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)[转]include <stdio.h>int func(int a, int b, int c, int d, int e,

    声明:本文转自Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码),此文干货很多。

    用户2930595
  • opengl 坐标变换

    用户2930595
  • fatal: Could not read from remote repository 解决办法

    在windows下正常使用的ssh rsa key 复制到ubuntu下进行git操作时,报:fatal: Could not read from remote...

    用户2930595
  • 网络编程懒人入门(三):快速理解TCP协议一篇就够1、前言2、系列文章3、参考资料4、TCP 协议的作用5、TCP 数据包的大小6、TCP 数据包的编号(SEQ) 7、TCP 数据包的组装 8、慢启动

    本系列文章的前两篇《网络编程懒人入门(一):快速理解网络通信协议(上篇)》、《网络编程懒人入门(二):快速理解网络通信协议(下篇)》快速介绍了网络基本通信协议及...

    JackJiang
  • 算法训练 数字三角形

    问题描述   (图3.1-1)示出了一个数字三角形。 请编一个程序计算从顶至底的某处的一条路   径,使该路径所经过的数字的总和最大。   ●每一步...

    AI那点小事
  • LeetCode 832. Flipping an Image

    Given a binary matrix A, we want to flip the image horizontally, then invert it,...

    大学里的混子
  • 几个django 2.2和mysql使用

    可能是由于Django使用的MySQLdb库对Python3不支持,我们用采用了PyMySQL库来代替,导致出现各种坑,特别是执行以下2条命令的是时候:

    py3study
  • 短视频旋转图像,原来如此简单!不用编辑器,1键搞定

    但是有时候竖屏拍摄的图像,或者横屏拍摄的图像想要进行旋转,做一些特效,进行二次加工的时候,需要耗费很多的时间。

    程序员小助手
  • 图解 Java 位运算

    需要说明的是,在计算机中,数字是以补码的形式存在的,计算也是用补码来进行计算,计算后的结果也是补码

    CoderJed
  • 了不起的WebRTC:生态日趋完善,或将实时音视频技术白菜化

    有人说 2017 年是 WebRTC 的转折之年,2018 年将是 WebRTC 的爆发之年,这并非没有根据。就在去年(2017年),WebRTC 1.0 标准...

    JackJiang

扫码关注云+社区

领取腾讯云代金券