深入探索 APKTool:Android 应用的反编译与重打包工具
The following article is from 陆业聪 Author 陆业聪
apktool 是一个非常强大的工具,用于反编译和重新打包 Android 应用程序(APK 文件)。这个工具主要用于应用程序的逆向工程,调试,以及修改已经编译的 APK 文件。本文将详细解释 apktool 的工作原理和使用过程。
1.1 解压 APK
1.2 DEX 文件转换
1.3 资源解码
Android 应用的资源文件(如 XML 布局和 resources.arsc)在 APK 中通常是以编译形式存在的。apktool 能够解码这些资源,将它们转换回原始的、可编辑的格式。例如,它可以将编译后的 XML 文件转换回可读的 XML 文件。
2.1 资源重新编译
2.2 smali 转换为 DEX
2.3 打包 APK
2.4 签名 APK
为了能在 Android 设备上安装和运行,新打包的 APK 需要被签名。这通常是使用 jarsigner 工具或 Android Studio 提供的签名工具完成的。签名确保了 APK 的来源和完整性。
3.1 Smali/Baksmali
3.1.1 DEX 文件格式
DEX 文件结构
│
├── Header
│ ├── 文件大小
│ ├── 版本号
│ └── 校验和
│
├── String Table
│ ├── 字符串1 (类名、方法名、字段名等)
│ ├── 字符串2
│ ├── ...
│ └── 字符串N
│
├── Type Table
│ ├── 类型1 (通过String Table索引)
│ ├── 类型2
│ ├── ...
│ └── 类型N
│
├── Prototype Table
│ ├── 原型1 (函数返回类型和参数类型)
│ ├── 原型2
│ ├── ...
│ └── 原型N
│
├── Field Table
│ ├── 字段1 (名称和类型)
│ ├── 字段2
│ ├── ...
│ └── 字段N
│
├── Method Table
│ ├── 方法1 (名称、返回类型和参数)
│ ├── 方法2
│ ├── ...
│ └── 方法N
│
├── Class Definitions
│ ├── 类1
│ │ ├── 访问权限
│ │ ├── 父类
│ │ ├── 接口
│ │ ├── 源文件名
│ │ ├── 静态值
│ │ ├── 字段
│ │ └── 方法
│ ├── 类2
│ ├── ...
│ └── 类N
│
└── Data Section
├── 类1数据
│ ├── 代码实现
│ └── 静态数据
├── 类2数据
├── ...
└── 类N数据
Header:包含了DEX文件的基本信息,如文件大小、版本号和校验和,这是文件的起始部分,为解析工具提供了文件的基本属性。 String Table:这是一个全局的字符串列表,存储了所有用到的字符串值,包括类名、方法名和字段名等。 Type Table:存储所有类型的列表,每个类型通过String Table中的索引来引用。 Prototype Table:定义了所有函数的原型,包括函数的返回类型和参数类型。 Field Table:列出所有类中的字段,包括字段的名称和类型。 Method Table:列出所有方法,包括方法名、返回类型和参数。 Class Definitions:包含所有类的详细信息,如类的访问权限、父类、接口、源文件名、静态值以及类中定义的方法和字段。 Data Section:包含所有类的数据,如代码实现和静态数据。
3.1.2 Smali 语法
3.1.2.1 指令
.method 和 .end method:定义一个方法的开始和结束。 .class:指定类的声明。 .super:指明当前类的父类。 .field:声明类中的字段。
3.1.2.2 寄存器
局部寄存器(v0, v1, v2, ...):用于存储方法内的临时数据。 参数寄存器(p0, p1, p2, ...):用于存储传递给方法的参数。在非静态方法中,p0 通常用于 this 引用。
3.1.2.3 操作码
invoke-virtual:调用对象的虚方法。 invoke-static:调用静态方法。 move:将数据从一个寄存器移动到另一个寄存器。 if-eq:如果两个寄存器中的值相等,则跳转到指定的标签。
3.1.2.4 注释
3.1.2.5 示例代码解析
.method public onClick(Landroid/view/View;)V
.locals 1 # 定义一个局部寄存器
iget-object v0, p0, Lcom/example/MyActivity;->button:Landroid/widget/Button;
# 从 p0 (this) 的 button 字段获取对象,存储到 v0
invoke-virtual {v0}, Landroid/widget/Button;->performClick()Z
# 调用 v0 (Button 对象) 的 performClick 方法
return-void
.end method
3.1.2.6 进阶 Smali 概念
标签和跳转:Smali 支持使用标签来标记代码中的位置,并使用跳转指令(如 goto、if-eq 等)来实现条件执行。 数组操作:Smali 提供了操作数组的指令,如 aput 和 aget,用于在数组中存取数据。 异常处理:通过 .catch 指令来处理方法中可能抛出的异常。
3.1.3 baksmali 的转换过程
读取 DEX 文件:baksmali 首先读取 DEX 文件,这个文件包含了应用的所有编译后的字节码。DEX 文件本身包括一系列的类定义、方法、字段和其他数据结构。 解析 DEX 结构:DEX 文件具有特定的格式,包括头部信息、字符串表、类型表、字段和方法表等。baksmali 解析这些结构以理解文件中的数据布局和内容。 反汇编字节码:对于 DEX 文件中的每个方法,baksmali 将其包含的字节码指令序列转换为 smali 指令。这一步是反汇编过程的核心,涉及将低级的字节码指令(如操作寄存器的指令、分支、调用等)转换为相对易懂的 smali 格式。 生成 smali 文件:每个类的方法被转换成 smali 代码后,baksmali 会为每个类生成一个 smali 文件。这些文件将包含类的定义、字段、方法以及方法中的 smali 指令。 处理类关系和层次结构:在生成 smali 文件的过程中,baksmali 也会处理类之间的继承关系和接口实现,确保这些关系在 smali 代码中得到正确表示。 输出结果:最终,baksmali 输出一系列的 smali 文件,每个文件对应 DEX 文件中的一个类。这些文件现在可以被人类阅读和编辑,也可以被用于进一步的分析或修改。
寄存器操作:DEX 字节码操作的是寄存器而不是栈,这与 Java 字节码有所不同。baksmali 在转换过程中会保留这种寄存器操作的形式。 类型安全和检查:在反汇编过程中,baksmali 也会尝试解析和表示类型信息,以确保转换后的 smali 代码在类型安全和逻辑上是准确的。
3.2 资源处理
3.2.1 resources.arsc 的格式
resources.arsc 文件结构
│
├── Header
│ ├── 类型
│ └── 版本
│
├── String Pool
│ ├── 字符串1 (例如资源名称、值等)
│ ├── 字符串2
│ ├── ...
│ └── 字符串N
│
└── Resource Table
├── Package 1
│ ├── Type 1 (例如 drawable)
│ │ ├── Key 1 (例如 icon.png)
│ │ ├── Key 2 (例如 background.jpg)
│ │ └── ...
│ ├── Type 2 (例如 layout)
│ │ ├── Key 1 (例如 main_activity.xml)
│ │ ├── Key 2 (例如 settings_activity.xml)
│ │ └── ...
│ └── ...
├── Package 2
│ ├── Type 1 (例如 string)
│ │ ├── Key 1 (例如 app_name)
│ │ ├── Key 2 (例如 hello_world)
│ │ └── ...
│ ├── Type 2 (例如 style)
│ │ ├── Key 1 (例如 AppTheme)
│ │ ├── Key 2 (例如 DialogTheme)
│ │ └── ...
│ └── ...
└── ...
Header:这部分包含了文件的基本信息,如类型和版本,它是文件的起始部分,为解析工具提供了文件的基本属性。 String Pool:这是一个集中存储所有字符串的区域,包括资源的名称和值。这些字符串在资源表中通过索引被引用,以减少文件大小和避免重复。 Resource Table:这是文件的核心部分,包含了所有资源的具体信息。它按照包、类型和键的结构进行组织: Package:代表一个资源包,一个应用可以有一个或多个包,通常对应于应用的不同模块或库。 Type:资源的类型,如 drawable、layout、string 等。 Key:具体的资源条目,如特定的图片、布局文件或字符串名称。
3.2.2 apktool 如何解码 resources.arsc
解析文件结构:apktool 首先读取并解析 resources.arsc 文件的二进制结构,包括头部信息、字符串池、资源表等。 读取和转换字符串池:字符串池中的字符串是编码存储的,apktool 需要将这些编码后的字符串转换为人类可读的形式。 构建资源映射:通过解析资源表和相关的包、类型、键结构,apktool 构建一个资源映射,这个映射允许它理解每个资源的具体信息和位置。 资源解码:使用上述映射,apktool 可以将编译后的资源(如布局文件中的引用)解码回它们原始的、可读的格式。例如,它可以将资源 ID 解码为对应的资源名称,使得资源引用在 XML 文件中更易于理解和编辑。 输出可编辑的资源文件:最后,apktool 将解码后的资源和信息输出为可编辑的文件格式,如 XML 文件,这些文件可以被开发者进一步编辑和修改。
通过这种方式,apktool 不仅能够还原出可编辑的资源文件,还能保持资源之间的引用关系和应用的结构完整性。这使得开发者可以轻松地修改和调试 APK 文件中的资源,而无需访问原始的源代码。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!