package org.jf.baksmali.Interface; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib.ClassDataItem; import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.DexFile; import org.jf.dexlib.Code.Analysis.ClassPath; import org.jf.dexlib.Code.Analysis.InlineMethodResolver; import org.jf.dexlib.Interface.DexAnalysis; import org.jf.dexlib.Interface.DexClass; import org.jf.dexlib.Interface.DexMethod; import org.jf.dexlib.Interface.DexProgram; import org.jf.util.AnalysisUtil; public class BakSmaliAnalysis implements DexAnalysis<BakSmaliAnalysis.BakSmaliInput> { private final BakSmaliConfig conf; public BakSmaliAnalysis(final BakSmaliConfig conf) { this.conf = conf; } public static class BakSmaliInput implements DexAnalysis.Input { public final String programDexFile; public BakSmaliInput(String programDexFile) { this.programDexFile = programDexFile; } public String toString() { return programDexFile; } } public DexProgram analyze(final BakSmaliInput in) throws DexAnalysisException { return analyze(in.programDexFile); } public DexProgram analyze(final String programDexFile) throws DexAnalysisException { final DexFile dexFile = initializeClassPathAndLoadProgram(conf, programDexFile); final DexProgram dexProg = new DexProgram(programDexFile, dexFile); final List<ClassDefItem> classDefItems = sortClassDefItems(dexFile); for (final ClassDefItem classDefItem : classDefItems) { /** * The path for the disassembly file is based on the package * name The class descriptor will look something like: * Ljava/lang/Object; Where the there is leading 'L' and a * trailing ';', and the parts of the package name are separated * by '/' */ // If we are analyzing the bytecode, make sure that this class // is loaded into the ClassPath. If it isn't // then there was some error while loading it, and we should // skip it final ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false); if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) { continue; } final String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); // validate that the descriptor is formatted like we expect if (classDescriptor.charAt(0) != 'L' || classDescriptor.charAt(classDescriptor.length() - 1) != ';') { conf.out.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); continue; } // create and initialize the top level string template final ClassDefinition classDefinition = new ClassDefinition(classDefItem); final DexClass dexClass = new DexClass(classDefItem); dexProg.addClass(dexClass); final ClassDataItem.EncodedMethod[] directMethods = classDefinition.getClassDataItem().getDirectMethods(); if (directMethods != null) { for (ClassDataItem.EncodedMethod method : directMethods) { if (method.codeItem == null || method.codeItem.getInstructions().length == 0) { continue; } final DexMethod dexMethod = DexMethod.build(method, conf.deodex, conf.inlineResolver); dexClass.addMethod(dexMethod); } } final ClassDataItem.EncodedMethod[] virtualMethods = classDefinition.getClassDataItem().getVirtualMethods(); if (virtualMethods != null) { for (ClassDataItem.EncodedMethod method : virtualMethods) { if (method.codeItem == null || method.codeItem.getInstructions().length == 0) { continue; } final DexMethod dexMethod = DexMethod.build(method, conf.deodex, conf.inlineResolver); dexClass.addMethod(dexMethod); } } } return dexProg; } private static List<ClassDefItem> sortClassDefItems(final DexFile dexFile) { // sort the classes, so that if we're on a case-insensitive file // system and need to handle classes with file // name collisions, then we'll use the same name for each class, if // the dex file goes through multiple // baksmali/smali cycles for some reason. If a class with a // colliding name is added or removed, the filenames // may still change of course final ArrayList<ClassDefItem> classDefItems = new ArrayList<ClassDefItem>(dexFile.ClassDefsSection.getItems()); Collections.sort(classDefItems, new Comparator<ClassDefItem>() { public int compare(ClassDefItem classDefItem1, ClassDefItem classDefItem2) { return classDefItem1.getClassType().getTypeDescriptor().compareTo(classDefItem1.getClassType().getTypeDescriptor()); } }); return classDefItems; } private static DexFile initializeClassPathAndLoadProgram(final BakSmaliConfig conf, final String programDexFile) throws DexAnalysisException { String bootClassPath = null; final StringBuffer extraBootClassPathEntries = new StringBuffer(); if ("".equals(conf.androidJars)) { bootClassPath = null; } else if (conf.androidJars != null && conf.androidJars.charAt(0) == ':') { extraBootClassPathEntries.append(conf.androidJars); } else { bootClassPath = conf.androidJars; } final File dexFileFile = new File(programDexFile); if (!dexFileFile.exists()) { final String msg ="Can't find the file " + programDexFile + " (" + dexFileFile.getAbsolutePath() + ")"; conf.out.println(msg); throw new DexAnalysisException(msg); } // Read in and parse the dex file DexFile dexFile = null; try { dexFile = new DexFile(dexFileFile, conf.preserveSignedRegisters, conf.skipInstructions); } catch (IOException exc) { exc.printStackTrace(conf.out); throw new DexAnalysisException(exc); } final String extraBootClassPath = extraBootClassPathEntries.toString(); final String[] classPathDirs = { "." }; ClassPath.ClassPathErrorHandler classPathErrorHandler = null; String[] extraBootClassPathArray = null; if (extraBootClassPath != null && extraBootClassPath.length() > 0) { assert extraBootClassPath.charAt(0) == ':'; extraBootClassPathArray = extraBootClassPath.substring(1).split(":"); } if (dexFile.isOdex() && bootClassPath == null) { // ext.jar is a special case - it is typically the 2nd jar // in the boot class path, but it also // depends on classes in framework.jar (typically the 3rd // jar in the BCP). If the user didn't // specify a -c option, we should add framework.jar to the // boot class path by default, so that it // "just works" if (extraBootClassPathArray == null && AnalysisUtil.isExtJar(dexFileFile.getPath())) { extraBootClassPathArray = new String[] { "framework.jar" }; } ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFileFile.getPath(), dexFile, classPathErrorHandler); } else { String[] bootClassPathArray = null; if (bootClassPath != null) { bootClassPathArray = bootClassPath.split(":"); } ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, dexFileFile.getPath(), dexFile, classPathErrorHandler); } return dexFile; } public static class BakSmaliConfig { public String androidJars = "data/core.jar:data/ext.jar:data/framework.jar:data/android.policy.jar:data/services.jar"; public PrintStream out = System.out; public boolean deodex = false; public InlineMethodResolver inlineResolver = null; public final boolean preserveSignedRegisters = true; public final boolean skipInstructions = false; public String toString() { final StringBuilder sb = new StringBuilder("BakSmaliAnalysis configuration:\n"); AnalysisUtil.writeAllFieldsToBuffer(this, sb); return sb.toString(); } } }