1. Dalvik虚拟机概述
Google于2007年底正式发布了Android SDK,Dalvik虚拟机也第一次进入了我们的视野。它的作者是丹·伯恩斯坦(Dan Bornstein),名字来源于他的祖先曾经居住过的名叫Dalvik的小渔村。Dalvik虚拟机作为Android平台的核心组件,拥有如下几个特点。
(1)体积小,占用内存空间小。
(2)专用的DEX可执行文件格式,体积更小,执行速度更快。
(3)常量池采用32位索引值,寻址类方法名、字段名、常量更快。
(4)基于寄存器架构,并拥有一套完整的指令系统。
(5)提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能。
(6)所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例。
Dalvik虚拟机与传统的Java虚拟机有着许多不同点,两者并不兼容,它们显著的不同点主要表现在以下几个方面。
(1)Java虚拟机运行的是Java字节码,Dalvik虚拟机运行的是Dalvik字节码。
(2)Dalvik可执行文件体积更小。
(3)Java虚拟机基于栈架构,Dalvik虚拟机基于寄存器架构。
2. Smali概述
Dalvik虚拟机(Dalvik VM)是Google专门为Android平台设计的一套虚拟机。区别于标准Java虚拟机JVM的class文件格式,Dalvik VM拥有专属的DEX可执行文件格式和指令集代码。Smali和Baksmali 则是针对 DEX 执行文件格式的汇编器和反汇编器,反汇编后DEX文件会产生.smali后缀的代码文件,Smali代码拥有特定的格式与语法,Smali语言是对Dalvik虚拟机字节码的一种解释。
Smali 语言起初是由一个名叫 JesusFreke 的黑客对 Dalvik 字节码的翻译,并非一种官方标准语言,因为 Dalvik 虚拟机名字来源于冰岛一个小渔村的名字,Smali和Baksmali 便取自冰岛语中的“汇编器”和“反编器”。目前,Smali 是在 Google Code上的一个开源项目。
虽然主流的DEX可执行文件反汇编工具不少,如Dedexer、IDA Pro,但Smali提供反汇编功能的同时,也提供了打包反汇编代码重新生成 DEX 的功能,因此 Smali 被广泛地用于App广告注入、汉化和破解,ROM定制等方面。
3. APKtool工具介绍
APKtool工具是在Smali工具的基础上进行封装和改进的,除了对DEX文件的汇编和反汇编功能外,还可以对 APK 中已编译成二进制的资源文件进行反编译和重新编译。同时也支持给Smali代码添加调试信息以支持断点调试。
在介绍APKtool使用之前,我们先来看一个APK包的组成。APK文件其实是zip压缩包格式,使用工具进行解压后,以HelloWorld.apk为例,如图1所示。
图1 解压文件
使用APKtool反编译HelloWorld.apk文件的方法,如图2所示。
图2 反编译文件
执行apktool d命令成功后会在HelloWorld目录下产生如下所示的一级目录结构,如图3所示。
图3 一级目录结构
在浏览各个子目录的结构后,我们可以发现其结构原始 App 工程目录结构基本一致,Smali目录结构对应原始的Java源码SRC目录,而META-INF目录已经不见了,因为反编译会丢失签名信息。反编译后会多生成apktool.yml文件,这个文件记录着APKtool版本和APK文件名和是否是framework文件等基本信息,在APKtool重新编译时会使用到。
4. Smali格式结构介绍
(1)文件格式
无论是普通类、抽象类、接口类或内部类,在反编译出的代码中,它们都以单独的Smali文件来存放。每个Smali文件头3行描述了当前类的一些信息,格式如下。
- .class<访问权限>[修饰关键字]<类名>
- .super<父类名>
- .source<源文件名>
打开HelloWorld.smali文件,头3行代码如下。
- .class public LHelloWorld;
- .super Landroid/app/Activity;
- .source "HelloWorld.java"
第1行“.class”指令指定了当前类的类名。在本例中,类的访问权限为public,类名为“LHelloWorld;”,类名开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串为一个类。
第 2 行的“.super”指令指定了当前类的父类。本例中的“LHelloWorld;”的父类为“Landroid/app/Activity;”。
第3行的“.source”指令指定了当前类的源文件名。经过混淆的DEX文件,反编译出来的Smali代码可能没有源文件信息,因此,“.source”行的代码可能为空。
前3行代码过后就是类的主体部分了,一个类可以由多个字段或方法组成。
(2)类的结构
无论普通类、抽象类、接口类还是内部类,反编译的时候会为每个类单独生成一个Smali文件,但是内部类相存在相对比较特殊的地方。
内部类的文件是“[外部类]$[内部类].smali”的形式来命名的,匿名内部类文件以“[外部类]$[数字].smali”来命名。
内部类访问外部类的私有方法和变量时,都要通过编译器生成的“合成方法”来间接访问。
编译器会把外部类的引用作为第一个参数插入到会内部类的构造器参数列表。
内部类的构造器中是先保存外部类的引用到一个“合成变量”,再初始化外部类,最后才初始化自身。