android逆向之Dalvik指令集

前言

Dalvik指令语法详解
该篇文章为本人的学习笔记,如有不对之处,请指教.

附参考链接:smali文件语法参考

类型

字节码类型描述符

语法 含义
V void,只用于返回值类型
Z boolean
B byte
S short
C char
I int
J long
F float
D double
L java类类型
[ 数组类型

其中L类型可以表示Java类型中的任何类.
例如

      java.lang.String  
在smali语法中表示为:
      Ljava.lang.String;

注意后面有个分号,L类型最后的分号表示对象名结束.

[类型可以表示所有基本类型的数组. [后面紧跟基本数据类型描述符. 如[I 相当于Java中的int[],即一维数组. [[I相当于Java中的int[][],即二维数组.
三维、四维等等数值以此类推. 注意多维数组的维数最大为255个.

L[ 可以同时使用用来表示对象数组. 如[Ljava.lang.String;就表示这是一个String类型的数组.

方法及字段

方法的表现格式如下

Lpackage/name/ObjectName;->MethodName(III)Z

其中 Lpackage/name/ObjectName;应该理解为该方法所在的类,MethodName为具体方法名,(III)Z 这是方法具体的传参和返回部分,其中括号内的III为方法参数(在这里是表示三个int类型的参数),Z表示方法多维返回值(在这里返回值为boolean类型).

字段的格式和方法很像,只是方法的括号、括号里面的参数及返回值,这些字段都是没有的,后面取而代之的是字段自己的类型.字段格式如下

Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;

其中Lpackage/name/ObjectName;不用说还是该字段所在的类,FieldName为字段名,Ljava/lang/String;为字段类型.其中字段名与字段类型之间用冒号:隔开.

Dalvik指令

首先咱们来解析一条指令

move-wide/from16 vAA,vBBBB

move为基础字节码,即操作符 . wide为名称后缀,标识操作的数组为64位. from16位字节码的后缀,标识源操作数是一个16位寄存器引用变量. vAA为目的寄存器,他始终在源寄存器的前面.
vBBBB为源寄存器. 若没有wide后缀,默认为32位.

move指令

move 指令的作用是将源寄存器的值赋值给目的寄存器,即

move vA,vB

move-wide作用同上,只是赋值的为64位.
move-object是为对象赋值.
move-result 指令的作用是将上一个invoke类型指令的操作结果赋值给目的寄存器,即

move-result vAA

move-result-wide作用同上,只是赋值的为64位.
move-object同上,只是赋值为对象类型.

返回指令

return-void表示函数从一个void方法返回.
return 表示函数返回一个32位非对象的值.
return-wide 表示函数返回一个64位非对象的值.
return-object 表示函数返回一个对象类型.

数据定义

const常用来定义程序中用到是常量、字符串、类等数据.
const 、const/4、const/16给寄存器赋值基本数据类型.即

const/4 v1, 0x2

const-string给寄存器赋字符串,即

const-string v0, "\u60a8\u7684\u8bd5"


const-class给寄存器赋值一个类引用.

锁指令

锁指令用于在多线程程序中对同一对象的操作.

monitor-enter v0

为指定的对象获取锁.

monitor-exit v0

释放指定对象的锁.

实例操作指令

  • 类型转换指令
check-cast v0,type@BBBB

v0寄存器转换成指定的类型.

  • 检查指令
instance-of v0,v1,type@BBBB

检测v1是否可以转换成指定类型,可以转换v0赋值为1,否则赋值 0.

  • 创建指令
new-instance v0,type@BBBB

构造一个指定类型的实例,并把实例对象的引用赋值给v0.类型符 type指定类型不能为数组.

数组操作指令

  • 创建数组
new-array v0,v1,type@BBBB

构造指定类型的数组,v1表示数组的大小,并将数组赋值给v0.

filed-new-array {v1,v2,v3},type@BBBB

构造数组的另一种方式,即相当于Java中的
int[] arrays= {1,2,3,4};

  • 获取数组长度
array-length v0,v1

获取v1寄存器中的数组长度,并赋值给v0寄存器.

跳转指令

  • goto指令
goto +AA

无条件跳转到指定偏移量处,偏移量不能为0.

  • switch指令
packed-switch v0,+BBBB

分支跳转,v0寄存器为switch分支中的判断值,+BBBB指向的是packed-switch-payload格式的偏移表,表中的值是有规律的.

sparse-switch v0,+BBBB

作用同上,唯一不同是偏移表中的值是无规律的.

  • if指令
    if指令格式如下
    if-eq(此处可替换) v0,v1,+BBBB
    比较两个寄存器的值,符合条件进行跳转.
操作符 作用 对应java语句
if-eq 如果v0等于v1则跳转. if(v0==v1)
if-ne 如果v0不等于v1则跳转. if(v0!=v1)
if-lt 如果v0小于v1则跳转. if(v0
if-gt 如果v0大于v1则跳转. if(v0>v1)
if-le 如果v0小于等于v1则跳转. if(v0<=v1)
if-ge 如果v0大于等于v1则跳转. if(v0>=v1)
    if-eq(此处可替换) v0,+BBBB

用寄存器中的值和0进行比较,符合跳转跳转.

操作符 作用 对应java语句
if-eqz 如果v0等于0则跳转. if(v0==0)
if-nez 如果v0不等于0则跳转. if(v0!=0)
if-ltz 如果v0小于0则跳转. if(v0<0)
if-gtz 如果v0大于0则跳转. if(v0>0)
if-lez 如果v0小于等于0则跳转. if(v0<=0)
if-gez 如果v0大于等于0则跳转. if(v0>=0)

比较指令

用于比较两个寄存器的值(浮点型或长整型),比较结果放到v0寄存器中.
格式

cmpl-float(此处可替换) v0,v1,v2
操作符 作用
cmpl-float 如果v1小于v2则结果为1,相等则结果为0,大于则结果为-1.
cmpg-float 如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1.
cmpl-double 如果v1小于v2则结果为1,相等则结果为0,大于则结果为-1.
cmpg-double 如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1.
cmp-long 如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1.

字段操作指令

字段操作指令分两大类:普通字段和静态字段,普通字段指令的前缀为i,静态字段指令的前缀为s.
字段的读操作指令为get,写操作指令为put,因此普通字段的操作指令为iget,iput.静态字段的操作指令为sget,sput.
指令格式如下

.line 16
iput-object p1, p0, Lcom/view/dialogapplication/PhoneInfo;->context:Landroid/content/Context;

上面是一段iput指令代码,它所对应的java代码如下

  this.context = context;

没错,它就会一个简单的赋值context的代码;
由此,可以看出来, p1是要赋值的context,p0是源,而后面的第三个参数

Lcom/view/dialogapplication/PhoneInfo;->context:Landroid/content/Context;

可以看出来是p1的字段名.

此外还有一组以a为前缀的的操作指令,分别为aputaget,不过它们应该不算在字段的范畴了,应该为数组操作范畴,但因为也是和读写操作有关,所以就写在这里了,具体格式如下

aput-object  v2,v1,v0

其具体作用为将v2的值放入到v1数组的v0位置处.所以可以看出,v2为要放入的值,v1代表着存放v2值的数组,而v0则是v2要存放在数组的位置,即v0为index(数组角标).

方法调用指令

方法调用指令赋值调用类实例(也就是对象)的方法,它的基础指令为invoke.指令格式如下

invoke-virtual(名称后缀可替换) {v0,v1},method@BBBB(具体的方法)

其中{v0,v1}大括号中第一位放的是调用方法的对象,之后的为方法中的参数.若没有参数则只需传入调用方法的对象,即{v0}.

指令 作用
invoke-virtualinvoke-virtual/range 调用实例的虚方法.
invoke-superinvoke-super/range 调用实例父类的方法.
invoke-directinvoke-direct/range 调用实例的直接方法.
invoke-staticinvoke-static/range 调用实例的静态方法.
invoke-interfaceinvoke-interface/range 调用实例的接口方法.

数字转换指令

数据转换指令用于将一种类型的数值转换成另一种类型.格式如下

neg-int(可替换如下) v0,v1

指令中,v1存放需要转换的数据,v0存放转换后的结果.

指令 作用
neg-int 对整型输求补.
not-int 对整型输求反.
neg-long 对长整型数求补.
not-long 对长整型数求反.
neg-float 对单精度浮点型数求补.
neg-double 对双精度浮点数求补.
int-to-long 将整型数转换为长整型.
int-to-float 将整型数转换为单精度浮点型.
int-to-double 将整型数转换为双精度浮点型.
long-to-int 将长整型数转换位整型.
long-to-float 将长整型数转换为单精度浮点型.
long-to-double 将长整型数转换为双精度浮点型.
float-to-int 将单精度浮点转换为整型.
float-to-long 将单精度浮点型转换为长整型.
float-to-double 将单精度浮点型转换为双精度浮点型.
double-to-int 将双精度浮点型转换为整型.
double-to-long 将双精度浮点型转换为长整型.
double-to-float 将双精度浮点型转换为单精度浮点型.
int-to-byte 将整型转换为字节型.
int-to-char 将整型转换为字符串.
int-to-short 将整型转换为短整型.

数据运算指令

数据运算指令分为算术运算指令和逻辑运算指令,即 加、减、乘、除、取模、位移及与、或、非、异或等.
格式如下

add-int(可替换如下) v0,v1,v2

指令中,将v1v2进行运算,结果存到v0.

指令 作用
add-type v1v2进行加法运算,即v1+v2.
sub-type v1v2进行减法运算,即v1-v2.
mul-type v1v2进行乘法运算,即v1*v2.
div-type v1v2进行除法运算,即v1/v2.
rem-type v1v2进行取模运算,即v1%v2.
and-type v1v2进行与运算,即v1 AND v2.
or-type v1v2进行或运算,即v1 OR v2.
xor-type v1v2进行异或运算,即v1 XOR v2.
shl-type v1进行(有符号位)左移v2位,即v1<.
shr-type v1进行(有符号位)右移v2位,即v1>>v2.
ushr-type v1进行(无符号位)右移v2位,即v1>>v2.

其中后面的-type可以是-int、-long、-float、-double.

至此,Dalvik指令集基本就都介绍完了

你可能感兴趣的:(android逆向之Dalvik指令集)