public void addClassTreeHook(HookClass origin, HookClass to) { addSuperclassHook(origin, to); Set<HookClass> superclassHookSet = new HashSet<HookClass>(); superclassHookSet.add(origin); for (ClassDefItem classDef : mDexFile.ClassDefsSection.getItems()) { HookClass superclass = new HookClass(classDef.getSuperclass()); if (superclassHookSet.contains(superclass)) { TypeIdItem classType = classDef.getClassType(); HookClass newHook = new HookClass(classType); superclassHookSet.add(newHook); mHooks.put(newHook, classType); } } HookClass[] superclassHooks = superclassHookSet.toArray(new HookClass[0]); HashSet<EncodedMethod> newClassMethods = getClassMethodsMap().get(to); if (newClassMethods != null) { for (EncodedMethod newVirtual : newClassMethods) { if (newVirtual.isDirect()) { continue; } for (HookClass superclass : superclassHooks) { HookMethod deadMethod = new HookMethod(newVirtual.method); deadMethod.mClasspath = superclass.mClasspath; mDead.add(deadMethod); } } } }
public void hookSuperclass(HookMap hooks, ClassDefItem classDef) { HookItem oldTarget = HookItem.getInstance(classDef.getSuperclass()); if (oldTarget == null) { return; } if (!hooks.containsKey(oldTarget)) { return; } TypeIdItem newTarget = (TypeIdItem) hooks.get(oldTarget); assert (newTarget != null); if (newTarget == classDef.getClassType()) { return; } classDef.setSuperclass(newTarget); HookClass classType = new HookClass(classDef.getClassType()); if (hooks.mIgnoreMethods.contains(classType)) { return; } if (hooks.mDead.isEmpty()) { return; } // get rid of virtual methods so the hooked class method is called instead String className = newTarget.getTypeDescriptor(); List<EncodedMethod> newDirects = new LinkedList<EncodedMethod>(); List<EncodedMethod> newVirtuals = new LinkedList<EncodedMethod>(); ClassDataItem classData = classDef.getClassData(); for (EncodedMethod encodedMethod : classData.getDirectMethods()) { newDirects.add(encodedMethod); } for (EncodedMethod encodedMethod : classData.getVirtualMethods()) { HookMethod method = new HookMethod(encodedMethod.method); method.mClasspath = className; if (!hooks.mDead.contains(method)) { newVirtuals.add(encodedMethod); } } classData.update(newDirects, newVirtuals); }
public static HookItem getInstance(Item item) { if (item instanceof MethodIdItem) { return new HookMethod((MethodIdItem) item); } if (item instanceof TypeIdItem) { return new HookClass((TypeIdItem) item); } if (item instanceof ClassDefItem) { ClassDefItem classDef = (ClassDefItem) item; return new HookClass(classDef.getClassType()); } return null; }
private void initClassMaps() { mClasses = new HashMap<HookClass, TypeIdItem>(); mClassMethods = new HashMap<HookClass, HashSet<EncodedMethod>>(); mSubclasses = new HashMap<String, Set<String>>(); for (ClassDefItem item : mDexFile.ClassDefsSection.getItems()) { TypeIdItem classType = item.getClassType(); HookClass classHook = new HookClass(classType); mClasses.put(classHook, classType); String superclassType = item.getSuperclass().getTypeDescriptor(); Set<String> subclassSet = mSubclasses.get(superclassType); if (subclassSet == null) { subclassSet = new TreeSet<String>(); mSubclasses.put(superclassType, subclassSet); } subclassSet.add(classType.getTypeDescriptor()); ClassDataItem classData = item.getClassData(); if (classData == null) { continue; } HashSet<EncodedMethod> classMethods = new HashSet<EncodedMethod>(); for (EncodedMethod method : classData.getDirectMethods()) { classMethods.add(method); } for (EncodedMethod method : classData.getVirtualMethods()) { classMethods.add(method); } mClassMethods.put(classHook, classMethods); } }
public static void disassembleDexFile( String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, String[] classPathDirs, String bootClassPath, String extraBootClassPath, boolean noParameterRegisters, boolean useLocalsDirective, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, boolean noAccessorComments, int registerInfo, boolean verify, boolean ignoreErrors, String inlineTable) { baksmali.noParameterRegisters = noParameterRegisters; baksmali.useLocalsDirective = useLocalsDirective; baksmali.useSequentialLabels = useSequentialLabels; baksmali.outputDebugInfo = outputDebugInfo; baksmali.addCodeOffsets = addCodeOffsets; baksmali.noAccessorComments = noAccessorComments; baksmali.deodex = deodex; baksmali.registerInfo = registerInfo; baksmali.bootClassPath = bootClassPath; baksmali.verify = verify; ClassPath.ClassPathErrorHandler classPathErrorHandler = null; if (ignoreErrors) { classPathErrorHandler = new ClassPath.ClassPathErrorHandler() { public void ClassPathError(String className, Exception ex) { System.err.println(String.format("Skipping %s", className)); ex.printStackTrace(System.err); } }; } if (registerInfo != 0 || deodex || verify) { try { 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 && isExtJar(dexFilePath)) { extraBootClassPathArray = new String[] {"framework.jar"}; } ClassPath.InitializeClassPathFromOdex( classPathDirs, extraBootClassPathArray, dexFilePath, dexFile, classPathErrorHandler); } else { String[] bootClassPathArray = null; if (bootClassPath != null) { bootClassPathArray = bootClassPath.split(":"); } ClassPath.InitializeClassPath( classPathDirs, bootClassPathArray, extraBootClassPathArray, dexFilePath, dexFile, classPathErrorHandler); } if (inlineTable != null) { inlineResolver = new CustomInlineMethodResolver(inlineTable); } } catch (Exception ex) { System.err.println("\n\nError occured while loading boot class path files. Aborting."); ex.printStackTrace(System.err); System.exit(1); } } File outputDirectoryFile = new File(outputDirectory); if (!outputDirectoryFile.exists()) { if (!outputDirectoryFile.mkdirs()) { System.err.println("Can't create the output directory " + outputDirectory); System.exit(1); } } if (!noAccessorComments) { syntheticAccessorResolver = new SyntheticAccessorResolver(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 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()); } }); ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); for (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 (registerInfo != 0 || deodex || verify) { // 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 ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false); if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) { continue; } } String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); // validate that the descriptor is formatted like we expect if (classDescriptor.charAt(0) != 'L' || classDescriptor.charAt(classDescriptor.length() - 1) != ';') { System.err.println( "Unrecognized class descriptor - " + classDescriptor + " - skipping class"); continue; } File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); // create and initialize the top level string template ClassDefinition classDefinition = new ClassDefinition(classDefItem); // write the disassembly Writer writer = null; try { File smaliParent = smaliFile.getParentFile(); if (!smaliParent.exists()) { if (!smaliParent.mkdirs()) { System.err.println( "Unable to create directory " + smaliParent.toString() + " - skipping class"); continue; } } if (!smaliFile.exists()) { if (!smaliFile.createNewFile()) { System.err.println( "Unable to create file " + smaliFile.toString() + " - skipping class"); continue; } } BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(smaliFile), "UTF8")); writer = new IndentingWriter(bufWriter); classDefinition.writeTo((IndentingWriter) writer); } catch (Exception ex) { System.err.println( "\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); ex.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (Throwable ex) { System.err.println("\n\nError occured while closing file " + smaliFile.toString()); ex.printStackTrace(); } } } if (!ignoreErrors && classDefinition.hadValidationErrors()) { System.exit(1); } } }