主流的DEX文件反汇编工具
目前反编译DEX文件的工具有BakSmail与Dedexer。两个反编译效果都不粗,虽然反编译出来的语句语法有点区别,但是相似度还是比较高的,
Dalvik虚拟机与Java虚拟机的区别
- Java程序经过编译,生成java字节码保存在class文件中,jvm通过解码class文件运行。
- Dalvik运行的是有java字节码转换过来的dalvik字节码,并将其打包到一个DEX(Delvik Executable)可执行文件中,Dalvik通过解释DEX文件来执行这些字节码。
- jvm基于栈架构,dalvik基于寄存器架构
- AndoridSDK中有个dx工具负责将java字节码转换为dalvik字节码,dx工具对java类文件重新排列,消除所有冗余信息,比如讲java类文件中常量池进行分解,消除冗余,重组成新的常量池,所有类文件共享它。
Dalvik 65536
- 因为Dalvik是基于寄存器架构,代码中大量使用到了寄存器,同时用到的寄存器是32位,64位类型用两个相邻的寄存器表示,而Dalvik最多能支持2的16次方即65535个寄存器,采用v0作为起始值,范围v0-v65536,在Android系统中,一个Dex文件中存储方法id用的是short类型数据,所以导致你的dex中方法不能超过65k。
Dalvik字节码类型描述符
语法 | 含义 |
---|---|
V | Void,只用于返回值类型 |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long |
F | float |
D | double |
L | java类类型 |
[ | 数组类型 |
Dalvik方法描述
- 方法格式:Lpackage/name/ObjectName;->MethodName()Z
- 方法代码以.method开始,以end.method结束,根据方法的不同,在方法指令前面可能有出现#号加以注释,如#virtual method代表一个虚方法,#direct method表示这个是直接方法。
寄存器表示方法(V P 命名法)
- V P 命名法,是Dalvik字节码中两种不同的寄存器表示方法。
- v命名,采用小写字母”v”开头表示函数中永达偶的局部变量与参数,所有的寄存器命名从v0开始,依次递增。
- p命名,对函数局部变量寄存器命名无影响,命名规则为:函数中引入的参数命名从p0开始,依次递增。
假设一个函数使用到M个寄存器,并且函数由N个参数,根据Dalvik虚拟机参数传递的规定:参数使用最后的N个寄存器,局部变量使用从v0开始的前M-N个寄存器。举个栗子
# virtual methods .method public foo(II)I .register 5 .parameter .parameter .prologue .line 3 add-int v0, p1, p2 sub-int v1, p1, p2 mul-int/2addr v0, v1 return v0 .end mehtod
foo()函数使用了5个寄存器,2个显式的整形参数,方法本身是类的非静态方法,函数被调用时会传入一个隐式的类对象引用,因此实际传入的参数是3个,根据传参规则,局部变量将使用前2个寄存器,参数会使用后三个寄存器。
Dalvik指令集
- Dalvik大多数指令用寄存器作为目的操作数或源操作数,一种A/B/C/D/E/F/G/H代表一个4位的数值,用老标识0~15的数值或者v0~v15的寄存器,而AA/BB/CC/DD/EE/FF/GG/HH代表一个8位的数值,用来标识0~255的数值或者v0~v255的寄存器,AAAA/BBBB……代表一个16位的数值0~65533或者v0~v65533的寄存器。
方法调用指令集
方法调用指令负责调用类实例的方法,基础指令为invoke,方法调用指令有”invoke-kind{vC,vD,vE,vF,vG},meth@bbbb”与”invoke-kind/range{vCCCC..vNNNN},meth@BBBB”两类,两类指令在作用上一样,只是后者在设置参数寄存器时使用了range来指定寄存器的范围。根据方法类型的不同,一共有5条方法调用指令。
“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” :调用实例的接口方法
在Android4.0版本后,Dalvik指令集增加了 “invoke-kind/jumbo{vCCCC..vNNNN},meth@BBBBBBBB” 这种类型的指令,作用一样,只是使用jumbo字节码后缀,并且寄存器与指令的索引范围更大。
方法调用指令的返回值类型必须使用move-result*指令来获取,如:
invoke-state {}, Landroid/os/Parcel:->obtain{}Landroid/os/Parcel;
move-result-object v0
空操作指令
空操作指令助记符位nop,它的值位00,通常此指令被用来作为对齐代码使用,没实际操作。
数据操作指令
数据操作指令为move,根据字节码的大小与类型不同,后面会跟上不同的后缀 move vA,vB 将vB寄存器的值赋给vA集群器,源寄存器与目的寄存器都位4位 move wide vA vB 为4位寄存器赋值 move/from16 vAA,vBBBB 将vBBBB寄存器的值赋给vAA寄存器,源寄存器为16位,目的寄存器为8位 move-object vA,vB 为对象赋值,源寄存器和目的寄存器都为4位 move-exception vAA 保存一个运行时发生的异常到vAA寄存器
返回指令
返回指令指的是函数结尾时运行的最后一条指令,基础字节码位return,共有四条返回指令 return-void 表示函数由一个void方法返回 return vAA 表示函数返回一个32位非对象类型的值,返回值为8位寄存器vAA return wide vAA表示函数返回一个64位非对象类型的值,返回值为8位寄存器vAA return-object vAA表示函数返回一个对象类型的值,返回值为8位急促器vAA
数据定义指令
数据定义指令用来定义常量、字符串、类等数据,基础字节码位const const/4 vA,#+B 将数值符号扩展为32位后赋值给寄存器vA const/16 vAA, #+BBBB 将数值符号扩展为32位后赋值vAA const-string vAA, string@BBBB通过字符串索引获取一个字符串并赋给寄存器vAA const-class vAA, type@BBBB 通过类型索引获取一个类引用并赋值给寄存器vAA
跳转指令
跳转指令用于从当前地址跳转到指定的偏移处,有三种跳转指令无条件跳转(goto)、分支跳转(switch)、条件跳转(if)。 goto +AA 无条件跳转到指定偏移处,偏移量AA不能为0。
if-test- vA,vB,+CCCC条件跳转指令,比较vA寄存器和vB寄存器的值,如果比较结果满足则跳转到CCCC指定的偏移处,CCCC不能为0,if-test类型指令有以下几条 if-eq 如果vA等于vB则跳转,对应java语法为if(vA == vB). if-ne 如果vA不等于vB则跳转,对应java语法为if(vA != vB) if-lt 如果vA小于vB则跳转……. if-le 如果小于等于 if-ge 如果大于等于 if-gt 如果大于
if=testz -vAA,+vBBBB条件跳转指令,拿vAA寄存器与0比较,如果比较结果满足或值为0就跳转到BBBB指定的偏移处
实例参考
// 空指令
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;-><init>{}V
//跳转指令
if-nez v0, :cond_0
goto : goto_0
:cond_0
//数据转换指令
int-to-float v2, v2
//数据运算指令
add-float v2, v2, v2
//比较指令
compl-float v0, v2, v2
//字段操作指令
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrantStream;
//构造字符串
const-string v1, "Hello World"
//方法调用指令
invoke-virtual{v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
//返回指令
:goto_0
return-void