Android逆向 - Dalvik可执行格式与字节码规范
Dalvik虚拟机
Dalvik虚拟机的特点
- 体积小,占用内存空间少。
- 专有的dex可执行文件格式,体积小,执行速度快。
- 常量池采用32位索引值,对类方法名、字段名、常量的寻址速度快。
- 基于寄存器架构,同时拥有一套完整的指令系统
- 提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等机制。
- Android程序都运行在Android系统中,每个进程都与一个Dalvik虚拟机实例对应。
Dalivk虚拟机与Java虚拟机的区别
- 运行的字节码不同
- Java虚拟机运行的是Java字节码,Dalivk虚拟机运行的是Dalvik字节码。
- Java程序编译后生成Java字节码保存在class文件中,Java虚拟机通过解码class文件运行程序。
- Dalivk运行的是Dalvik字节码,所有的Dalvik字节码都是又Java字节码转换的,并打包到DEX文件中,Dalvik虚拟机解析DEX文件来执行这些字节码
- Dalivk 可执行文件的体积更小
- 在Android SDK中,
dx
工具会负责将Java字节码转换成Dalvik字节码。 dx
工具会对所有Java类文件中的常量池进行分解,消除冗余的的信息,重新组合成一个新的常量池,并让所有类文件共享这个常量池。
- 在Android SDK中,
- 虚拟机架构不同
- Java虚拟机是基于栈架构,在程序运行时会频繁地对栈进行数据读写操作。
- Dalvik虚拟机是基于寄存器结构的,数据的访问是直接在寄存器之间传递。
Dalivk字节码与Java字节码对比
1、编写Java代码
public class Hello {
public int foo(int a, int b) {
return (a + b) * (a - b);
}
public static void main(String[] args) {
Hello hello = new Hello();
System.out.println(hello.foo(5, 3));
}
}
2、编译成class : javac hello.java
3、把class文件通过Android SDK工具转码成Dex文件格式 : d8 --output=d8out .\Hello.class
4、查看Java字节码 : javap -c -classpath . Hello
5、查看Dalvik字节码: dexdump.exe -d .\classes.dex
Java字节码
解析:
- 下面的foo()函数 占8个字节,代码中每条指令都占一个字节,并且没有参数。
- Java虚拟机的指令集也被称为零地址形式的指令集,零地址形式指令的源参数和目标参数都是隐含的,通过Java虚拟机提供的数据结构
求值栈
来传递。 - 对Java程序来说,每个线程在执行时都有一个PC计数器和一个Java栈。
- 看foo()函数, 左边的偏移量就是程序执行每行代码时PC寄存器的值
iload
是load指令中的一条,i是指令前缀,表示操作类型是int; load表示将局部变量存入Java栈。(类似指令:lload(long类型),fload(float类型),dload(double类型)入栈)- 下划线右边的数字表示,要操作的是那个局部变量,索引值从0开始计数,例如
iload_1
表示使第2个int类型的局部变量入栈,而这个局部变量都是存放在局部变量区foo()函数中的第2个参数 -
下面的指令:
iload_2
取第3个参数;iadd
从栈顶弹出两个int类型的值并求它们的和;iload_1
和iload_2
分别再次把第2个参数和第3个参数压入栈;isub
从栈顶弹出两个int类型的值并求它们的差;imul
从栈顶弹出两个int类型的值并求它们的积;ireturn
返回一个int类型的值。Compiled from "Hello.java" public class Hello { public Hello(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public int foo(int, int); Code: 0: iload_1 1: iload_2 2: iadd 3: iload_1 4: iload_2 5: isub 6: imul 7: ireturn public static void main(java.lang.String[]); Code: 0: new #2 // class Hello 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_1 12: iconst_5 13: iconst_3 14: invokevirtual #5 // Method foo:(II)I 17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 20: return }
Dalvik字节码
解析:
- 相比Java字节码, Dalvik字节码只用了4个步骤就完成了这个操作。
add-int
将v2,v3 寄存器中的值相加,将结果保存到v0寄存器中;v2,v3表示foo()函数的第1个参数和第2个参数sub-int
将v2,v3 寄存器中的值相减, 将结果保存到第一个寄存器中,就是v2中(2addr 和sub-int v1 的区别,后者要多一个寄存器)mul-int
将v0,v2寄存器中的值相乘,结果保存在v0寄存器中- return 返回v0 寄存器的值
-
Dalvik 虚拟机在运行时也会为每个线程维护一个PC计数器和一个调用栈,与Java不同,这个调用栈维护了一个寄存器列表,寄存器数量会在方法结构体registers中给出。
Opened '.\classes.dex', DEX version '035' Class #0 - Class descriptor : 'LHello;' Access flags : 0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - Direct methods - #0 : (in LHello;) name : '<init>' type : '()V' access : 0x10001 (PUBLIC CONSTRUCTOR) code - registers : 1 ins : 1 outs : 1 insns size : 4 16-bit code units 00016c: |[00016c] Hello.<init>:()V 00017c: 7010 0400 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0004 000182: 0e00 |0003: return-void catches : (none) positions : 0x0000 line=1 locals : 0x0000 - 0x0004 reg=0 this LHello; #1 : (in LHello;) name : 'main' type : '([Ljava/lang/String;)V' access : 0x0009 (PUBLIC STATIC) code - registers : 4 ins : 1 outs : 3 insns size : 17 16-bit code units 000184: |[000184] Hello.main:([Ljava/lang/String;)V 000194: 2203 0100 |0000: new-instance v3, LHello; // type@0001 000198: 7010 0000 0300 |0002: invoke-direct {v3}, LHello;.<init>:()V // method@0000 00019e: 6200 0000 |0005: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000 0001a2: 1251 |0007: const/4 v1, #int 5 // #5 0001a4: 1232 |0008: const/4 v2, #int 3 // #3 0001a6: 6e30 0100 1302 |0009: invoke-virtual {v3, v1, v2}, LHello;.foo:(II)I // method@0001 0001ac: 0a03 |000c: move-result v3 0001ae: 6e20 0300 3000 |000d: invoke-virtual {v0, v3}, Ljava/io/PrintStream;.println:(I)V // method@0003 0001b4: 0e00 |0010: return-void catches : (none) positions : 0x0000 line=7 0x0005 line=8 0x0010 line=9 locals : 0x0000 - 0x0011 reg=3 (null) [Ljava/lang/String; Virtual methods - #0 : (in LHello;) name : 'foo' type : '(II)I' access : 0x0001 (PUBLIC) code - registers : 4 ins : 3 outs : 0 insns size : 6 16-bit code units 000150: |[000150] Hello.foo:(II)I 000160: 9000 0203 |0000: add-int v0, v2, v3 000164: b132 |0002: sub-int/2addr v2, v3 000166: 9200 0002 |0003: mul-int v0, v0, v2 00016a: 0f00 |0005: return v0 catches : (none) positions : 0x0000 line=3 locals : 0x0000 - 0x0006 reg=1 this LHello; 0x0000 - 0x0006 reg=2 (null) I 0x0000 - 0x0006 reg=3 (null) I source_file_idx : 1 (Hello.java)
虚拟机的执行流程
- Android 系统由Linux内核、函数库、Android运行时、应用程序框架及应用程序组成,Android系统结构采用了分层的思想,Dalvik虚拟机属于Android运行时环境,它与一些核心库一起承担了Android应用程序的运行工作
- Android 系统启动并加载内核后,会立即执行init进程,在读取init.rc文件并启动系统中重要外部程序zygote.
- Zygote是Android系统中所有进程的孵化器进程,它启动后,会初始化Dalvik虚拟机,再启动system_server进程并进入Zygote模式,通过socket等候命令的下达。
- 执行程序时,system_server进程通过Binder IPC方式将命令放送给Zygote,它收到命令后通过fork其自身创建一个Dalvik虚拟机的实例来执行应用程序的入口函数,从而启动程序。
虚拟机的执行方式
- 即使编译又称为动态编译, 是通过在运行时将字节码翻译为机器码使程序的执行速度加快。
- 主流的JIT包括两种字节码编译方式:
2.1、method方式: 以函数或方法为单位进行编译
2.2、trace方式:以trace为单位进行编译;指能快速获取热路径的代码,从而采用更短的方式编译
版权所有:Ljierui'Blog
文章标题:Android逆向 - Dalvik可执行格式与字节码规范
文章链接:https://fuckdog.org/post-56.html
本站文章均为原创,未经授权请勿用于任何商业用途
文章标题:Android逆向 - Dalvik可执行格式与字节码规范
文章链接:https://fuckdog.org/post-56.html
本站文章均为原创,未经授权请勿用于任何商业用途