Apktool source code analysis-Article 2: apktool source code article 2
In the previous article, we talked about the ApkDecoder class. Most of the calls were made to the Androlib class, and the last time we found that the brutall code was not the latest, we went to the iBotP. Code.
Today, let's look at the Androlib code:
Private final AndrolibResources mAndRes = new AndrolibResources (); protected final ResUnknownFiles mResUnknownFiles = new ResUnknownFiles (); public ApkOptions apkOptions;
/** Two constructor Methods */public Androlib (ApkOptions apkOptions) {this.apk Options = apkOptions; mAndRes.apk Options = apkOptions;} public Androlib () {// default value: ApkOption this.apk Options = new ApkOptions (); mAndRes.apk Options = this.apk Options;} public ResTable getResTable (ExtFile apkFile) throws AndrolibException {return mAndRes. getResTable (apkFile, true); // It is still in the AndrolibRecources class after all, so the next article will announce it} public ResTable getResTable (ExtFile apkFile, boolean loadMainPkg) throws AndrolibException {return mAndRes. getResTable (apkFile, loadMainPkg );}
Androlib is divided into two types: decodeXXX decoding (decompilation) method and buildXXX building (decompilation) method. Here we will not talk about the build method for the moment. first look at decode.
The Decompilation of source files involves three methods: decodeSourceRow (), decodeSourceSmali (), decodeSourceJava (), and decodeSourceRow. the output directory of dex file copy. The decodeSourceSmali () method uses the SmaliDecoder class to decode the smali file. The decodeSourceJava () method calls the AndrolibJava class to decode the java file.
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 (); // create the smali directory LOGGER.info ("Baksmaling" + filename + "... "); SmaliDecoder. decode (apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api); // parse 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 (). there are not many decode () methods, just an input file and an output directory.
}
XXXRow suffixes are directly copied without decoding. below is the decompilation of 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); // There Is A ResTable parameter here}
Xml files are decompiled using AndrolibRecources. The following describes the res decoding.
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 (resTa Ble, apkFile, outDir); // All decode methods of AndrolibRecources must be ResTable, resource table? }
The next step is the decompilation of the lib directory and assets Directory. In fact, the output is copied directly.
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); } }
There is also a decodeUnknownFiles () method, which is a common file in non-apk. Here we will first list the apk standard file names:
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] { "classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };
Other files are not supported by apk. The processing method is to directly copy the output.
Private boolean isAPKFileNames (String file) {// determine whether the above regular file is in the apk package 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) {// retrieve all file names in the apk if (! IsAPKFileNames (file )&&! File. endsWith (". dex ") {// not a regular file or. dex file // copy file out of archive into special "unknown" folder unk. copyToDir (unknownOut, file); // copy to the unknown directory 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 (invZi PFile! = Null) {// collect them here. If you need to recompile them, you can also insert them back to mResUnknownFiles. addUnknownFileInfo (invZipFile. getName (), String. valueOf (invZipFile. getMethod () ;}} catch (NullPointerException ignored) {}} apkZipFile. close ();} catch (DirectoryException | IOException ex) {throw new AndrolibException (ex );}}
The last writeOriginalFiles () method knows that the decompilation directory contains an original directory, which is the directory for storing the original files.
Public void writeOriginalFiles (ExtFile apkFile, File outDir) throws AndrolibException {LOGGER.info ("Copying original files... "); File originalDir = new File (outDir," original "); // create the original directory 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") {// The certificate file is in the original directory in. copyToDir (originalDir, "META-INF") ;}} catch (DirectoryException ex) {throw new AndrolibException (ex );}}
However, there is also a method to create an apktool. yml description file.
Public void writeMetaFile (File mOutDir, Map <String, Object> meta) // key-Value Pair information throws AndrolibException {DumperOptions options = new DumperOptions (); options. setdefaflowflowstyle (DumperOptions. flowStyle. BLOCK); Yaml yaml = new Yaml (options); try (Writer writer = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (new File (mOutDir, "apktool. yml ")," UTF-8 "); // output directory) {yaml. dump (meta, writer);} catch (IOException ex) {throw new AndrolibException (ex );}}
Let's take a look at the directory of A decompiled instance.
Now, everyone must be aware of it. Here are some points to talk about. The signature certificate is in the original directory. In addition, there is also an AndroidManifest. xml file in original which is not decoded and opened with garbled characters. The outermost layer is decoded.
There is also the unknown Directory, which can be punched in to see if it may be the rarfile, image file, data file of other libraries. Finally, let's take a look at apktool. tml:
Version: 2.0.0-RC3apkFileName: Plugin: falseusesFramework: ids:-1 sdkInfo: minSdkVersion: '8' targetSdkVersion: '11' packageInfo: forced-package-id: '100' versionInfo: versionCode: '16' versionName: 2.0.1compressionType: trueunknownFiles: // The previous meta key-value pairs generate 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'
Let's take a look at the ApkDecoder. decode () method mentioned in the previous article. The idea is clear.
1. First create the output directory
2. decompile the resource file. Here are several judgments if the apk has recources. the arsc file calls AndrolibRecources. decodeResourcesXXX (), if no resource file has AndroidMenifest. the xml file directly calls AndrolibRecources. decodeManifestXXX () method. It can be seen that if both recources. arsc and AndroidMenifest. xml are available, they should all be decoded in AndrolibRecources. decodeResources.
3. decompile the source file. There are two cases here. The new version of Android supports MultiDex (the original 53566 Method number limit), which means that there may be more than classes in an apk. dex is a dex file, which may be classes1.dex or classes2.dex (not in practice ). If there are multiple dex, call the three methods decodeSourcesSmali, decodeSourcesJava, and decodeSourcesRow cyclically.
4. Copy the libs and assets Directory files and other files to the output directory. // MAndrolib. decodeRawFiles (mApkFile, outDir); mAndrolib. decodeUnknownFiles (mApkFile, outDir, mResTable );
5. Output original file original directory, here only look at the copies of the two files AndroidManifest. xml and META-INF directory. // MAndrolib. writeOriginalFiles (mApkFile, outDir );
The Code of ApkDecoder. decode () is subsidized. The previous article should have been posted. Here, I will post a few pieces of judgment code to make it easier for everyone to understand.
Public boolean hasSources () throws AndrolibException {// check whether the source file exists based on classes in the apk package. dex file try {return mApkFile. getDirectory (). containsFile ("classes. dex ");} catch (DirectoryException ex) {throw new AndrolibException (ex);} public boolean hasMultipleSources () throws AndrolibException {// check whether there are multiple. dex file 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 {// is there any AndroidManifest. xml file. This must be a try {return mApkFile. getDirectory (). containsFile ("AndroidManifest. xml ");} catch (DirectoryException ex) {throw new AndrolibException (ex);} public boolean hasResources () throws AndrolibException {// determine whether there is any resource file resources. arsc try {return mApkFile. getDirectory (). containsFile ("resources. arsc ");} catch (DirectoryException ex) {throw new AndrolibException (ex );}}