安卓逆向學習----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字符串

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