安卓逆向学习----Dalvik汇编代码(smali语言)

这里的dalvik汇编代码值得是针对dalvik虚拟机设计的指令集,与一般的汇编代码不同

1.dalvik指令的格式

Dalvik汇编代码由一系列的dalvik指令组成,指令语法由指令位描述和格式标识来决定,这部分感觉没用先不看了,在Android4.0及之前的安卓源码Dalvik/docs目录下的instruction-formats.html可以看到具体介绍

2.相关工具

smali.jar用来将smali文件汇编为dex文件,而baksmali.jar则是用来将dex文件反汇编为smali文件,ddx.jar将dex文件反汇编为ddx文件,其语法规则与smali类似

使用方法分别为:

java -jar smali.jar -o xxx.dex  xxx.smali
java -jar baksmali.jar -o ouput/ XXX.dex
java -jar baksmali.jar -o ouput/ XXX.dex

 3.dalvik寄存器

dalvik虚拟机采用ARM架构,将一部分寄存器映射到ARM寄存器上,另一部分使用调用栈进行模拟。Dalvik的寄存器都是32位的,64位需要用两个相邻寄存器表示。寄存器编号取值范围为v0-v65535

Android SDK中有名为dalvik.bytecode.Opcodes的接口,处理字节码的函数为一个宏HANDLE_OPCODE(),每个字节码对应一个cpp文件,在其中对这个宏进行重写。

寄存器的命名有两种,v命名和p命名。一个函数有m个寄存器用于储存局部变量和隐式传入的函数引用对象,n个用于储存参数,v命名法将寄存器命名为v0~vm+n-1,而p命名法前m个寄存器仍然为v0~vm-1,储存参数的寄存器则被命名为p0~pn-1。

4.Dalvik字节码的类型、方法与字段表示方法

可以参考官网https://source.android.com/devices/tech/dalvik/dalvik-bytecode的介绍,百度了好多还是官网说的比较清楚

(1)类型:

Dalvik字节码只有基本类型和引用类型两种,这两种类型可以表示Java语言的全部类型,除了对象和数组属于引用对象以外,其它的Java类型都是基本类型 

  • J、D这种64位的类型需要使用相邻的两个寄存器储存
  • L可以代表Java类型的任何类,这些类在java代码中以package.name.ObjectName的方式引用,对应的汇编形式为Lpackage/name/ObjectName;最后有一个分号。例如Ljava/lang/String;对应于java.lang.String
  • [表示所有基本类型的数组,[后面紧跟类型描述符,例如[I代表java中的int[],n个[表示n维数组,与L组合就可以表示对象数组,[Ljava/lang/String就表示字符串数组

(2)方法

方法的格式为Lpackage/name/ObjectName;->MethodName(III)Z   III表示该方法有三个int型参数,Z表示返回值为boolean型,转换为java方法应该为Boolean MethodName(int,int, int)

(3)字段

格式为Lpackage/name/ObjectName;->FildName:Ljava/lang/String;

5.Dalvik指令集

(1)特点

参数顺序先是目的操作数,后是源操作数

名称后缀:64位字节码添加-wide后缀,特殊类型字节码根据具体类型添加后缀,例如-boolean -byte -class -void。

字节码后缀:根据布局和选项的不同,添加后缀用“/”分隔开例如/from16表示源为16位的寄存器变量

(2)空操作指令

助记符为nop,被用来对齐代码

(3)数据操作指令

move vA, vB 将vB的值赋给vA ;  move-wide vA, vB 赋值为64位

寄存器默认为4位,/16表示源寄存器和目的寄存器都为16位,/from16表示源为16位  -object表示为对象赋值,

move-result vA表示将上一个invoke类型操作的单字非对象结果赋值给vA寄存器,这里只有destination

move-exception vA 保存一个异常到vA寄存器

(4)返回指令

return-void 从一个void方法返回

return-vAA 表示返回一个32位非对象类型的值,返回值寄存器为8位的寄存器

return-wide vAA 表示函数返回64位非对象类型的值,返回值寄存器为8位的寄存器

return-object vAA 表示函数返回对象类型的值,返回值寄存器为8位的寄存器

(5)数据定义指令

const-wide/16 vAA, #+BBBB   将该常量值符号拓展至64位后赋值给vAA

......

(6)锁指令

monitor-enter vAA 为指定的对象获取锁

monitor-exit vAA 释放指定对象的锁

(7)实例操作指令

包括实例的类型转换、检查以及新建等

check-cast vAA,type@BBBB 将vAA寄存器中的对象引用转换为指定类型,失败会抛出异常

instance-of vA , vB ,type@CCCC  判断vB寄存器中的对象引用是否可以转换为指定类型,可以则给vA赋值为1,否则为0

new-instance vAA,type@BBBB 构造一个制定类型对象的新实例,并将对象引用赋值给vAA寄存器,type不能是数组类型

这三种指令加上/jumbo之后功能一样,但是寄存器值与指令索引取值范围更大

(8)数组操作指令

array-length vA, vB 获取vB寄存器数组的长度并将值赋给vA寄存器,数组长度指的是数组条目个数

new-array vA, vB, type@CCCC 构造指定类型的与大小的数组,将索引赋给vA,大小由vB决定

filled-new-array {vC,vD,vE,vF,vG} , type@BBBB 构造指定类型与大小的数组并填充数组内容,大小由vA隐含提供,并将值赋给vA

fill-array-data vAA,+BBBBBBBB 用指定的数据填充数组,vAA寄存器为数组引用,引用必须为基础类型的数组

(9)异常指令

throw vAA 抛出vAA寄存器中制定的异常

(10)跳转指令

分为无条件跳转goto、分支跳转switch与条件跳转if

goto +AA 跳转AA的偏移量

packed-switch vAA, +BBBBBBBB分支跳转指令,vAA寄存器为switch分支需要判断的值。+BBBBBBB指向一个packed-switch-payload格式的偏移表

sparse-switch vAA, +BBBBBBBB分支跳转指令,vAA寄存器为switch分支需要判断的值。+BBBBBBB指向一个sparse-switch-payload格式的偏移表

if-xx  vA,vB, +CCCC 比较vA和vB,满足xx条件跳转到CCCC的偏移处,条件包括eq(等于)、ne(不等于)、lt(小于)、ge(大于等于)等等

if-xxx  vA, +CCCC 把vA和0比较,满足xxx条件跳转到CCCC的偏移处,条件包括eqz(等于)、nez(不等于)、ltz(小于)、gez(大于等于)等等

(11)比较指令

cmpl-xxx vAA,vBB,vCC  如果vBB小于vCC,则结果为1,相等为0,否则-1,xxx指的是float、double

cmpg-xxx vAA,vBB,vCC  如果vBB大于vCC,则结果为1,相等为0,否则-1,xxx指的是float、double

cmp-long vAA,vBB,vCC  如果vBB大于vCC,则结果为1,相等为0,否则-1

(12)字段操作指令

这些指令用来对对象实例的字段进行读写操作,按照普通字段和静态字段分为两类:

iop vA,vB, field@CCCC 和 sop vAA, field@BBBB

iop代表iget iget-wide iget-object iget-boolean iget-byte iput iput-wide......

sop代表sget sget-wide sget-object sget-boolean sget-byte sput sput-wide......

其中get表示读操作,put表示写操作

(13)方法调用指令

invoke-kind {vC,vD,vE,vF,vG},meth@BBBB和 invoke-kind/range {vCCCC..vNNN},meth@BBBB ,二者相似,只是后者使用range指定寄存器范围。按照调用方法的kind具体分为以下几种:

invoke-virtual 或 invoke-virtual/range调用实例的虚方法

invoke-super 或 invoke-super/range调用实例的父类方法

invoke-direct 或 invoke-direct/range调用实例的直接方法

invoke-static 或 invoke-static/range调用实例的静态方法invoke-interface 或 invoke-interface/ragne调用实例的接口方法

(14)数据转换指令

op vA,vB  表示将VB寄存器的数据进行转换然后储存到vA寄存器.op分为以下几种

neg-int/long/float/double 求补

not-int/long/float/double 求补

int-to-long int转换为long

float-to-int

.......

(15)数据运算指令

数据运算指令包括算术运算指令(加减乘除移位等)和逻辑运算指令(与或非异或得等)

指令格式为以下几种:

op vAA,vBB,vCC  将vBB,vCC 进行运算,保存到vAA

op/2addr vA,vB    将 vA,vB进行运算,保存到vA

op/lit16 vA,vB,#+CCCC 将vB与常量CCCC运算,保存到vA

op/lit8 vAA,vBB,#+CC   将vBB与常量CC运算,保存到vAA

op包含add-type sub-type mul-type div-type rem-type and-type or-type xor-type shl-type shr-type ushr-type

6.smali代码实例

.class public LHelloWorld; 	#定义类名
.super Ljava/lang/Object; 	#定义父类
.method public static main([Ljava/lang/String;)V
        .registers 4		#程序中使用4个寄存器,v0,v1,v2和一个参数寄存器
        .parameter		#一个参数,有n个参数则有n行
        .prologue		#代码起始的指令
        #空指令
        nop
        nop
        nop
        nop
        #数据定义指令
        const/16 v0, 0x8
    	const/4 v1, 0x5
	const/4 v2, 0x3
	#数据操作指令
	move v1, v2
	#数组操作指令
	new-array v0, v0, [I
	array-length v1, v0
	#实例操作指令
	new-instance v1, Ljava/lang/StringBuilder;
	#方法调用指令
	invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
	#跳转指令
	if-nez v0, :cond_0
	goto :goto_0
	:cond_0
	#数据转换指令
	int-to-float v2, v2
	#数据运算指令
	add-float v2, v2, v2
	#比较指令
	cmpl-float v0, v2, v2
	#字段操作指令
	sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
	const-string v1, "Hello World" #构造字符串
	#方法调用指令
	invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
	#返回指令
	:goto_0
        return-void
.end method

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章