Apktool源碼解析——第二篇,apktool源碼第二篇

來源:互聯網
上載者:User

Apktool源碼解析——第二篇,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);        }    }

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.