package com.android.reverse.smali; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.jf.baksmali.baksmaliOptions; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.CustomInlineMethodResolver; import org.jf.dexlib2.dexbacked.DexBackedClassDef; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.MemoryDexFileItemPointer; import org.jf.dexlib2.dexbacked.MemoryReader; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.util.ClassFileNameHandler; import org.jf.util.IndentingWriter; import com.android.reverse.collecter.ModuleContext; import com.android.reverse.util.Logger; import com.android.reverse.util.NativeFunction; import com.google.common.collect.Ordering; public class MemoryBackSmali { private static baksmaliOptions configOptions() { baksmaliOptions options = new baksmaliOptions(); options.apiLevel = ModuleContext.getInstance().getApiLevel(); options.outputDirectory = ModuleContext.getInstance().getAppContext().getFilesDir().getAbsolutePath()+"/smali"; options.allowOdex = true; options.deodex = true; options.jobs = 8; options.bootClassPathDirs.add("/system/framework/"); if (options.apiLevel >= 17) { options.checkPackagePrivateAccess = true; } options.registerInfo = 128; options.noAccessorComments = false; options.useLocalsDirective = true; options.noParameterRegisters = false; options.useSequentialLabels = true; options.outputDebugInfo = true; options.addCodeOffsets = false; return options; } public static boolean disassembleDexFile(int mCookie, String outDexName) { long startTime = System.currentTimeMillis(); Logger.log("start disassemble the mCookie " + mCookie); final baksmaliOptions options = configOptions(); File outputDirectoryFile = new File(options.outputDirectory); if (!outputDirectoryFile.exists()) { if (!outputDirectoryFile.mkdirs()) { Logger.log("Can't create the output directory " + options.outputDirectory); return false; } } Opcodes opcodes = new Opcodes(ModuleContext.getInstance().getApiLevel()); MemoryReader reader = new NativeFunction(); MemoryDexFileItemPointer pointer = NativeFunction .queryDexFileItemPointer(mCookie); DexBackedDexFile mmDexFile = new DexBackedDexFile(opcodes, pointer, reader); options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel); options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs, options.bootClassPathEntries, mmDexFile, options.apiLevel); String inlineString = NativeFunction.getInlineOperation(); options.inlineResolver = new CustomInlineMethodResolver( options.classPath, inlineString); List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy( mmDexFile.getClasses()); if (!options.noAccessorComments) { options.syntheticAccessorResolver = new SyntheticAccessorResolver( classDefs); } final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler( outputDirectoryFile, ".smali"); ExecutorService executor = Executors.newFixedThreadPool(options.jobs); List<Future<Boolean>> tasks = new ArrayList<Future<Boolean>>(); for (final ClassDef classDef : classDefs) { tasks.add(executor.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return disassembleClass(classDef, fileNameHandler, options); } })); } boolean errorOccurred = false; try { for (Future<Boolean> task : tasks) { while (true) { try { if (!task.get()) { errorOccurred = true; } } catch (InterruptedException ex) { continue; } catch (ExecutionException ex) { throw new RuntimeException(ex); } break; } } } finally { executor.shutdown(); } Logger.log("end disassemble the mCookie: cost time = " + ((System.currentTimeMillis() - startTime) / 1000) +"s"); startTime = System.currentTimeMillis(); Logger.log("start build the smali files to dex"); boolean result = DexFileBuilder.buildDexFile(options.outputDirectory,outDexName); Logger.log("end build the smali files to dex: cost time = " + ((System.currentTimeMillis() - startTime) / 1000)+"s"); if (result) { try { Runtime.getRuntime().exec("rm -rf " + options.outputDirectory); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return !errorOccurred; } private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, baksmaliOptions options) { DexBackedClassDef classdf = (DexBackedClassDef) classDef; if (!classdf.isValid()) return false; String classDescriptor = classDef.getType(); //Logger.log("start backsmali the class = " + classDescriptor); // validate that the descriptor is formatted like we expect if (classDescriptor.charAt(0) != 'L' || classDescriptor.charAt(classDescriptor.length() - 1) != ';') { Logger.log("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); return false; } File smaliFile = fileNameHandler .getUniqueFilenameForClass(classDescriptor); // create and initialize the top level string template ClassDefinition classDefinition = new ClassDefinition(options, classDef); // write the disassembly Writer writer = null; try { File smaliParent = smaliFile.getParentFile(); if (!smaliParent.exists()) { if (!smaliParent.mkdirs()) { // check again, it's likely it was created in a different // thread if (!smaliParent.exists()) { Logger.log("Unable to create directory " + smaliParent.toString() + " - skipping class"); return false; } } } if (!smaliFile.exists()) { if (!smaliFile.createNewFile()) { Logger.log("Unable to create file " + smaliFile.toString() + " - skipping class"); return false; } } BufferedWriter bufWriter = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(smaliFile), "UTF8")); writer = new IndentingWriter(bufWriter); classDefinition.writeTo((IndentingWriter) writer); } catch (Exception ex) { Logger.log("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); ex.printStackTrace(); // noinspection ResultOfMethodCallIgnored smaliFile.delete(); return false; } finally { if (writer != null) { try { writer.close(); } catch (Throwable ex) { Logger.log("\n\nError occurred while closing file " + smaliFile.toString()); ex.printStackTrace(); } } } return true; } private static List<String> getDefaultBootClassPathForApi(int apiLevel) { List<String> list = new ArrayList<String>(); if (apiLevel < 9) { list.add("/system/framework/core.jar"); list.add("/system/framework/ext.jar"); list.add("/system/framework/framework.jar"); list.add("/system/framework/android.policy.jar"); list.add("/system/framework/services.jar"); } else if (apiLevel < 12) { list.add("/system/framework/core.jar"); list.add("/system/framework/bouncycastle.jar"); list.add("/system/framework/ext.jar"); list.add("/system/framework/framework.jar"); list.add("/system/framework/android.policy.jar"); list.add("/system/framework/services.jar"); list.add("/system/framework/core-junit.jar"); } else if (apiLevel < 14) { list.add("/system/framework/core.jar"); list.add("/system/framework/apache-xml.jar"); list.add("/system/framework/bouncycastle.jar"); list.add("/system/framework/ext.jar"); list.add("/system/framework/framework.jar"); list.add("/system/framework/android.policy.jar"); list.add("/system/framework/services.jar"); list.add("/system/framework/core-junit.jar"); } else if (apiLevel < 16) { list.add("/system/framework/core.jar"); list.add("/system/framework/core-junit.jar"); list.add("/system/framework/bouncycastle.jar"); list.add("/system/framework/ext.jar"); list.add("/system/framework/framework.jar"); list.add("/system/framework/android.policy.jar"); list.add("/system/framework/services.jar"); list.add("/system/framework/apache-xml.jar"); list.add("/system/framework/filterfw.jar"); } else { // this is correct as of api 17/4.2.2 list.add("/system/framework/core.jar"); list.add("/system/framework/core-junit.jar"); list.add("/system/framework/bouncycastle.jar"); list.add("/system/framework/ext.jar"); list.add("/system/framework/framework.jar"); list.add("/system/framework/telephony-common.jar"); list.add("/system/framework/mms-common.jar"); list.add("/system/framework/android.policy.jar"); list.add("/system/framework/services.jar"); list.add("/system/framework/apache-xml.jar"); } return list; } }