安卓移动逆向(三)-Android Dalvik虚拟机

大家都知道Java程序是运行在Java虚拟机上,Android程序呢?
虽然Android平台使用Java语言来开发应用程序,但是Android程序却不是运行在标准的Java虚拟机上的. Google为Android平台专门设计了一套虚拟机来运行Android程序–Dalvik Virtual Machine,也就是Dalvik虚拟机了

本篇作用:

  • 扫盲Dalvik虚拟机
  • 了解Smail的语法,能读懂Smail文件

Dalvik概述

Dalvik的特点(相对于JVM)
  • 体积小,占用内存小;
  • 专有的DEX可执行文件格式,体积更小,执行速度更快;
  • 常量池采用32位索引值,寻址类方法名,字段名,常亮更快;
  • 基于寄存器架构,并拥有一套完成的指令系统
  • 提供了对象生命周期管理,堆栈管理,线程管理,安全和异常管理以及垃圾回收等重要功能;
  • 所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例;

Dalvik虚拟机与Java虚拟机的区别
  1. Java虚拟机运行的Java字节码,Dalvik虚拟机运行的是Dalvik字节码
  2. Dalvik可执行文件的体积更小
    稍作解析:
    SDK中有一个叫做dx的工具负责将Java字节码转换为Dalvik字节码.dx工具对Java类文件重新排列,消除在类文件中出现的所有冗余信息,避免虚拟机在初始化时出现重复的文件加载与解析过程.

    举个栗子:
    在Java中有大量的字符串常量在多个类文件中被重复使用,这些荣誉信息会直接增加文件的体积,同事也会严重影响虚拟机解析文件的效率.dx工具针对这个问题做了专门的处理,它将所有Java类文件中的常量池进行分解,消除其中的冗余信息,重新组合成一个常量池,所有的类文件共享同一个常量池.dx工具转换过程如图所示,由于dx工具对常量池的压缩,是的相同的字符串,常量在DEX文件中只出现一次,从而减小了文件的体积.

dx工具转换过程
3. Java虚拟机与Dalvik虚拟机架构不同

简单说一下:Java虚拟机基于栈架构,Dalvik基于寄存器架构;


Dalvik指令格式

一般Dalvik汇编代码由一系列的Dalvik指令组成,指令语法由指令的位描述与指令格式标识来决定.位描述约定如下

  • 每16位的字采用空格分割开来;
  • 每个字母表示四位,每个字母按顺序从高字节开始,排列到低字节.每四位之间可能用竖线”|”来表示不同的内容;
  • 顺序采用A-Z的翻个大写字母作为一个4位的操作码,op表示一个8位的操作码;
  • “Ø”来表示这个字段的所有位为0值;

栗子
“A|B|op BBBB F|E|D|C”
指令中间有两个空格,每个分开的部分是16位,共有3个16位组成这条指令;
第一个16位是”A|B|op” 高8位由A和B组成,低字节由操作码op组成;
第二个16位由BBBB组成,他表示一个16位的偏移值;
第三个16位分别由F,E,D,C共四个字节组成,在这里他们表示寄存器的参数.

单独使用位标识还无法确定一条指令的意思,必须通过指令格式标识来指定指令的格式编码,约定如下

  • 指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母;
  • 第一个数字式标识指令由多少个16位的字组成;
  • 第二个数字标识指令最多使用寄存器的个数,特殊标记”r”标识使用一定范围内的寄存器;
  • 第三个字母为类型码,标识指令用到的额外数据的类型,见下图:
  • 还有一种特殊的情况 是末尾可能还会多出另一个字母,如果是”s”表示指令采用静态链接,如果是”i”表示指令应该被内联处理.

    指令格式标识的类型码

栗子

“22x” 有三条信息可以读出

  1. 指令由2个16位字组成
  2. 指令使用2个寄存器
  3. 没有使用到额外的数据

另外,Dalvik指令对语法做了一些说明,约定如下

  • 每条指令从操作码开始,后面紧跟参数,参数个数不定,每个参数之间采用逗号分开;
  • 每条指令的参数从指令的第一部分开始,op位于低8位,高8位可以是一个8位的参数也可以是两个4位的参数,还可以为空.如果指令超过16位,则后面的部分依次作为参数;
  • 如果参数使用”vX”的方式标识,表明它是一个寄存器,如v0,v1等;
  • 如果参数采用”#+X”的方式,表明它是一个常量数字;
  • 如果参数采用”+X”的方式,表明它是一个相对指令的地址偏移;
  • 如果参数采用”kind@X”的方式,表明它是一个常量池索引值.其中kind表示常量池类型,例如string@BBBB,表示的就是字符串常量池索引BBBB;

栗子
“op vAA string@BBBB”
高8位为空,用到1个寄存器参数vAA,还用到一个字符串常量池索引BBBB;


Dalvik寄存器

扫盲结束了,开始重点了

Dalvik字节码的类型,方法,与字段表示方法
  1. 类型
    Dalvik字节码只有两种类型,基本与引用,话不多说,看图;
    类型
    每个Dalvik寄存器都是32位大小,对于小鱼或者等于32位长度的类型来说,一个寄存器就可以存放该类型的值,而像J(long),D(double)等64位的类型,它们的值是使用相邻量个寄存器来存储的,v0和v1或者vN与vN+1等;
    L就好理解了,表示任何一个Java类,在Dalvik汇编代码中,它们以” Lpackage/name/ObjectName; “表示,注意最后一个分号,比如” Ljava/lang/String;”相当于String;
    [类型就是所有的数组,[后面紧跟基本类型的描述符,如[I表示一个整型一维数组,->int[],[[I表示int[][]<—> [Ljava/lang/String; 表示对象数组 String [];
  2. 方法
    Dalvik使用方法名,类型参数与返回值来描述一个方法;
    格式如下:
    Lpackage/name/ObjectName;->MethodName(III)Z
    说明:
    Lpackage/name/ObjectName;是一个类型;
    MethodName方法名
    (III)参数,三个int参数
    Z返回值void
    栗子
    method(I[[IILjava/lang/String;[Ljava/lang/String;)Ljava/lang/String;
    咳咳,按照上面的知识,将其转换为Java形式的代码为:
    String method(int ,int[][],String,String[])
  3. 字段
    字段和方法很相似,就是没有参数和返回值,取而代之的是字段的类型,格式如下
    Lpackage/name/ObjectName;->FieldName:TYPE
    说明:
    Lpackage/name/ObjectName;是一个类型;
    FieldName字段名
    TYPE字段类型
    FieldName与TYPE用冒号隔开
    栗子
    name:Ljava/lang/String;
    转换:
    String name;
    Dalvik代码中的字段代码以.field指令开头,根据字段类型不同,在字段指令的开始,可能会用到井号”#”加以注释;

Dalvik指令集

指令特点

Dalvik指令在调用格式上模仿了C语言的调用约定.Dalvik指令语法与助词符有如下特点:

  • 参数采用从目标(destination)到源(source)的方式;
  • 根据字节码的布局与选项的不同,一些字节码添加了字节码后缀消除歧义,这些后缀通过在字节码主名称后添加斜杠”/”来分隔开;
  • 在指令集的描述中,宽度值中的每个字母表示宽度为4位;
  • 根据字节码的大小与类型的不同,一些字节码添加了名称后缀以消除歧义:
    • 32位常规类型的字节码,未添加任何后缀;
    • 64位常规类型的字节码以-wide后缀;
    • 特殊类型的字节码根据具体类型添加后缀,他们可以是-boolean,-byte,-char,-short,-int,-long,-float,-double,-object,-string,-class,-void之一;

栗子
“move-wide/from16 vAA,vBBBB”

move为基础字节码.标识这是基本操作;
wide为名称后缀.标识指令操作的数据宽度(64位);
from16位字节码后缀.标识源为一个16位的寄存器引用变量;
vAA为目的寄存器,它始终在源的前面 取值范围为v0-v2^8-1(255);
vBBBB为源寄存器,取值范围为v0-v2^16-1(65535)

空操作指令

空操作指令的助记符为nop,他的值是00,通常nop指令被用过对齐代码用途,没啥大用;

数据操作指令

数据操作指令为move.move指令的原型为move destination,source或者move destination,move指令根据字节码的大小与类型不同,后面会跟上不同的后缀.
栗子(表示太多直接上图,都差不多)

数据操作指令

返回指令

返回指令指的是函数结尾时运行的最后一条指令.他的基础字节码位return,共有以下四条返回指令
栗子

  1. “return-void” 返回一个void
  2. “return vAA” 返回一个32位非对象类型的值,返回值寄存器位8位的寄存器vAA;
  3. “return-wide vAA” 返回一个64位非对象类型的值,返回值寄存器位8位的寄存器vAA;
  4. “return-object vAA” 返回一个对象类型的值,返回值寄存器位8位的寄存器vAA;

数据定义指令

数据定义指令用来定义程序中用到的变量,字符串,类等数据,他的基础字节码为const
栗子(表示太多直接上图-_-)
这里写图片描述
这里写图片描述

锁指令

锁指令多用在多线程程序中对同一对象的操作,Dalvik指令集中有两条锁指令.

  1. “monitor-enter vAA” 为指定的对象获取锁
  2. “monitor-exit vAA”为指定的对象释放锁

实例操作指令

与实例相关的操作包括实例的类型传换,检查及新建等;

  • “check-case vAA,type@BBBB” 将vAA中的对象引用强转为BBBB类型;
(BBBB)vAA;
  • “instance-of vA,vB,type@CCCC” 判断vB中的对象引用是否能转成CCCC类型,能vA=1,不能vA=0;
if(vB.instanceof(type@CCCC)){
    vA =1;
}else{
    vA = 0;
}
  • “new-instance vAA,type@BBBB” 新建一个BBBB的对象vAA,BBBB不能为数组
BBBB vAA = new BBBB();
  • “check-cast/jumbo vAAAA,type@BBBBBBBB” 与”check-case vAA,type@BBBB”作用相同,只是取值范围更大(Android 4.0新增)
  • “instance-of/jumbo vAAAA,vBBBB,type@CCCCCCCC”与”instance-of vAA,vBB,type@CCCC”作用相同,只是取值范围更大(Android 4.0新增)
  • “new-instance/jumbo vAAAA,type@BBBBBBBB”与”new-instance vAA,type@BBBB”作用相同,只是取值范围更大(Android 4.0新增)

数组操作指令

数组操作包括获取数组长度(指的是数组的条目个数),新建数组,数组赋值,数组元素取值与赋值等操作;

  • “array-length vA,vB”
vA = vB.length; //  将vB的长度赋值给vA
  • “new-array vA,vB,type@CCCC”
vA = CCCC[vB]; //   构建一个vB大的CCCC类型的数组赋值给vA
  • 其余的附图
    这里写图片描述

异常指令

Dalvik 指令集中有一条指令用于抛出异常

  • “throw vAA” 抛出vAA寄存器中指定类型的异常

跳转指令

Dalvik指令集中有三种跳转指令:无条件跳转(goto),分支跳转(switch),条件跳转(if)

  • “goto +AA” 无条件跳转到指定偏移处,偏移量AA不能为0;
  • “goto/16+AAAA” 无条件跳转到指定偏移处,偏移量AAAA不能为0;
  • “goto/32+AAAAAAAA” 无条件跳转到指定偏移处;
  • “packed-switch vAA,+BBBBBBBB” 分支跳转指令. vAA寄存器为switch分支中需要判断的值即(switch(vAA)),BBBBBBBB指向一个packed-switch-payload格式的偏移表,表中的值是规律递增的.(先这么记住就好,感兴趣可以找百度..)
  • “sparse-switch vAA,+BBBBBBBB”分支跳转指令,vAA寄存器为switch分支中需要判断的值即(switch(vAA)),BBBBBBBB指向一个sparse-switch-payload格式的偏移表,表中的值是无规律的偏移量.
  • “if-test vA,vB,+CCCC” 条件跳转指令,比较vA与vB的值,如果比较结果满足就跳转到CCCC指定的偏移处,偏移量CCCC不能为0,if-test类型的指令有以下几条:
    • “if-eq vA, vB, :cond_xx” 如果vA等于vB则跳转到:cond_xx
    • “if-ne vA, vB, :cond_xx” 如果vA不等于vB则跳转到:cond_xx
    • “if-lt vA, vB, :cond_xx” 如果vA小于vB则跳转到:cond_xx
    • “if-ge vA, vB, :cond_xx” 如果vA大于等于vB则跳转到:cond_xx
    • “if-gt vA, vB, :cond_xx” 如果vA大于vB则跳转到:cond_xx
    • “if-le vA, vB, :cond_xx” 如果vA小于等于vB则跳转到:cond_xx
  • “if-testz vAA,+BBBB”条件跳转指令,那vAA与0作比较,满足结果或者不满足结果就跳转到BBBB的指定偏移处BBBB不能为0, if-testz类型的指令有以下几条:
    • “if-eqz vA, :cond_xx” 如果vA等于0则跳转到:cond_xx
    • “if-nez vA, :cond_xx” 如果vA不等于0则跳转到:cond_xx
    • “if-ltz vA, :cond_xx” 如果vA小于0则跳转到:cond_xx
    • “if-gez vA, :cond_xx” 如果vA大于等于0则跳转到:cond_xx
    • “if-gtz vA, :cond_xx” 如果vA大于0则跳转到:cond_xx
    • “if-lez vA, :cond_xx” 如果vA小于等于0则跳转到:cond_xx

比较指令

比较指令用于对两个寄存器的值(浮点型或者长整型)进行比较格式为:
“cmpkind vAA,vBB,vCC”

Dalvik指令集中共有5条比较指令:

  • “cmpl-float” 比较两个float值;
if(vBB == vCC){
    vAA =0;
}else if(vBB>vCC){
    vAA = -1;
}else if(vBB<vCC>){
    vAA = 1;
}
  • “cmpg-float” 比较两个float的值
if(vBB == vCC){
    vAA =0;
}else if(vBB<vCC){
    vAA = -1;
}else if(vBB>vCC>){
    vAA = 1;
}

当cmpg或者cmp时,B > C时A = 1,反之-1;当cmpl时,B > C时A = -1反之1;

  • “cmpg-double” 比较两个double的值
  • “cmpl-double” 比较两个double的值
  • “cmp-long” 比较两个long的值

字段操作指令

字段操作指令用来对对象实例的字段进行读写操作.
字段的类型可以是Java中有效的数据类型,对普通字段与静态字段操作有两种指令集,分别是”iinstanceop vA,vB,field@CCCC”与”sstaticop vAA,field@BBBB”.
在Android 4.0系统中,有”iinstanceop /jumbovAAAA,vBBBB,field@CCCCCCCC”与”sstaticop/jumbo vAAAA,field@BBBBBBBB”.和上面的两种作用相同,只是加了jmpbo后缀,寄存器与指令索引取值范围更大(后面的只会说有/jumbo指令后缀的指令集,作用就不指明了)
普通字段指令的指令前缀为i,如.对普通字段读操作使用iget指令,写操作使用iput指令;静态字段的指令前缀为s,如.对静态字段的读操作为sget,写操作为sput;
根据访问的字段类型不同,字段操作指令后面会紧跟字段类型的后缀,如iget-byte指令表示读取实例字段的值类型为byte;

方法调用指令
方法调用指令负责调用类实例的方法,它的基础指令为invoke,方法调用指令有”invoke-kind{vC,vD,vE,vF,vG},meth@BBBB”与”invoke-kind/range{vCCCC…VNNNN},meth@BBBB”两类,这两类指令作用没啥不同,后者在设置参数寄存器时使用了range来指定寄存器的范围,根据方法类型的不同,共有如下5条方法调用指令:

  1. “invoke-virtual” 调用实例的虚方法
  2. “invoke-super” 调用实例的父类方法
  3. “invoke-direct” 调用实例的直接方法
  4. “invoke-static” 调用实例的静态方法
  5. “invoke-interface” 调用实例的接口方法
    Android 4.0有jumbo的指令集;

方法调用指令的返回值必须使用move-result*指令来获取:
invoke-static{},Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
move-result-object v0;

数据转换指令
数据转换指令用于将一种类型的数值转换为另一种类型,他的格式为”unop vA,vB” 把vB中的数据做一定运算(转换)放在vA中:(比较简单,直接上图)

这里写图片描述

数据运算指令

数据运算指令包括算数运算指令与逻辑运算指令:

  • 算数运算指令:加,减,乘,除,模,移位等
  • 逻辑运算指令:间与,或,非,抑或等;
    上个图吧
    这里写图片描述
    其中基础字节码后面的-type可以是-int,-long,-float,-double,后面3类指令也差不多,就不列了,触类旁通;

== == == == == == == == == == == == == == == == == == == == == ==

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值