/* * jimple2boogie - Translates Jimple (or Java) Programs to Boogie * Copyright (C) 2013 Martin Schaeaeaef and Stephan Arlt * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package soottocfg.soot; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import com.google.common.base.Verify; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ClassInfo; import soot.ArrayType; import soot.BooleanType; import soot.Modifier; import soot.PackManager; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Type; import soot.VoidType; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soottocfg.Options; /** * The Soot Runner * * @author schaef * @author Dietsch */ public class SootRunner { private final soot.options.Options sootOpt; // private final List<String> resolvedClassNames; public SootRunner() { this(new ArrayList<String>()); } public SootRunner(List<String> resolvedClassNames) { this.sootOpt = soot.options.Options.v(); } public void run(String input, String classPath) { if (null == input || input.isEmpty()) { return; } if (input.endsWith(".jar")) { // run with JAR file runWithJar(input, classPath); throw new RuntimeException("currently not tested"); } else if (input.endsWith(".apk")) { runWithApk(input, classPath); throw new RuntimeException("currently not tested"); } else { File file = new File(input); if (file.isDirectory()) { runWithPath(input, classPath); } else { throw new RuntimeException("Don't know what to do with: " + input); } } } /** * Runs Soot by using a JAR file * * @param jarFile * JAR file * @param classPath * Optional classpath (may be null) * */ private void runWithJar(String jarFile, String classPath) { try { // extract dependent JARs List<File> jarFiles = new ArrayList<File>(); jarFiles.addAll(extractClassPath(new File(jarFile))); jarFiles.add(new File(jarFile)); // additional classpath available? String cp = buildClassPath(jarFiles); if (classPath != null) { cp += File.pathSeparatorChar + classPath; } // set soot-class-path sootOpt.set_soot_classpath(cp); // finally, run soot loadClassesIntoScene(enumClasses(new File(jarFile))); } catch (Exception e) { throw e; } } private void runWithApk(String apkFile, String androidPlatformPath) { try { sootOpt.set_src_prec(soot.options.Options.src_prec_apk); // https://github.com/Sable/android-platforms if (androidPlatformPath == null) { throw new RuntimeException( "You need to pass the android-platforms folder from https://github.com/Sable/android-platforms"); } sootOpt.set_android_jars(androidPlatformPath); List<String> procdir = new LinkedList<String>(); procdir.add(apkFile); sootOpt.set_process_dir(procdir); // finally, run soot loadClassesIntoScene(enumClasses(new File(apkFile))); } catch (Exception e) { throw e; } } /** * Runs Soot by using a path (e.g., from Joogie) * * @param path * Path * @param classPath Optional classpath (may be null) * @param classPath * Optional classpath (may be null) */ private void runWithPath(String path, String classPath) { try { // dependent JAR files List<File> jarFiles = new ArrayList<File>(); // additional classpath available? String cp = buildClassPath(jarFiles); if (classPath != null) { cp += File.pathSeparatorChar + classPath; } // set soot-class-path sootOpt.set_soot_classpath(cp); sootOpt.set_src_prec(soot.options.Options.src_prec_only_class); List<String> processDirs = new LinkedList<String>(); processDirs.add(path); if (Options.v().useBuiltInSpecs()) { File specDir = new File("spec_stuff/"); writeSpecPackageToDisc(specDir); processDirs.add(specDir.getAbsolutePath()); } if (Options.v().checkMixedJavaClassFiles()) { enforceNoSrcPolicy(processDirs); } sootOpt.set_process_dir(processDirs); // finally, run soot loadClassesIntoScene(new LinkedList<String>()); // now set the main class inferMainMethod(); } catch (Exception e) { throw e; } } private void inferMainMethod() { SootMethod mainMethod = null; SootClass mainClass = null; boolean toManyMains = false; StringBuilder sb = new StringBuilder(); for (SootClass c : Scene.v().getApplicationClasses()) { if (c.declaresMethod("main", Arrays.asList((Type)ArrayType.v(RefType.v("java.lang.String"), 1)), VoidType.v())) { if (mainMethod != null) { toManyMains = true; } mainMethod = c.getMethod("main", Arrays.asList((Type)ArrayType.v(RefType.v("java.lang.String"), 1)), VoidType.v()); mainClass = c; // System.err.println(mainMethod.getSignature()); sb.append(mainMethod.getSignature()); sb.append("\n"); } } Verify.verify(mainClass!=null && mainMethod!=null, "No main method found. Terminating."); Scene.v().setMainClass(mainClass); if (toManyMains) { System.err.println("More than one main found:"); System.err.println(sb.toString()); System.err.println("Picking the last one."); } } /** * Soot only runs properly if there are only class files in the processed * directory. * If there are source files mixed with class files, stange errors happen * because * soot mixes them in the scene. * To avoid these random error, we fail early. * * @param dirs */ private void enforceNoSrcPolicy(List<String> dirs) { for (String dir : dirs) { for (File f : Files.fileTreeTraverser().preOrderTraversal(new File(dir))) { if (f != null && f.isFile() && f.getName().endsWith(".java")) { StringBuilder sb = new StringBuilder(); sb.append("Found mix of source and class files in folder "); sb.append(f.getParent()); sb.append("\nSoot expects a directory tree that only contains class files.\n"); sb.append("Please create a directory, compile your code with javac -d [dir]\n"); sb.append("and pass us this dir. Sorry for the inconvenience."); System.err.println(sb.toString()); throw new UnsupportedOperationException("Bad value for -j argument."); } } } } /** * Run Soot and creates an inter-procedural callgraph that could be loaded * by Soot. * * @param classes * additional classes that need to be loaded (e.g., when * analyzing jars) */ protected void loadClassesIntoScene(List<String> classes) { sootOpt.set_keep_line_number(true); sootOpt.set_prepend_classpath(true); // -pp sootOpt.set_output_format(soot.options.Options.output_format_none); // prevent strange assertion optimization. sootOpt.setPhaseOption("jop.cpf", "enabled:false"); sootOpt.set_allow_phantom_refs(true); for (String s : classes) { Scene.v().addBasicClass(s, SootClass.BODIES); } // TODO: hack for the implicit entry points. Scene.v().addBasicClass("java.lang.System", SootClass.SIGNATURES); Scene.v().addBasicClass("java.lang.Thread", SootClass.SIGNATURES); Scene.v().addBasicClass("java.lang.ThreadGroup", SootClass.SIGNATURES); Scene.v().addBasicClass("java.lang.ClassLoader", SootClass.SIGNATURES); Scene.v().addBasicClass("java.security.PrivilegedActionException", SootClass.SIGNATURES); Scene.v().addBasicClass("java.lang.ref.Finalizer", SootClass.SIGNATURES); try { // redirect soot output into a stream. ByteArrayOutputStream baos = new ByteArrayOutputStream(); soot.G.v().out = new PrintStream(baos, true, "utf-8"); // Now load the soot classes. Scene.v().loadBasicClasses(); // if (resolvedClassNames.isEmpty()) { Scene.v().loadNecessaryClasses(); // } else { // //TODO: Is this reachable? // loadNecessaryClasses(); // } PackManager.v().runPacks(); createAssertionClass(); /* * TODO: apply some preprocessing stuff like: * soot.jimple.toolkits.base or maybe the optimize option from soot. * TODO: NOT SURE IF THE CODE BELOW IS NECESSARY! */ for (SootClass sc : Scene.v().getClasses()) { if (sc.resolvingLevel() < SootClass.SIGNATURES) { sc.setResolvingLevel(SootClass.SIGNATURES); } // if (classes.contains(sc.getName())) { // sc.setApplicationClass(); // } } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e.toString()); } catch (RuntimeException e) { throw e; } } // private void loadNecessaryClasses() { // for (String eachClassname : resolvedClassNames) { // final SootClass theClass = Scene.v().loadClassAndSupport(eachClassname); // theClass.setApplicationClass(); // } // } public static final String assertionClassName = "JayHornAssertions"; public static final String assertionProcedureName = "super_crazy_assertion"; public static final String exceptionGlobalName = "lastExceptionThrown"; /** * TODO */ public static void createAssertionClass() { if (Scene.v().containsClass(assertionClassName)) { throw new RuntimeException("Don't try to call me twice!"); } SootClass sClass = new SootClass(assertionClassName, Modifier.PUBLIC); sClass.setSuperclass(Scene.v().getSootClass("java.lang.Object")); // add a static field to keep track of thrown exceptions. SootClass javaThrowableClass = Scene.v().getSootClass("java.lang.Throwable"); SootField exceptionGlobal = new SootField(exceptionGlobalName, javaThrowableClass.getType(), Modifier.PUBLIC | Modifier.STATIC); sClass.addField(exceptionGlobal); // add a method to model assertions. SootMethod internalAssertMethod = new SootMethod(assertionProcedureName, Arrays.asList(new Type[] { BooleanType.v() }), VoidType.v(), Modifier.PUBLIC | Modifier.STATIC); sClass.addMethod(internalAssertMethod); JimpleBody body = Jimple.v().newBody(internalAssertMethod); internalAssertMethod.setActiveBody(body); body.insertIdentityStmts(); body.getUnits().add(Jimple.v().newReturnVoidStmt()); SootMethod staticInitializer = new SootMethod(SootMethod.staticInitializerName, Arrays.asList(new Type[] {}), VoidType.v(), Modifier.PUBLIC | Modifier.STATIC); body = Jimple.v().newBody(staticInitializer); staticInitializer.setActiveBody(body); body.insertIdentityStmts(); body.getUnits().add(Jimple.v().newReturnVoidStmt()); sClass.addMethod(staticInitializer); Scene.v().addClass(sClass); sClass.setApplicationClass(); } /** * Returns the class path argument for Soot * * @param files * Files in the class path * @return Class path argument for Soot */ protected String buildClassPath(List<File> files) { StringBuilder sb = new StringBuilder(); for (File file : files) { sb.append(file.getPath() + File.pathSeparatorChar); } return sb.toString(); } /** * Extracts dependent JARs from the JAR's manifest * * @param file * JAR file object * @returns jarFiles List of dependent JARs */ protected List<File> extractClassPath(File file) { List<File> jarFiles = new LinkedList<File>(); try { // open JAR file JarFile jarFile = new JarFile(file); // get manifest and their main attributes Manifest manifest = jarFile.getManifest(); if (manifest == null) { jarFile.close(); return jarFiles; } Attributes mainAttributes = manifest.getMainAttributes(); if (mainAttributes == null) { jarFile.close(); return jarFiles; } String classPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH); // close JAR file jarFile.close(); // empty class path? if (null == classPath) return jarFiles; // look for dependent JARs String[] classPathItems = classPath.split(" "); for (String classPathItem : classPathItems) { if (classPathItem.endsWith(".jar")) { // add jar jarFiles.add(new File(file.getParent(), classPathItem)); } } } catch (IOException e) { throw new RuntimeException(e.toString()); } return jarFiles; } /** * Enumerates all classes in a JAR file * * @param file * a Jar file * @returns list of classes in the Jar file. */ protected List<String> enumClasses(File file) { List<String> classes = new LinkedList<String>(); try { // open JAR file JarFile jarFile = new JarFile(file); Enumeration<JarEntry> entries = jarFile.entries(); // iterate JAR entries while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); if (entryName.endsWith(".class")) { // get class String className = entryName.substring(0, entryName.length() - ".class".length()); className = className.replace('/', '.'); // add class classes.add(className); } } // close JAR file jarFile.close(); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } return classes; } /* * ======= below deals with writing out the spec classes */ /** * Writes all classes from the soottocfg.spec to targetDir * so that we can use them later when re-writing the bytecode. * * @param targetDir */ protected void writeSpecPackageToDisc(File targetDir) { if (!targetDir.isDirectory()) { if (!targetDir.mkdirs()) { throw new RuntimeException("Can't write to disk"); } } try { ClassLoader cl = this.getClass().getClassLoader(); ClassPath cp = ClassPath.from(cl); for (ClassInfo ci : cp.getTopLevelClassesRecursive("soottocfg.spec")) { StringBuilder sb = new StringBuilder(); sb.append(targetDir.getAbsolutePath()); sb.append(File.separator); sb.append(ci.getPackageName().replace(".", File.separator)); File outFileDir = new File(sb.toString()); if (!outFileDir.exists() && !outFileDir.mkdirs()) { throw new RuntimeException("Couldn't generate dirs for " + sb.toString()); } sb.append(File.separator); sb.append(ci.getSimpleName()); sb.append(".class"); File outFile = new File(sb.toString()); try (InputStream inputStream = cl.getResourceAsStream(ci.getResourceName()); OutputStream outputStream = new FileOutputStream(outFile);) { ByteStreams.copy(inputStream, outputStream); } catch (Throwable e) { e.printStackTrace(); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }