package org.jf.smali.Interface; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import org.antlr.runtime.ANTLRFileStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.TokenSource; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.CommonTreeNodeStream; import org.jf.dexlib.ClassDataItem; import org.jf.dexlib.ClassDataItem.EncodedMethod; import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.CodeItem; import org.jf.dexlib.DexFile; import org.jf.dexlib.Code.Opcode; import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; import org.jf.dexlib.Code.Analysis.ClassPath; import org.jf.dexlib.Code.Analysis.MethodAnalyzer; 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.smali.LexerErrorInterface; import org.jf.smali.smaliFlexLexer; import org.jf.smali.smaliLexer; import org.jf.smali.smaliParser; import org.jf.smali.smaliTreeWalker; import org.jf.util.AnalysisUtil; /** * * @author Juergen Graf <juergen.graf@gmail.com> * */ public class SmaliAnalysis implements DexAnalysis<SmaliAnalysis.SmaliInput> { private final SmaliConfig conf; public SmaliAnalysis(final SmaliConfig conf) { this.conf = conf; } public DexProgram analyze(final SmaliInput input) throws DexAnalysisException { final DexFile dexFile = assembleDexFile(conf, input); initializeLibraryClassPaths(conf, dexFile); final DexProgram dexProg = new DexProgram(input.toString(), dexFile); for (final ClassDefItem clsDef : dexFile.ClassDefsSection.getItems()) { final String classDescriptor = clsDef.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; } final ClassDataItem clsData = clsDef.getClassData(); if (clsData == null) { continue; } final DexClass dexClass = new DexClass(clsDef); dexProg.addClass(dexClass); if (clsData.getDirectMethods() != null) { for (final EncodedMethod em : clsData.getDirectMethods()) { final MethodAnalyzer analyze = new MethodAnalyzer(em, false, null); analyze.analyze(); final List<AnalyzedInstruction> instructions = analyze.getInstructions(); final DexMethod dexMethod = new DexMethod(instructions, em); dexClass.addMethod(dexMethod); } } if (clsData.getVirtualMethods() != null) { for (final EncodedMethod em : clsData.getVirtualMethods()) { if (em.codeItem == null) { continue; } final MethodAnalyzer analyze = new MethodAnalyzer(em, false, null); analyze.analyze(); final List<AnalyzedInstruction> instructions = analyze.getInstructions(); final DexMethod dexMethod = new DexMethod(instructions, em); dexClass.addMethod(dexMethod); } } } return dexProg; } private static void initializeLibraryClassPaths(final SmaliConfig conf, final DexFile dexFile) { final ClassPath.ClassPathErrorHandler classPathErrorHandler = new ClassPath.ClassPathErrorHandler() { public void ClassPathError(String className, Exception ex) { conf.out.println(String.format("Skipping %s", className)); ex.printStackTrace(conf.out); } }; String[] extraBootClassPathArray = null; if (conf.androidJars != null && conf.androidJars.length() > 0) { extraBootClassPathArray = (conf.androidJars.charAt(0) == ':' ? conf.androidJars.substring(1).split(":") : conf.androidJars.split(":")); } String[] bootClassPathDirsArray = new String[conf.bootClassPathDirs.size()]; for (int i = 0; i < bootClassPathDirsArray.length; i++) { bootClassPathDirsArray[i] = conf.bootClassPathDirs.get(i); } if (dexFile.isOdex() && conf.bootClassPath == null) { ClassPath.InitializeClassPathFromOdex(bootClassPathDirsArray, extraBootClassPathArray, conf.dexFilePath, dexFile, classPathErrorHandler); } else { String[] bootClassPathArray = null; if (conf.bootClassPath != null) { bootClassPathArray = conf.bootClassPath.split(":"); } ClassPath.InitializeClassPath(bootClassPathDirsArray, bootClassPathArray, extraBootClassPathArray, conf.dexFilePath, dexFile, classPathErrorHandler); } } private static DexFile assembleDexFile(final SmaliConfig conf, final SmaliInput input) throws DexAnalysisException { Opcode.updateMapsForApiLevel(conf.apiLevel); final DexFile dexFile = new DexFile(); if (conf.apiSet && conf.apiLevel >= 14) { dexFile.HeaderItem.setVersion(36); } if (conf.apiSet && conf.apiLevel >= 14) { dexFile.HeaderItem.setVersion(36); } boolean errors = false; for (final File file: input) { try { errors |= !assembleSmaliFile(conf, file, dexFile); } catch (final IOException e) { e.printStackTrace(conf.out); throw new DexAnalysisException(e); } catch (final RecognitionException e) { e.printStackTrace(conf.out); throw new DexAnalysisException(e); } } if (errors) { throw new DexAnalysisException("There were errors during assembly."); } if (conf.sort) { dexFile.setSortAllItems(true); } if (conf.fixJumbo || conf.fixGoto) { fixInstructions(dexFile, conf.fixJumbo, conf.fixGoto); } dexFile.place(); return dexFile; } /** * copied from smali.main - should be moved to a common utility class * @throws IOException * @throws RecognitionException */ private static boolean assembleSmaliFile(final SmaliConfig conf, final File smaliFile, final DexFile dexFile) throws IOException, RecognitionException { CommonTokenStream tokens; LexerErrorInterface lexer; if (conf.oldLexer) { ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8"); input.name = smaliFile.getAbsolutePath(); lexer = new smaliLexer(input); tokens = new CommonTokenStream((TokenSource) lexer); } else { FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); lexer = new smaliFlexLexer(reader); ((smaliFlexLexer) lexer).setSourceFile(smaliFile); tokens = new CommonTokenStream((TokenSource) lexer); } if (conf.printTokens) { tokens.getTokens(); for (int i = 0; i < tokens.size(); i++) { Token token = tokens.get(i); if (token.getChannel() == smaliLexer.HIDDEN) { continue; } conf.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); } } smaliParser parser = new smaliParser(tokens); parser.setVerboseErrors(conf.verboseErrors); parser.setAllowOdex(conf.allowOdex); parser.setApiLevel(conf.apiLevel); smaliParser.smali_file_return result = parser.smali_file(); if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { return false; } final CommonTree t = (CommonTree) result.getTree(); final CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); treeStream.setTokenStream(tokens); smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); dexGen.dexFile = dexFile; dexGen.smali_file(); return dexGen.getNumberOfSyntaxErrors() <= 0; } /** * copied from smali.main - should be moved to a common utility class */ private static void fixInstructions(final DexFile dexFile, final boolean fixJumbo, final boolean fixGoto) { dexFile.place(); for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) { codeItem.fixInstructions(fixJumbo, fixGoto); } } public static class SmaliInput implements DexAnalysis.Input, Iterable<File> { private final LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); public boolean addFile(final String fileName) { final File f = new File(fileName); return addFile(f); } public boolean addFile(final File file) { if (file.isDirectory()) { return addSmaliFilesInDir(file); } else { return filesToProcess.add(file); } } public boolean addFiles(final String[] files) { boolean change = false; for (final String file : files) { change |= addFile(file); } return change; } public boolean addFiles(final File[] files) { boolean change = false; for (final File file : files) { change |= addFile(file); } return change; } public Iterator<File> iterator() { return Collections.unmodifiableSet(filesToProcess).iterator(); } public int getNumberOfFiles() { return filesToProcess.size(); } private boolean addSmaliFilesInDir(final File dir) { boolean change = false; for(final File file: dir.listFiles()) { if (file.isDirectory()) { change |= addSmaliFilesInDir(file); } else if (file.getName().endsWith(".smali")) { change |= filesToProcess.add(file); } } return change; } public String toString() { return filesToProcess.toString(); } } public static class SmaliConfig { public String androidJars = "data/core.jar:data/ext.jar:data/framework.jar:data/android.policy.jar:data/services.jar"; public List<String> bootClassPathDirs = new LinkedList<String>(); public String bootClassPath = null; public boolean apiSet = false; public int apiLevel = 14; public PrintStream out = System.out; public boolean verboseErrors = false; public boolean oldLexer = false; public boolean printTokens = false; public boolean allowOdex = true; public boolean sort = true; public boolean fixJumbo = true; public boolean fixGoto = true; public String dexFilePath = "."; public SmaliConfig() { bootClassPathDirs.add("."); } public String toString() { final StringBuilder sb = new StringBuilder("SmaliAnalysis configuration:\n"); AnalysisUtil.writeAllFieldsToBuffer(this, sb); return sb.toString(); } } }