1.7.2 Dalvik 指令集
Dalvik 虚拟机
Android 程序运行在 Dalvik 虚拟机中,它与传统的 Java 虚拟机不同,完全基于寄存器架构,数据通过直接通过寄存器传递,大大提高了效率。Dalvik 虚拟机属于 Android 运行时环境,它与一些核心库共同承担 Android 应用程序的运行工作。Dalvik 虚拟机有自己的指令集,即 smali 代码,下面会详细介绍它们。
Dalvik 指令集
指令格式
Dalvik 指令语法由指令的位描述与指令格式标识来决定。
位描述约定如下:
每 16 位使用空格分隔。
每个字母占 4 位,按照顺序从高字节到低字节排列。
顺序采用 A~Z 的单个大写字母作为一个 4 位的操作码,op 表示一个 8 位的操作码。
”∅“来表示这字段所有位为0值。
指令格式约定如下:
指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母。
第一个数字表示指令有多少个 16 位的字组成。
第二个数字表示指令最多使用寄存器的个数。
第三个字母为类型码,表示指令用到的额外数据的类型。
寄存器
Dalvik 寄存器都是 32 位的,如果是 64 位的数据,则使用相邻的两个寄存器来表示。
寄存器有两种命名法:v 命名法和 p 命名法。如果一个函数使用到 M 个寄存器,其中有 N 个参数,那么参数会使用最后的 N 个寄存器,而局部变量使用从 v0 开始的前 M-N 个寄存器。在 v 命名法中,不管寄存器中是参数还是局部变量,都以 v 开头。而 p 命名法中,参数命名从 p0 开始,依次递增,在代码比较复杂的时候,使用 p 命名法可以清楚地区分开参数和局部变量,大多数工具使用的也是 p 命名法。
类型、方法和字段
Dalvik 字节码只有基本类型和引用类型两种。除了对象类型和数组类型是引用类型外,其余的都是基本类型:
V
void
Z
boolean
B
byte
S
short
C
char
I
int
J
long
F
float
D
double
L
对象类型
[
数组类型
对象类型格式是
L<包名>/<类名>;
,如 String 表示为Ljava/lang/String;
。数组类型格式是
[
加上类型,如int[]
表示为[I
,int[][]
表示为[[I
。
Dalvik 使用方法名、类型参数和返回值来描述一个方法。方法格式如下:
例如把下面的 Java 代码转换成 smali:
字段格式如下:
空操作指令
空操作指令的助记符为 nop
,值为 00,通常用于对齐代码。
数据操作指令
数据操作指令为 move
,原型为 move destination, source
。
move vA, vB
:vB -> vA,都是 4 位move/from16 vAA, vBBBB
:vBBBB -> vAA,源寄存器 16 位,目的寄存器 8 位move/16 vAAAA, vBBBB
:vBBBB -> vAAAA,都是 16 位move-wide vA, vB
:4 位的寄存器对赋值,都是 4 位move-wide/from16vAA, vBBBB
、move-wide/16 vAAAA, vBBBB
:与 move-wide 相同move-object vA, vB
:对象赋值,都是 4 位move-object/from16 vAA, vBBBB
:对象赋值,源寄存器 16 位,目的寄存器 8 位move-object/16 vAAAA, vBBBB
:对象赋值,都是 16 位move-result vAA
:将上一个 invoke 类型指令操作的单字非对象结果赋值给 vAA 寄存器move-result-wide vAA
:将上一个 invoke 类型指令操作的双字非对象结果赋值给 vAA 寄存器move-result-object vAA
:将上一个 invoke 类型指令操作的对象结果赋值给 vAA 寄存器move-exception vAA
:保存一个运行时发生的异常到 vAA 寄存器
返回指令
基础字节码为 return
。
return-void
:从一个 void 方法返回return vAA
:返回一个 32 位非对象类型的值,返回值寄存器位 8 位的寄存器 vAAreturn-wide vAA
:返回一个 64 位非对象类型的值,返回值寄存器为 8 位的 vAAreturn-object vAA
:返回一个对象类型的值,返回值寄存器为 8 位的 vAA
数据定义指令
基础字节码为 const
。
const/4 vA, #+B
:将数值符号扩展为 32 位后赋值给寄存器 vAconst/16 vAA, #+BBBB
:将数值符号扩展为 32 位后赋值给寄存器 vAAconst vAA, #+BBBBBBBB
:将数值赋值给寄存器 vAAconst/high16 vAA, #+BBBB0000
:将数值右边零扩展为 32 位后赋值给寄存器 vAAconst-wide/16 vAA, #+BBBB
:将数值符号扩展为 64 位后赋值给寄存器 vAAconst-wide/32 vAA, #+BBBBBBBB
:将数值符号扩展为 64 位后赋值给寄存器 vAAconst-wide vAA, #+BBBBBBBBBBBBBBBB
:将数值赋给寄存器对 vAAconst-wide/high16 vAA, #+BBBB000000000000
:将数值右边零扩展为 64 位后赋值给寄存器对 vAAconst-string vAA, string@BBBB
:通过字符串索引构造一个字符串并赋值给寄存器 vAAconst-string/jumbo vAA, string@BBBBBBBB
:通过字符串索(较大)引构造一个字符串并赋值给寄存器 vAAconst-class vAA, type@BBBB
:通过类型索引获取一个类型引用并赋值给寄存器 vAAconst-class/jumbo vAAAA, type@BBBBBBBB
:通过给定的类型索引获取一个类引用并赋值给寄存器 vAAAA。这条指令占用两个字节,值为 0x00ff
锁指令
用在多线程程序中对同一对象操作。
monitor-enter vAA
:为指定的对象获取锁monitor-exit vAA
:释放指定的对象的锁
实例操作指令
check-cast vAA, type@BBBB
check-cast/jumbo vAAAA, type@BBBBBBBB
:将 vAA 寄存器中的对象引用转换成指定的类型,如果失败会抛出 ClassCastException 异常。如果类型 B 指定的是基本类型,对于非基本类型的 A 来说,运行始终会失败instance-of vA, vB, type@CCCC
instance-of vAAAA, vBBBB, type@CCCCCCCC
:判断 vB 寄存器中的对象引用是否可以转换成指定的类型,如果可以 vA 寄存器赋值为 1,否则 vA 寄存器赋值为 0new-instance vAA, type@BBBB
new-instance vAAAA, type@BBBBBBBB
:构造一个指定类型对象的新实例,并将对象引用赋值给 vAA 寄存器,类型符 type 指定的类型不能是数组类
数组操作指令
array-length vA, vB
:获取vB寄存器中数组的长度并将值赋给vA寄存器。new-array vA, vB, type@CCCC
new-array/jumbo vAAAA, vBBBB, type@CCCCCCCC
:构造指定类型(type@CCCCCCCC)与大小(vBBBB)的数组,并将值赋给 vAAAA 寄存器filled-new-array {vC, vD, vE, vF, vG}, type@BBBB
:构造指定类型(type@BBBB)和大小(vA)的数组并填充数组内容。vA 寄存器是隐含使用的,处理指定数组的大小外还指定了参数的个数,vC~vG 是使用的参数寄存器列表。filled-new-array/range {vCCCC .. vNNNN}, type@BBBB
:同上,只是参数寄存器使用 range 字节码后缀指定了取值范围,vC 是第一个参数寄存器,N=A+C-1。fill-array-data vAA, +BBBBBBBB
:用指定的数据来填充数组,vAA 寄存器为数组引用,引用必须为基础类型的数组,在指令后面紧跟一个数据表。arrayop vAA, vBB, vCC
:对 vBB 寄存器指定的数组元素进行取值和赋值。vCC 寄存器指定数组元素索引,vAA 寄存器用来存放读取的或需要设置的数组元素的值。读取元素使用 aget 类指令,元素赋值使用 aput 类指令。
异常指令
throw vAA
:抛出 vAA 寄存器中指定类型的异常
跳转指令
有三种跳转指令:无条件跳转(goto)、分支跳转(switch)和条件跳转(if)。
goto +AA
goto/16 +AAAA
goto/32 +AAAAAAAA
:无条件跳转到指定偏移处,不能为 0packed-switch vAA, +BBBBBBBB
:分支跳转指令。vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个 packed-switch-payload 格式的偏移表,表中的值是有规律递增的sparse-switch vAA, +BBBBBBBB
:分支跳转指令。vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个sparse-switch-payload
格式的偏移表,表中的值是无规律的偏移量if-test vA, vB, +CCCC
:条件跳转指令。比较 vA 寄存器与 vB 寄存器的值,如果比较结果满足就跳转到 CCCC 指定的偏移处,CCCC 不能为 0。if-test
类型的指令有:if-eq
:if(vA==vB)if-ne
:if(vA!=vB)if-lt
:if(vA<vB)if-ge
:if(vA>=vB)if-gt
:if(vA>vB)if-le
:if(vA<=vB)
if-testz vAA, +BBBB
:条件跳转指令。拿 vAA 寄存器与 0 比较,如果比较结果满足或值为 0 就跳转到 BBBB 指定的偏移处,BBBB 不能为 0。if-testz
类型的指令有:if-eqz
:if(!vAA)if-nez
:if(vAA)if-ltz
:if(vAA<0)if-gez
:if(vAA>=0)if-gtz
:if(vAA>0)if-lez
:if(vAA<=0)
比较指令
对两个寄存器的值进行比较,格式为 cmpkind vAA, vBB, vCC,其中 vBB 和 vCC 寄存器是需要比较的两个寄存器或两个寄存器对,比较的结果放到 vAA 寄存器。指令集中共有5条比较指令:
cmpl-float
cmpl-double
:如果 vBB 寄存器大于 vCC 寄存器,结果为 -1,相等结果为 0,小于结果为 1cmpg-float
cmpg-double
:如果 vBB 寄存器大于 vCC 寄存器,结果为 1,相等结果为 0,小于结果为 -1cmp-long
:如果 vBB 寄存器大于 vCC 寄存器,结果为 1,相等结果为 0,小于结果为 -1
字段操作指令
用于对对象实例的字段进行读写操作。对普通字段与静态字段操作有两种指令集,分别是 iinstanceop vA, vB, field@CCCC
与 sstaticop vAA, field@BBBB
。扩展为 iinstanceop/jumbo vAAAA, vBBBB, field@CCCCCCC
与 sstaticop/jumbo vAAAA, field@BBBBBBBB
。
普通字段指令的指令前缀为 i
,静态字段的指令前缀为 s
。字段操作指令后紧跟字段类型的后缀。
方法调用指令
用于调用类实例的方法,基础指令为 invoke
,有 invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
和 invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB
两类。扩展为 invoke-kind/jumbo {vCCCC .. vNNNN}, meth@BBBBBBBB
这类指令。
根据方法类型的不同,共有如下五条方法调用指令:
invoke-virtual
或invoke-virtual/range
:调用实例的虚方法invoke-super
或invoke-super/range
:调用实例的父类方法invoke-direct
或invoke-direct/range
:调用实例的直接方法invoke-static
或invoke-static/range
:调用实例的静态方法invoke-interface
或invoke-interface/range
:调用实例的接口方法
方法调用的返回值必须使用 move-result*
指令来获取,如:
数据转换指令
格式为 unop vA, vB
,vB 寄存器或vB寄存器对存放需要转换的数据,转换后结果保存在 vA 寄存器或 vA寄存器对中。
求补
neg-int
neg-long
neg-float
neg-double
求反
not-int
not-long
整型数转换
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
数据运算指令
包括算术运算符与逻辑运算指令。
数据运算指令有如下四类:
binop vAA, vBB, vCC
:将 vBB 寄存器与 vCC 寄存器进行运算,结果保存到 vAA 寄存器。以下类似binop/2addr vA, vB
binop/lit16 vA, vB, #+CCCC
binop/lit8 vAA, vBB, #+CC
第一类指令可归类为:
add-type
:vBB + vCCsub-type
:vBB - vCCmul-type
:vBB * vCCdiv-type
:vBB / vCCrem-type
:vBB % vCCand-type
:vBB AND vCCor-type
:vBB OR vCCxor-type
:vBB XOR vCCshl-type
:vBB << vCCshr-type
:vBB >> vCCushr-type
:(无符号数)vBB >> vCC
smali 语法
类声明:
字段声明:
方法声明:
需要注意的是,在一些老教程中,会看到 .parameter
,表示使用的寄存器个数,但在最新的语法中已经不存在了,取而代之的是 .param
,表示方法参数。
接口声明:
注释声明:
循环语句
比如下面的 Java 代码:
对应下面的 smali:
switch 语句
对应下面的 smali:
根据 switch 语句的不同,case 也有两种方式:
try-catch 语句
对应的下面的 smali:
更多资料
《Android软件安全与逆向分析》
Last updated