安卓逆向学习----Dalvik虚拟机

Dalvik名字来源于其作者祖先居住的小村庄,老外喜欢起这种名字,类似的还有ubuntu、Kali,虽说现在使用ART取代了Dalvik,但是感觉简单学习一下还是有用的。

一、.与java虚拟机对比

Java虚拟机解析class文件,Dalvik虚拟机解析dex(dalvik executable)文件

android SDK 的dx工具可以将java字节码转换为Dalvik字节码,对java类文件进行了压缩,去除了冗余信息,因此体积更小

架构不同,Java虚拟机基于栈结构,Dalvik基于寄存器架构,对于手机设备来说后者更适用,并且速度更快

下面以Hello.java 为示例文件,分析两种字节码的区别

public class Hello {
	public int foo(int a,int b){
		return (a+b)*(a-b);
	}

	public static void main(String[] argc){
		Hello hello=new Hello();
		System.out.println(hello.foo(5,3));
	}
}

1. javac Hello.java 将其编译为Hello.class
2. ./dx --dex --output=Hello.dex Hello.class 将class文件转化为dex文件

3. javap -c -classpath . Hello得到反编译代码,这是jvm指令集,这里只列出foo函数对应代码。

java字节码一个指令为一个字节,PC计数器以字节为单位记录指令偏移量,图中PC对应的指令为iadd,前两行指令取出了函数两个参数并加载到求值栈


4.dexdump -d Hello.dex  得到的是dalvik指令集代码,这里只列出foo函数对应代码,左边的上半部分和下半部分分别对应指令的十六进制和助记符格式。Dalvik维护一个pc计数器和一个调用栈,但是这个调用栈维护的是一份寄存器列表。

二、安卓系统如何启动及dalvik虚拟机如何运行

1.系统架构以及运行流程

(1)Loader层:加载和运行引导程序

         boot ROM:开机时,引导芯片从rom中预设的代码开始执行,然后将引导程序加载到ram。

         boot loader:运行引导程序,主要是检查ram、初始化参数等。

(2)kernel层:Android内核层,在这里开机刚刚完成进入系统 

         启动swapper进程(pid=0的进程),这是系统初始化过程kernel创建的第一个进程,用于初始化进程管理、内存管理、加载驱动等工作

         启动kthreadd进程,这是linux系统的内核进程,会创建内核工作线程kworkder、软中断进程ksoftirqd和thermal等内核守护进程,kthreadd是所有内核进程的父进程。

(3)native层(C++ Framework层):包含C++库、硬件抽象层(HAL)、Dalvik虚拟机或者安卓运行时(ART)

        由init进程孵化出用户空间的守护进程、开机动画、hal层等。init是linux的守护进程,是所有用户空间进程的父进程.

        init进程孵化出MediaServer进程,负责启动和管理C++Framework层,包含AudioFlinger,Camera Service等服务。

        特别的,init进程会解析init.rc文件,然后孵化出zygote进程,zygote进程是Android系统的第一个java进程(虚拟机进程),zygote是所有java进程的父进程

(4)java Framework层(application Framework层):这一层由java语言编写

        zygote进程负责加载ZygoteInit类、加载虚拟机、提前加载类preloadClasses、提前加载资源preloadResource

        zygote进程孵化出System Server进程,它负责启动和管理整个Java Framework,包含ActivityManager、PackageManager、WindowManager等服务

(5)App层:这一层与用户直接交互,应用使用的是java语言开发

         Zygote进程孵化出的第一个App进程是Luncher,就是桌面App,然后孵化出browser、phone、Email等基础的App进程,一个App至少运行在一个进程上。

         所有的App进程都是由Zygote进程fork而来

(6)Syscall和JNI

          Native和kernel之间是系统调用(Syscall)层

          Java层与Native层之间的纽带是JNI(Java Native Interface)

2.dalvik虚拟机如何运行

上文已经介绍了Zygote进程,Zygote fork出其他进程之后,执行的工作就有Dalvik来进行。

它首先通过LoadClassFromDex()函数完成类的装载工作,类被解析之后有一个ClassObject类型的数据结构储存在运行时环境中,虚拟机使用gDvm.loadedClasses全局哈希表来储存和查询所有装载的类;

字节码验证器使用verifyCodeFlow()函数对装入的代码进行校验,随后虚拟机调用FindClass()函数查找和装载main方法类,随后调用dvmInterpret()函数初始化解释器并执行字节码流

3.Dalvik虚拟机JIT(just in time 即时编译)机制

JIT又称为动态编译,是一种在运行时将字节码翻译为机器码的技术,这使得程序执行速度更快

JIT有两种代码编译方式:Method方式和trace方式。分别以函数为单位和以trace为单位进行编译。简单说一下trace方式,函数的代码被分为许多条执行路径,根据执行的频繁与否被分为热路径和冷路径。trace方式能够快速的获取热路径,以更短的时间和更少的内存来编译代码

三、Dalik指令

Dalvik指令语言其实就是smali语言,指令这部分有点多,回头再研究

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

编译smali文件,生成dex文件

java-jar smali.jar -o classes.dex Helloworld.smali

在真机或者虚拟机测试运行

先将classes.dex压缩为Helloworld.zip

adb push Helloworld.zip /data/local/tmp
adb shell dalvikvm -cp /data/local/tmp/Helloworld.zip HelloWorld

正常运行会打印helloworld字符串

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