Dalvik 和 Java 字节码的比较

以下博客文章描述了 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. 安卓应用架构
【Dalvik 和 Java 字节码的比较】JVM 字节码由一个或多个 .class 文件组成(每个文件包含一个 Java 类)。在运行时,JVM 将从相应的 .class 文件中动态加载每个类的字节码。而 Dalvik 字节码仅由一个.dex文件组成,包含应用程序的所有类。下图展示了 .dex 文件的生成过程。Java 编译器创建 JVM 字节码后,Dalvik dx编译器删除所有 .class 文件并将它们重新编译为 Dalvik 字节码。之后dx将它们合并为一个.dex 文件。这个过程包括对应用程序基本元素(常量池、类定义和数据段)的翻译、重构和解释。常量池描述了所有常量,包括引用、方法名称和数字常量。类定义包括访问标志、类名等。 数据段包括目标 VM 执行的所有函数代码以及类和函数的相关信息(例如 DVM 使用的寄存器数量、局部变量列表、操作数堆栈大小)和实例变量。
Dalvik 和 Java 字节码的比较
文章图片

  1. 寄存器结构
DVM 是基于寄存器的,而 JVM 是基于栈的。在 JVM 字节码中,局部变量会被列在局部变量列表中,然后被压入堆栈供操作码操作。此外,JVM 还可以直接在堆栈上工作,而无需将局部变量显式存储到变量列表中。在 Dalvik 字节码中,局部变量将分配给 16 个可用寄存器中的任何一个(原文216 个寄存器,疑似有误)。Dalvik 操作码不访问堆栈中的元素。相反,它们直接对寄存器进行操作。
  1. 指令集
Dalvik 有 218 个操作码,它们与 Java 中的 200 个操作码有本质的不同。例如,有十几种操作码用于在堆栈和局部变量列表之间传输数据,而在 Dalvik 中完全没有。Dalvik 中的指令比 Java 中的要长,因为它们中的大多数都包含寄存器的源地址和目标地址。有关 Dalvik 操作码的全面概述,请参阅 Gabor Paller 和 Android 开发人员的博客文章。
  1. 常量池结构
JVM 字节码需要从所有 .class 文件中循环迭代出了全部常量的常量池,例如引用的函数名称。通过为 Dalvik 中所有类的引用提供一个常量池,dx 编译器消除了迭代。此外,dx 通过使用内联技术删除了一些常量。因此,在 dx 编译期间,整数、长整数以及单浮点数和双浮点数常量都消失了。
  1. 模糊的原始类型
在 JVM 中,整数和单浮点常量的操作码是不同的,长整数和双浮点常量也是如此。相对应的 Dalvik 为整数和浮点常量实现了相同的操作码。
  1. 空引用
Dalvik 字节码没有特定的Null类型。相反,Dalvik 使用 0 值常量。因此,应正确区分常数 0 的含糊含义。
  1. 对象引用
JVM 字节码使用不同的操作码进行对象引用比较和空类型比较,而 Dalvik 将它们简化为一个操作码。因此,在反编译过程中必须恢复比较对象的类型信息。
  1. 原始类型数组的存储
Dalvik 使用不确定的操作码对数组进行操作,而 JVM 使用定义的操作码。必须恢复数组类型信息才能正确转换。

    推荐阅读