Apktool源码解析——第二篇

2024-02-17 00:38

本文主要是介绍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-RC3apkFileName: Baidu_Lebo_M01.apkisFrameworkApk: falseusesFramework:ids: - 1sdkInfo:minSdkVersion: '8'targetSdkVersion: '11'packageInfo:forced-package-id: '127'versionInfo:versionCode: '16'versionName: 2.0.1compressionType: trueunknownFiles://前面都是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源码解析——第二篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/716174

相关文章

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很