partone
APK 格式
你开发的应用将被打包成APK文件,你可以从谷歌获得Play商店或其他渠道找到它。换句话说,对于手机上的任何一个应用程序,有相应的APK文件(包括预装的应用程序也不例外)。
apk文件实际上也是一个zip文件,所以你拿到它以后,可以将其重命名,然后解压得到里面的文件。
条目 | 说明 |
---|---|
AndroidManifest.xml | manifest的二进制文件 |
classes.dex | 程序的代码将被编译成dex文件 |
resources.arsc file | 包含预编译的程序资源,二进制的xml格式 |
res/ | 文件夹中包括未被编译打包进入resources.arsc的资源 |
assets/ | 可选的文件夹,包含应用的assets文件,由AssetManager进行检索 |
lib/ | 可选的文件夹,包含已编译的代码,例如jni的libraray |
META-INF/ | 文件夹中包含MANIFEST.MF 文件,APK的签名文件也在这个文件夹中 |
什么是DEX?
简单讲,DEX/Dalvik执行文件是一种Android平台上的文件格式,里面包含编译好的代码,可以被Dalvik虚拟机或者Android Runtime (ART)读取识别。
当一个APK文件是由Android编译系统产生的(就像当你在Android Studio 上运行你的应用)。首先,Java类会被编译成.class
文件,后来,DX
将在这些文件转换DEX格式。 DX是Android交叉编译器Build Tools
的一部分,你可以在以下位置找到它:
$ANDROID_SDK/build-tools
关于DEX文件的详细信息请点击 这里.
如何得到apk文件
这里有以下几种方式:
- 如果你想随便找一个应用,可以使用一些网站,通过浏览器直接下载到你的桌面
- 如果应用已经装在你的手机上了,你可以使用备份软件,例如这个,然后把他拷贝到你的手机内存或者SD卡的公共文件夹
- 在
/system/app
文件夹中,你可以找到预装应用 ,例如calculator, Chrome, camera, … 这取决于你手机上的具体ROM。 - 在
/data/app
中,你可以找到用户安装的应用。为了从手机中导出apk,你想要使用命令行列出可用的应用(记住使用usb连接手机):
adb shell pm list packages -f
Having the path of the APK file, you can now pull it:
有了apk文件的路径,你就可以将其导出了:
adb pull -p PATH/base.apk OUTPUT.apk
-p
选项可以展示传输文件的进度,如果导出文件的名称没有指定,默认会使用base。apk
adb pull -p
你可能对应用如何备份你的apk感到好奇:事实上,如果用手机上安装的文件管理系统去访问/data/app
文件夹, 你会发现很多情况下是无法访问的。
但是,确实是可以通过编程方式访问用户安装应用的apk文件。
首先,您需要检索应用程序的列表:
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> infos = getPackageManager().queryIntentActivities(mainIntent, 0);
然后,通过resolveinfo
,您可以访问applicationinfo
类中的publicsourcedir
字段,这是SourceDir
中公开部分的完整路径,包括资源和manifest。
File apkFile = new File(pkgInfo.activityInfo.applicationInfo.publicSourceDir);
if(apkFile.exists()) {
...
}
AAR 格式
aar包是一个Android Library 项目的二进制分发:例如,Android Support Library ,你可以使用此格式将其添加到你的应用程序中。另外,如果你的Android项目的发布形式是一个Library,而不是商店的应用程序,采用这种格式再好不过了。
aar文件实际上也是一个zip文件,所以你拿到它以后,可以将其重命名,然后解压得到里面的文件。
你可以在aar文件中找到以下内容:
|条目 | 必选|说明| | -- | -- | -- | |AndroidManifest.xml |必须的| manifest的XML源文件| |classes.jar |必须的| Java classes打出的jar文件| |res/ |必须的| 该文件夹用于存放使用到的资源| |R.txt |必须的|使用 aapt --output-text-symbols
的输出内容.它是library使用到的资源的清单 ( 包括strings, colors, dimens, attrs, layouts, ...). | |assets/| 可选的| 存放assert资源文件的文件夹| |libs/.jar |可选的|该文件夹用于存放library使用到的jar文件 | |jni//.so |可选的| 该文件夹用于存放library使用到的jni文件| |proguard.txt |可选的|Proguard配置文件| |lint.jar |可选的| 自定义Lint规则.|
aar和apk相比,唯一的区别是的AndroidManifest.xml
和res
文件夹下的XML文件,他们都是普通的XML,所以你可以很容易地打开它们。
请注意,例如,经常被我们作为项目依赖的Support Library是AAR的格式,您可以在以下路径找到他们:
$ANDROID_SDK/extras/android/m2repository/com/android
在本系列的其余部分,我将需要关注APK格式,因为装在手机上的应用就是这种格式的,也是这种格式分发到谷歌Play商店或其他渠道。
在下一篇文章中,我将介绍aapt和dex2jar 两个工具,你可以使用它们通过分析从apk文件中获取很多重要的信息。
parttwo
AAPT
如果你安装了Android SDK,那么你就有aapt了。事实上,“Android Asset Packaging Tool”是android 构建工具中的一部分,你可以在以下路径中找到它,例如:
ANDROID_SDK_HOME/build-tools/23.0.2
请注意,你会发现编译工具的每个版本都会有一个单独的文件夹:当你使用 Android SDK Manager去安装新版本的Build tools的时候,现有的版本并没有被覆盖,而是为其单独创建文件夹。这让你可以在不同版本的工具之间切换。
在使用Android Studio的时候,你一定记得在model对应的build.gradle
脚本文件中设置Build Tools的版本。
这个工具是Android构建系统中的一部分,它允许您查看、创建和更改ZIP兼容文件(例如zip,jar,APK)。它还可以将资源编译成二进制资源。
Details about how the Android Build System works are beyond this article, but aapt is mainly used in the process to:
关于Android构建过程的具体细节不在本文的讨论范围之内,但是,你需要知道aapt主要有以下几个作用:
- 生成R.java文件,这样是对资源文件的初步处理
- 将Android manifest、资源、assets等装入APK文件
- Add to the APK file the compiled classes, which have been already converted to the dex format by the dx tool.
- 编译class,使用dx工具将其转化成dex,添加到APK文件中。 更为详细的构建过程请看官方文档.
同时,aapt还可以用来从一个apk文件提取一些信息。
If you would like to try the same commands, you just need to get one APK file as I have already explained in the first article.
如果你还想尝试那些命令,你可以使用我在第一篇中提供的apk文件。
PACKAGE CONTENT
获取apk文件中的文件清单,只需要如下简单的指令:
aapt list FILENAME.apk
在其后面添加 -v
,你将知道更多关于上述文件的信息,例如文件的大小,创建的日期时间,CRC-32循环冗余校验码等。
PACKAGE DETAILS
使用dump
指令,你可以找到更为详细的信息。
添加badging
选项,会打印更多信息,如包名称、版本名称、版本号、权限、支持屏幕、启动时的Activity
、应用程序名称和图标文件,…
aapt dump badging FILENAME.apk
permissions
选项可以打印(和Android manifest文件中的包名称对应)应用所需的权限。请注意,只有在清单中显式声明的权限才会被列出来。例如, android.permission.WRITE_EXTERNAL_STORAGE
隐式要求权限android.permission.READ_EXTERNAL_STORAGE
,但是该权限却不会出现在列表中。 aapt dump permissions FILENAME.apk
aapt dump permissions
configurations
选项会打印出apk的configurations:
aapt dump configurations FILENAME.apk
resources
指令会打印出APK文件的resource table 。 因此,你所得到的将是应用引用的所有资源列表,包括attributes, strings, dimens, layouts, styles, menus, drawables, …
您还可以从应用的依赖库得到对应的资源:例如,如果appcompat-v7
是项目中的一个依赖,那么他的资源也会被列出来。
aapt dump resources FILENAME.apk
最后一个命令xmltree
,他十分有用:它可以打印出asset中编译好的xml文件。正如我在第一篇文章所提到的,xml文件是以二进制文件的形式被打包到apk文件中。所以你不能使用编辑器或者阅读器把它打开。但是,使用这个命令,你至少读起来更容易一些。
aapt dump xmltree FILENAME.apk RESOURCE.xml
aapt dump xmltree
这就是aapt中比较重要的命令,但是,我觉得你还是有必要去看看所有的参数。
正如你所看到的,使用这些简单的命令,就可以获取应用程序的一些细节信息,但它只是只读的,而你不能改变任何东西的apk文件。 在这篇文章中,我原本打算提过一下** dex2jar **以及如何使用它来反编译Android应用程序,但现在看有很多信息需要整理消化,所以这是顺延到下一个博客.
partthree
在这篇文章中我将重点讲述dex2jar,它是一个作用于Android .dex文件和Java .class文件的工具。已经有一些参照文章,但是你可以点击这里进入官方网站。
正如你所期望的那样,这个工具的核心功能就是转换APK的classes.dex文件为classes.jar文件(或任何你选择的工程),反之亦然。所以使用任何Java反编译工具来查看Android应用的源代码是可能的。
你从.class文件中得到的是什么,不要期望得到应用程序开发者所写的Java源代码,然后,正如稍后你所看到的,这些源代码是完全可以获得来阅读查看的。
安装
为了得到最新的可用版本,请到官方的repositories下载。
安装进程非常简单:你只需要将这个安装包解压到你指定的文件夹同时添加这个路径到你的path环境变量下。这样,你就可以开始使用dex2jar!
注:您需要文件夹包含脚本文件的执行权限。
在写这篇文章时,dex2jar最新稳定的版本是2.0,因此,文本中使用的是最新的版本。
version: reader-2.0, translator-2.0, ir-2.0
在挖掘dex2jar工具的核心功能之前,你如果仔细看了下dex2jar文件夹的内容,你能注意到一些可运行脚本(Unix/Mac和Windows系统版本)。这是因为dex2jar工具的每个核心功能通过分开的脚本提供,我认为这是一个很好的解决方案,所以你不需要为了一次执行传递太多的参数。
现在让我们转移到dex2jar工具的核心功能上:转换DEX classes文件为JAR文件。
如果你阅读了这个系列的第一篇文章,你知道一个APK文件中可执行代码以dex格式存在,它被定义为Android字节码格式文件。当然,为了分析一个应用程序最好是能有一个容易阅读的代码格式,这就是dex2jar能帮助你的。
在使用第一个命令之前,我们需要选择从Google Play Store上选择一个应用同时安装一个反编译工具来查看源码(另一方面我们获得是.class文件不是.java文件)。
我们即将测试的第一个命令,让你提取应用程序的可执行代码转换为JAR格式:
d2j-dex2jar.sh -f -o classes.jar FILENAME.apk
使用-o选项,可以让你指定这个命令的输出文件名称,同时-f选项告诉dex2jar如果文件存在就进行覆写。
如果你通过JD-GUI打开输出的文件,你可以看到应用程序的源代码,这次,你看到的并不是完全的Java类文件,但是它足够清楚地阅读。带下划线的项是可点击的,所以向导指引代码很容易。
源代码在哪里?
如果你仔细的看截图,你会注意到用数字替换资源名称,如R.id.SOMETHING, R.layout.SOMETHING, … 。正如官方文档说明这些是资源文件在R.java文件中对应的所有的id、layout、drawable等索引。这些文件在编译的时候自动生成(不允许修改,自动生成!),同时可以在你项目的.../build/generated/source/r/...文件夹下找到。本质上它是一个final class类,同时它为每个资源类型文件定义static final class内部类,例如strings, layouts, arrays, colors, dimens, …,同时为每个资源类型定义static final类型的int field(字段),即你的资源文件的IDS。这样,在实现应用程序的时候,你不必直接处理数字。
IDS被存储为16进制数,因此通过dex2jar文件的getStringArray方法(你可以在上面的截图中看到)提取出0x7f080000 ( 原始应用程序源代码中R.array.pick_color_array的资源文件)是2131230720 (decimal)
混淆代码
我选择这个应用程序是因为我知道它的代码已经通过ProGuard进行混淆了。你能清楚地看到几个包和类的名称为a,b,c...
在下面的截图中,它是ProGuard所做工作的的见证,ProGuard改变了方法和变量的名称来混乱代码的连贯性。然而,代码仍然是可读的同时它不是特别难找到和理解特定的算法或业务逻辑。
当让,并不是所有的类都被混淆了,这是依靠声明在ProGuard配置文件中的特定的规则,例如这个例子的应用程序,你可以在GitHub的这里找到它。
内存溢出错误
当前的文档没有更新关于这个话题的,反正它是可能的,当转换DEX文件为JAR或viceversa,会得到OutOfMemoryError问题:就像你正在试图转换一个很大的dex文件。
为了防止这个问题,你需要增加JVM的内存大小,你必须打开d2j_invoke脚本同时找到下面的一段话:
java -Xms512m -Xmx1024m -classpath "${_classpath}" "$@"
根据你的系统和需求改变这个值,就我个人而言,我只需要改变内存池的大小,如:-Xmx2048m。
回到DEX
dex2jar也支持从.class文件到.dex文件的转换,这是非常有趣的,因为它让可执行代码从新打包到apk文件中成为可能。
具体的指令是:
d2j-jar2dex.sh -f -o classes.dex classes.jar
使用-o选项,你可以通过指令指定输出的名称,同时-f告知dex2jar如果文件存在则重写文件。
apk文件本质上是一个ZIP文件,所以新的DEX文件可以插入后很容易:
zip -r FILENAME.apk classes.dex
记得这个操作会改变原始的APK文件,所以你不能简单地将它安装在您的设备,你得使用Jarsigner重新为APK签名为了再生manifest.mf文件。这是一种不改变应用程序的安全保障:如果你这样做,你要放弃这个包,因为没有原来应用程序的密钥库和私钥,你更改后的APK不能作为原来APK的一个更新发布。
当系统安装更新到一个应用程序时,它将与现有版本中的新版本的证书进行比较。如果证书匹配,该系统允许更新。
SMALI
来自官方文档:
smali/baksmali是dex文件的assembler/disassembler程序,通过dalvik,Android’s Java VM实现。语法是基于松散的Jasmin’s/dedexer的语法,并支持对dex格式的全部功能(annotations, debug info, line info, etc.)。
详细的细节讲解超出了本文的范围,但是我想让你至少知道smali和baksmali,只是想让你知道直接从classes.dex文件中获取.smali是可能的。
这意味着可以直接更改应用程序的源代码并使其生效。
指令非常简单:
d2j-dex2smali.sh FILENAME.apk
这个工具创建了一个新的文件夹,通过为APK名称新增后缀-out来命名,文件内部通过包名组织。
有一个想法,就是smali看起来像这样:它不是那么难读,一旦你习惯了它的语法是完全可以理解的。
请注意这是一个a.class类文件,我已经发布了截图上面,当谈到混淆代码。
apktool 同样可以操纵smali文件:我将在这个系列的下篇文章中介绍。
这是所有在更新和dex2jar介绍:正如你所看到的,一个相对简单易用的工具,你已经可以从APK文件中提取(可能回退)大量信息。但该工具缺乏对XML资源的支持,它们是任何安卓应用程序的重要组成部分。
在下篇文章中,我将向你介绍apktool同时也有些事情变得真的很有趣。