Dalvik 和 Java 位元組碼的比較

Yujiaao發表於2021-09-26

以下部落格文章描述了 Dalvik 和 Java 位元組碼的主要異同。這對於瞭解 Dalvik 和 Java 的不同之處尤為重要,以便能夠了解 Android 應用程式的特徵和惡意行為。

Android 應用程式通常用 Java 語言編寫,並在Dalvik 虛擬機器 (DVM) 中執行,這與經典的Java 虛擬機器 (JVM) 不同。DVM 由 Google 開發,針對移動作業系統(尤其是 Android 平臺)的特性進行了優化。在 Dalvik 中執行的位元組碼通過使用轉換工具 dx 翻譯 Java .class 檔案從傳統的 JVM 位元組碼轉換為 dex 格式. 與 DVM 不同,JVM 使用純 Java 類檔案。如果你想逆向一個Android應用程式,需要了解Dalvik位元組碼格式,以及你需要深入的靜態和動態檢測知識。威廉·恩克等作者在他們的論文《Android 應用程式安全性研究》中總結了 JVM 和 DVM 位元組碼之間的差異如下:

  1. 安卓應用架構

JVM 位元組碼由一個或多個 .class 檔案組成(每個檔案包含一個 Java 類)。在執行時,JVM 將從相應的 .class 檔案中動態載入每個類的位元組碼。而 Dalvik 位元組碼僅由一個.dex檔案組成,包含應用程式的所有類。下圖展示了 .dex 檔案的生成過程。Java 編譯器建立 JVM 位元組碼後,Dalvik dx編譯器刪除所有 .class 檔案並將它們重新編譯為 Dalvik 位元組碼。之後dx將它們合併為一個.dex 檔案。這個過程包括對應用程式基本元素(常量池、類定義和資料段)的翻譯、重構和解釋。常量池描述了所有常量,包括引用、方法名稱和數字常量。類定義包括訪問標誌、類名等。 資料段包括目標 VM 執行的所有函式程式碼以及類和函式的相關資訊(例如 DVM 使用的暫存器數量、區域性變數列表、運算元堆疊大小)和例項變數。

image.png

  1. 暫存器結構

DVM 是基於暫存器的,而 JVM 是基於棧的。在 JVM 位元組碼中,區域性變數會被列在區域性變數列表中,然後被壓入堆疊供操作碼操作。此外,JVM 還可以直接在堆疊上工作,而無需將區域性變數顯式儲存到變數列表中。在 Dalvik 位元組碼中,區域性變數將分配給 16 個可用暫存器中的任何一個(原文216 個暫存器,疑似有誤)。Dalvik 操作碼不訪問堆疊中的元素。相反,它們直接對暫存器進行操作。

  1. 指令集

Dalvik 有 218 個操作碼,它們與 Java 中的 200 個操作碼有本質的不同。例如,有十幾種操作碼用於在堆疊和區域性變數列表之間傳輸資料,而在 Dalvik 中完全沒有。Dalvik 中的指令比 Java 中的要長,因為它們中的大多數都包含暫存器的源地址和目標地址。有關 Dalvik 操作碼的全面概述,請參閱 Gabor PallerAndroid 開發人員的部落格文章

  1. 常量池結構

JVM 位元組碼需要從所有 .class 檔案中迴圈迭代出了全部常量的常量池,例如引用的函式名稱。通過為 Dalvik 中所有類的引用提供一個常量池,dx 編譯器消除了迭代。此外,dx 通過使用內聯技術刪除了一些常量。因此,在 dx 編譯期間,整數、長整數以及單浮點數和雙浮點數常量都消失了。

  1. 模糊的原始型別

在 JVM 中,整數和單浮點常量的操作碼是不同的,長整數和雙浮點常量也是如此。相對應的 Dalvik 為整數和浮點常量實現了相同的操作碼。

  1. 空引用

Dalvik 位元組碼沒有特定的Null型別。相反,Dalvik 使用 0 值常量。因此,應正確區分常數 0 的含糊含義。

  1. 物件引用

JVM 位元組碼使用不同的操作碼進行物件引用比較和空型別比較,而 Dalvik 將它們簡化為一個操作碼。因此,在反編譯過程中必須恢復比較物件的型別資訊。

  1. 原始型別陣列的儲存

Dalvik 使用不確定的操作碼對陣列進行操作,而 JVM 使用定義的操作碼。必須恢復陣列型別資訊才能正確轉換。

相關文章