• 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);
            }
        }
  • 相关阅读:
    Spring Boot 2 快速教程:WebFlux Restful CRUD 实践(三)
    Spring Boot 2 快速教程:WebFlux 快速入门(二)
    ES 集群上,业务单点如何优化升级?
    Spring Boot 2.x 系列教程:WebFlux 系列教程大纲(一)
    泥瓦匠想做一个与众不同的技术"匠"
    java编程行业微信群,无论新手老手欢迎加入,会一直更新
    Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error Handling
    解决方案:如何防止数据重复插入?
    阿里 Java 手册系列教程:为啥强制子类、父类变量名不同?
    品阿里 Java 开发手册有感
  • 原文地址:https://www.cnblogs.com/bvin/p/4158014.html
Copyright © 2020-2023  润新知