上一篇講到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); } }