Apktool源码解析——第二篇
上一篇讲到ApkDecoder这个类,大部分调用到还是Androlib类,而且上次发现brutall的代码竟然不是最新的,遂去找iBotP.的代码了。
今天来看Androlib的代码:
private final AndrolibResources mAndRes = new AndrolibResources();
protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles();
public ApkOptions apkOptions;
/**两个构造方法*/
public Androlib(ApkOptions apkOptions) {
this.apkOptions = apkOptions;
mAndRes.apkOptions = apkOptions;
} public Androlib() {//默认ApkOption
this.apkOptions = new ApkOptions();
mAndRes.apkOptions = this.apkOptions;
} public ResTable getResTable(ExtFile apkFile)
throws AndrolibException {
return mAndRes.getResTable(apkFile, true);//终究还是去AndrolibRecources类里,所以下篇预告就是它了
} public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg)
throws AndrolibException {
return mAndRes.getResTable(apkFile, loadMainPkg);
}
Androlib主要分为两类,一类是decodeXXX解码(反编译)方法,一类是buildXXX构建(回编译)方法。这里暂且不讲build方法,先看decode。
源文件的反编译有三个方法decodeSourceRow()、decodeSourceSmali()、decodeSourceJava(),decodeSourceRow()方法就直接把classes.dex文件拷贝的输出目录,decodeSourceSmali()方法是通过SmaliDecoder类去解码出smali文件,decodeSourceJava()方法就是调用AndrolibJava类解码java文件。
public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename)
throws AndrolibException {
try {
LOGGER.info("Copying raw classes.dex file...");
apkFile.getDirectory().copyToDir(outDir, filename);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
} public void decodeSourcesSmali(File apkFile, File outDir, String filename, boolean debug, String debugLinePrefix,
boolean bakdeb, int api) throws AndrolibException {
try {
File smaliDir;
if (filename.equalsIgnoreCase("classes.dex")) {
smaliDir = new File(outDir, SMALI_DIRNAME);
} else {
smaliDir = new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
}
OS.rmdir(smaliDir);
smaliDir.mkdirs();//创建smali目录
LOGGER.info("Baksmaling " + filename + "...");
SmaliDecoder.decode(apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api);//解析出smali
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
} public void decodeSourcesJava(ExtFile apkFile, File outDir, boolean debug)
throws AndrolibException {
LOGGER.info("Decoding Java sources...");
new AndrolibJava().decode(apkFile, outDir);//这个AndrolibJava().decode()方法不多,就一个输入文件和输出目录
}
XXXRow后缀的方法都是不解码直接拷贝,下面是对AndroidManifest.xml的反编译。
public void decodeManifestRaw(ExtFile apkFile, File outDir)
throws AndrolibException {
try {
Directory apk = apkFile.getDirectory();
LOGGER.info("Copying raw manifest...");
apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
} public void decodeManifestFull(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException {
mAndRes.decodeManifest(resTable, apkFile, outDir);//这里有一个ResTable参数
}
xml文件都是用AndrolibRecources去反编译的,下面看res的解码。
public void decodeResourcesRaw(ExtFile apkFile, File outDir)
throws AndrolibException {
try {
LOGGER.info("Copying raw resources...");
apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
} public void decodeResourcesFull(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException {
mAndRes.decode(resTable, apkFile, outDir);//这里发现AndrolibRecources的所有decode方法都要一个ResTable,资源表?
}
接下来是lib目录和assets目录的反编译,其实这里就是直接拷贝输出。
public void decodeRawFiles(ExtFile apkFile, File outDir)
throws AndrolibException {
LOGGER.info("Copying assets and libs...");
try {
Directory in = apkFile.getDirectory();
if (in.containsDir("assets")) {
in.copyToDir(outDir, "assets");
}
if (in.containsDir("lib")) {
in.copyToDir(outDir, "lib");
}
if (in.containsDir("libs")) {
in.copyToDir(outDir, "libs");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
还有一个decodeUnknownFiles()方法,就是非apk内常见的文件。这里先列一下哪些是apk标准文件名:
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };
其他的都不是apk支持的文件,处理方法就是直接拷贝输出。
private boolean isAPKFileNames(String file) {//判断apk包内文件是不是以上的常规文件
for (String apkFile : APK_STANDARD_ALL_FILENAMES) {
if (apkFile.equals(file) || file.startsWith(apkFile + "/")) {
return true;
}
}
return false;
}
public void decodeUnknownFiles(ExtFile apkFile, File outDir, ResTable resTable)
throws AndrolibException {
LOGGER.info("Copying unknown files...");
File unknownOut = new File(outDir, UNK_DIRNAME);
ZipEntry invZipFile;
// have to use container of ZipFile to help identify compression type
// with regular looping of apkFile for easy copy
try {
Directory unk = apkFile.getDirectory();
ZipExtFile apkZipFile = new ZipExtFile(apkFile.getAbsolutePath());
// loop all items in container recursively, ignoring any that are pre-defined by aapt
Set<String> files = unk.getFiles(true);
for (String file : files) {//取出apk内所有文件名
if (!isAPKFileNames(file) && !file.endsWith(".dex")) {//不是常规文件也不是.dex文件
// copy file out of archive into special "unknown" folder
unk.copyToDir(unknownOut, file);//拷贝至unknown目录
try {
// ignore encryption
apkZipFile.getEntry(file).getGeneralPurposeBit().useEncryption(false);
invZipFile = apkZipFile.getEntry(file);
// lets record the name of the file, and its compression type
// so that we may re-include it the same way
if (invZipFile != null) {//这里把他们收集起来,如果需要回编译还可以原封不动的塞回去
mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
}
} catch (NullPointerException ignored) { }
}
}
apkZipFile.close();
} catch (DirectoryException | IOException ex) {
throw new AndrolibException(ex);
}
}
最后一个writeOriginalFiles()方法,相比大家用过apktool的都知道反编译的目录里有个original目录,就是存放原始文件的目录。
public void writeOriginalFiles(ExtFile apkFile, File outDir)
throws AndrolibException {
LOGGER.info("Copying original files...");
File originalDir = new File(outDir, "original");//创建original目录
if (!originalDir.exists()) {
originalDir.mkdirs();
} try {
Directory in = apkFile.getDirectory();
if(in.containsFile("AndroidManifest.xml")) {
in.copyToDir(originalDir, "AndroidManifest.xml");
}
if (in.containsDir("META-INF")) {//证书文件是在original目录
in.copyToDir(originalDir, "META-INF");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
不过还有一个创建apktool.yml描述文件的方法。
public void writeMetaFile(File mOutDir, Map<String, Object> meta)//键值对信息
throws AndrolibException {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options); try (
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
new File(mOutDir, "apktool.yml")), "UTF-8"));//输出目录
) {
yaml.dump(meta, writer);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
好了,我们看一眼一个反编译实例的目录。

这下想必大家都了然于胸了,这里有几点要说的。签名证书是在original目录,另外original也有一份AndroidManifest.xml是没有解码的,打开是乱码的,最外层的那个才是解码后的。
还有unknown目录,可以打卡看一看可能会是其他库的rar文件,图片文件,数据文件之类的。最后看一眼apktool.tml:
version: 2.0.0-RC3 apkFileName: Baidu_Lebo_M01.apk isFrameworkApk: false usesFramework: ids: - 1 sdkInfo: minSdkVersion: '8' targetSdkVersion: '11' packageInfo: forced-package-id: '127' versionInfo: versionCode: '16' versionName: 2.0.1 compressionType: true unknownFiles://前面都是meta键值对生成 com/baidu/music/lebo/logic/api/model/model.rar: '8'
com/handmark/pulltorefresh/library/logo.png: '8'
com/j256/ormlite/android/LICENSE.txt: '8'
com/j256/ormlite/android/README.txt: '8'
com/j256/ormlite/core/LICENSE.txt: '8'
com/j256/ormlite/core/README.txt: '8'
再回过头来看一下上篇讲到的ApkDecoder.decode()方法,思路就很清晰了。
1.首先创建输出目录
2.反编译资源文件,这里有几个判断,如果apk有recources.arsc文件就调用AndrolibRecources.decodeResourcesXXX(),如果没有资源文件有AndroidMenifest.xml文件,就直接调用AndrolibRecources.decodeManifestXXX()方法。由此可见,如果recources.arsc和AndroidMenifest.xml都有的话,应该都是在AndrolibRecources.decodeResources里解码的。
3.反编译源文件,这里也有两种情况,新版Android支持MultiDex(原来的有53566方法数限制)了也就意味着一个apk里可能不止classes.dex一个dex文件了,可能叫classes1.dex、classes2.dex(没去实践)。如果是有多个dex就循环调用decodeSourcesSmali、decodeSourcesJava、decodeSourcesRow这三个方法。
4.拷贝libs、assets目录文件和其他文件至输出目录。//mAndrolib.decodeRawFiles(mApkFile, outDir);mAndrolib.decodeUnknownFiles(mApkFile, outDir, mResTable);
5.输出原始文件original目录,这里只看对这两个文件的拷贝AndroidManifest.xml和META-INF目录。//mAndrolib.writeOriginalFiles(mApkFile, outDir);
ApkDecoder.decode()的代码就补贴了,上一篇应该贴过了,这里贴一下几个判断的代码,这样大家更容易明白。
public boolean hasSources() throws AndrolibException {//判断有没有源文件的依据就是看apk压缩包内有没有classes.dex文件
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasMultipleSources() throws AndrolibException {//看有没有多个.dex文件
try {
Set<String> files = mApkFile.getDirectory().getFiles(true);
for (String file : files) {
if (file.endsWith(".dex")) {
if (! file.equalsIgnoreCase("classes.dex")) {
return true;
}
}
}
return false;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasManifest() throws AndrolibException {//有没有AndroidManifest.xml文件,这个必须要有啊
try {
return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasResources() throws AndrolibException {//判断有没有资源文件resources.arsc
try {
return mApkFile.getDirectory().containsFile("resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
Apktool源码解析——第二篇的更多相关文章
- Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Apktool源码解析——第一篇
著名的apktool是android逆向界用的最普遍的一个工具,这个项目的原始地址在这里http://code.google.com/p/android-apktool/,但是你们都懂的在天朝谷歌是无 ...
- jQuery2.x源码解析(缓存篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
- jQuery2.x源码解析(设计篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...
- jQuery2.x源码解析(回调篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...
- Shiro源码解析-Session篇
上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下. 1. 分析切入点:DefaultSecurityManger的login方 ...
- tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解
TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客.如果觉得本篇文章对您有所帮助.点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) ...
- myBatis源码解析-类型转换篇(5)
前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...
随机推荐
- 【Unity笔记】UGUI中Canvas屏幕适配
1.通过RectTransform中的Anchors和Pivot来进行控件和窗体的布局适配. Anchors控制当前Panel相对于父窗体的布局位置,可以设置为居中或者左上角,当父窗体拉伸的时候当前P ...
- 抽取、转换和装载介绍(七)管理ETL环境(待续)
数据仓库的目标之一是能够为增强业务功能提供适时的.一致的和可靠的数据. 为了达到上述目标,ETL必须按照下述三条标准不断地加以完善: 可靠性 可用性 易管理性 子系统22--作业调度器 子系统23-- ...
- SQL2005 全文检索超时的解决方案
如果没有 Internet 连接的服务器上运行的 SQL Server 2005 实例中运行全文查询时,您可能会遇到 45 秒钟的延迟 详细的解释请查看http://support.microsoft ...
- Qt 4.8.5 icpc: Command not found
icpc: Command not found 交叉编译Qt4.8.5的时候出现
- php7+apache2.4 安装(window)
一.下载 需要下载 php7.apache2.4.vc2015(VC库) PHP7下载地址:http://windows.php.net/download/ Apache 下载地址:https://h ...
- FreeMarker 技术资料与问题解决备忘录
技术资料: http://www.ibm.com/developerworks/cn/java/j-lo-freemarker/ FM 解析hash序列 output[name = mouse; pr ...
- LintCode - Copy List with Random Pointer
LintCode - Copy List with Random Pointer LintCode - Copy List with Random Pointer Web Link Descripti ...
- HTML文档的经常使用标记
一.HTML文档中经常使用的标记有文字标记.段落标记.列表标记.超链接标记.图像标记.表格标记.框架标记和多媒体标记,以下对这些经常使用标记进行介绍: 1.文字标记:文字是网页重要的组成部分之中的一个 ...
- ROS 教程之 navigation :在 catkin 环境下创建costmap layer plugin
在做机器人导航的时候,肯定见到过global_costmap和local_costmap.global_costmap是为了全局路径规划服务的,如从这个房间到那个房间该怎么走.local_costma ...
- 联合主键用hibernate注解映射方式主要有三种:
将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode 第一.将该类注解为@Embeddable,最后在主类中(该类不包含联合主键 ...