zoukankan      html  css  js  c++  java
  • 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);
            }
        }
  • 相关阅读:
    LeetCode153 Find Minimum in Rotated Sorted Array. LeetCode162 Find Peak Element
    LeetCode208 Implement Trie (Prefix Tree). LeetCode211 Add and Search Word
    LeetCode172 Factorial Trailing Zeroes. LeetCode258 Add Digits. LeetCode268 Missing Number
    LeetCode191 Number of 1 Bits. LeetCode231 Power of Two. LeetCode342 Power of Four
    LeetCode225 Implement Stack using Queues
    LeetCode150 Evaluate Reverse Polish Notation
    LeetCode125 Valid Palindrome
    LeetCode128 Longest Consecutive Sequence
    LeetCode124 Binary Tree Maximum Path Sum
    LeetCode123 Best Time to Buy and Sell Stock III
  • 原文地址:https://www.cnblogs.com/bvin/p/4158014.html
Copyright © 2011-2022 走看看