/* * <Manal project is an eclipse plugin for the automation of malware analysis.> * Copyright (C) <2014> <Nikolay Akatyev, Hojun Son> * This file is part of Manal project. * * Manal project 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, version 3 of the License. * * Manal project 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 Manal project. If not, see <http://www.gnu.org/licenses/>. * * Contact information of contributors: * - Nikolay Akatyev: nikolay.akatyev@gmail.com * - Hojun Son: smuoon4680@gmail.com */ package com.dforensic.plugin.manal.input.flowdroid; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.dforensic.plugin.manal.model.ApiDescriptor; import com.dforensic.plugin.manal.model.ProjectProperties; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.xmlpull.v1.XmlPullParserException; import soot.jimple.infoflow.InfoflowResults; import soot.jimple.infoflow.IInfoflow.CallgraphAlgorithm; import soot.jimple.infoflow.InfoflowResults.SinkInfo; import soot.jimple.infoflow.InfoflowResults.SourceInfo; import soot.jimple.infoflow.android.SetupApplication; import soot.jimple.infoflow.android.AndroidSourceSinkManager.LayoutMatchingMode; import soot.jimple.infoflow.handlers.ResultsAvailableHandler; import soot.jimple.infoflow.solver.IInfoflowCFG; import soot.jimple.infoflow.taintWrappers.EasyTaintWrapper; public class FlowDroidExecutor { public interface FlowDroidCallback { public void onExecutionDone(); } /** * path to apk-file */ private String mApkPath = null; /** * path to android-dir (path/android-platforms/) */ private String mAndroidSdkPath = null; private class FlowDroidResultsAvailableHandler implements ResultsAvailableHandler { private final BufferedWriter wr; private FlowDroidResultsAvailableHandler() { this.wr = null; } private FlowDroidResultsAvailableHandler(BufferedWriter wr) { this.wr = wr; } private void print(String string) { try { System.out.println(string); if (wr != null) wr.write(string + "\n"); } catch (IOException ex) { // ignore } } @Override public void onResultsAvailable(IInfoflowCFG cfg, InfoflowResults results) { cleanAfterExec(); if (mSinks == null) { mSinks = new ArrayList<ApiDescriptor>(); } // Dump the results if (results == null) { print("No results found."); } else { for (SinkInfo sink : results.getResults().keySet()) { ApiDescriptor sinkDesc = new ApiDescriptor(sink); sinkDesc.setSootMethod(cfg.getMethodOf(sink.getContext())); mSinks.add(sinkDesc); print("Found a flow to sink " + sink + ", from the following sources:"); for (SourceInfo source : results.getResults().get(sink)) { ApiDescriptor sourceDesc = new ApiDescriptor(source); sourceDesc.setSootMethod(cfg.getMethodOf(source .getContext())); // sourceDesc.addDependency(sinkDesc); sinkDesc.addDependency(sourceDesc); // mSources.add(sourceDesc); print("\t- " + source.getSource() + " (in " + cfg.getMethodOf(source.getContext()) .getSignature() + ")"); if (source.getPath() != null && !source.getPath().isEmpty()) print("\t\ton Path " + source.getPath()); } } } } } private List<ApiDescriptor> mSinks = null; // not use: there would be many repeating sources. // make a UUID: signature + class + line // private List<ApiDescriptor> mSources = null; private FlowDroidCallback mFlowDroidCallback = null; private String command; private boolean generate = false; private int timeout = -1; private int sysTimeout = -1; private boolean stopAfterFirstFlow = false; private boolean implicitFlows = false; private boolean staticTracking = false; // true; private boolean enableCallbacks = false; // true; private boolean enableExceptions = false; // true; private int accessPathLength = 5; private LayoutMatchingMode layoutMatchingMode = LayoutMatchingMode.MatchSensitiveOnly; private boolean flowSensitiveAliasing = false; // true; private boolean computeResultPaths = true; private boolean aggressiveTaintWrapper = false; private CallgraphAlgorithm callgraphAlgorithm = CallgraphAlgorithm.AutomaticSelection; private static boolean DEBUG = false; public void registerFlowDroidCallback(FlowDroidCallback cb) { mFlowDroidCallback = cb; } public void unregisterFlowDroidCallback(FlowDroidCallback cb) { mFlowDroidCallback = null; } public List<ApiDescriptor> getDiscoveredSinks() { return mSinks; } /* private static boolean parseAdditionalOptions(String[] args) { int i = 2; while (i < args.length) { if (args[i].equalsIgnoreCase("--timeout")) { timeout = Integer.valueOf(args[i + 1]); i += 2; } else if (args[i].equalsIgnoreCase("--systimeout")) { sysTimeout = Integer.valueOf(args[i + 1]); i += 2; } else if (args[i].equalsIgnoreCase("--singleflow")) { stopAfterFirstFlow = true; i++; } else if (args[i].equalsIgnoreCase("--implicit")) { implicitFlows = true; i++; } else if (args[i].equalsIgnoreCase("--nostatic")) { staticTracking = false; i++; } else if (args[i].equalsIgnoreCase("--aplength")) { accessPathLength = Integer.valueOf(args[i + 1]); i += 2; } else if (args[i].equalsIgnoreCase("--cgalgo")) { String algo = args[i + 1]; if (algo.equalsIgnoreCase("AUTO")) callgraphAlgorithm = CallgraphAlgorithm.AutomaticSelection; else if (algo.equalsIgnoreCase("VTA")) callgraphAlgorithm = CallgraphAlgorithm.VTA; else if (algo.equalsIgnoreCase("RTA")) callgraphAlgorithm = CallgraphAlgorithm.RTA; else { System.err.println("Invalid callgraph algorithm"); return false; } i += 2; } else if (args[i].equalsIgnoreCase("--nocallbacks")) { enableCallbacks = false; i++; } else if (args[i].equalsIgnoreCase("--noexceptions")) { enableExceptions = false; i++; } else if (args[i].equalsIgnoreCase("--layoutmode")) { String algo = args[i + 1]; if (algo.equalsIgnoreCase("NONE")) layoutMatchingMode = LayoutMatchingMode.NoMatch; else if (algo.equalsIgnoreCase("PWD")) layoutMatchingMode = LayoutMatchingMode.MatchSensitiveOnly; else if (algo.equalsIgnoreCase("ALL")) layoutMatchingMode = LayoutMatchingMode.MatchAll; else { System.err.println("Invalid layout matching mode"); return false; } i += 2; } else if (args[i].equalsIgnoreCase("--aliasflowins")) { flowSensitiveAliasing = false; i++; } else if (args[i].equalsIgnoreCase("--nopaths")) { computeResultPaths = false; i++; } else if (args[i].equalsIgnoreCase("--aggressivetw")) { aggressiveTaintWrapper = false; i++; } else i++; } return true; } private static boolean validateAdditionalOptions() { if (timeout > 0 && sysTimeout > 0) { return false; } return true; } private static void runAnalysisTimeout(final String fileName, final String androidJar) { FutureTask<InfoflowResults> task = new FutureTask<InfoflowResults>( new Callable<InfoflowResults>() { @Override public InfoflowResults call() throws Exception { final BufferedWriter wr = new BufferedWriter( new FileWriter("_out_" + new File(fileName).getName() + ".txt")); try { final long beforeRun = System.nanoTime(); wr.write("Running data flow analysis...\n"); final InfoflowResults res = runAnalysis(fileName, androidJar); wr.write("Analysis has run for " + (System.nanoTime() - beforeRun) / 1E9 + " seconds\n"); wr.flush(); return res; } finally { if (wr != null) wr.close(); } } }); ExecutorService executor = Executors.newFixedThreadPool(1); executor.execute(task); try { System.out.println("Running infoflow task..."); task.get(timeout, TimeUnit.MINUTES); } catch (ExecutionException e) { System.err .println("Infoflow computation failed: " + e.getMessage()); e.printStackTrace(); } catch (TimeoutException e) { System.err.println("Infoflow computation timed out: " + e.getMessage()); e.printStackTrace(); } catch (InterruptedException e) { System.err.println("Infoflow computation interrupted: " + e.getMessage()); e.printStackTrace(); } // Make sure to remove leftovers executor.shutdown(); } private static void runAnalysisSysTimeout(final String fileName, final String androidJar) { String classpath = System.getProperty("java.class.path"); String javaHome = System.getProperty("java.home"); String executable = "/usr/bin/timeout"; String[] command = new String[] { executable, "-s", "KILL", sysTimeout + "m", javaHome + "/bin/java", "-cp", classpath, "soot.jimple.infoflow.android.TestApps.Test", fileName, androidJar, stopAfterFirstFlow ? "--singleflow" : "--nosingleflow", implicitFlows ? "--implicit" : "--noimplicit", staticTracking ? "--static" : "--nostatic", "--aplength", Integer.toString(accessPathLength), "--cgalgo", callgraphAlgorithmToString(callgraphAlgorithm), enableCallbacks ? "--callbacks" : "--nocallbacks", enableExceptions ? "--exceptions" : "--noexceptions", "--layoutmode", layoutMatchingModeToString(layoutMatchingMode), flowSensitiveAliasing ? "--aliasflowsens" : "--aliasflowins", computeResultPaths ? "--paths" : "--nopaths", aggressiveTaintWrapper ? "--aggressivetw" : "--nonaggressivetw" }; System.out.println("Running command: " + executable + " " + command); try { ProcessBuilder pb = new ProcessBuilder(command); pb.redirectOutput(new File("_out_" + new File(fileName).getName() + ".txt")); pb.redirectError(new File("err_" + new File(fileName).getName() + ".txt")); Process proc = pb.start(); proc.waitFor(); } catch (IOException ex) { System.err.println("Could not execute timeout command: " + ex.getMessage()); ex.printStackTrace(); } catch (InterruptedException ex) { System.err.println("Process was interrupted: " + ex.getMessage()); ex.printStackTrace(); } } */ public String callgraphAlgorithmToString(CallgraphAlgorithm algorihm) { switch (algorihm) { case AutomaticSelection: return "AUTO"; case VTA: return "VTA"; case RTA: return "RTA"; default: return "unknown"; } } public String layoutMatchingModeToString(LayoutMatchingMode mode) { switch (mode) { case NoMatch: return "NONE"; case MatchSensitiveOnly: return "PWD"; case MatchAll: return "ALL"; default: return "unknown"; } } private InfoflowResults runAnalysis(final String fileName, final String androidJar) { try { final long beforeRun = System.nanoTime(); final SetupApplication app = new SetupApplication(androidJar, fileName); app.setStopAfterFirstFlow(stopAfterFirstFlow); app.setEnableImplicitFlows(implicitFlows); app.setEnableStaticFieldTracking(staticTracking); app.setEnableCallbacks(enableCallbacks); app.setEnableExceptionTracking(enableExceptions); app.setAccessPathLength(accessPathLength); app.setLayoutMatchingMode(layoutMatchingMode); app.setFlowSensitiveAliasing(flowSensitiveAliasing); app.setComputeResultPaths(computeResultPaths); final EasyTaintWrapper taintWrapper; if (new File("../soot-infoflow/EasyTaintWrapperSource.txt") .exists()) taintWrapper = new EasyTaintWrapper( "../soot-infoflow/EasyTaintWrapperSource.txt"); else taintWrapper = new EasyTaintWrapper( "EasyTaintWrapperSource.txt"); taintWrapper.setAggressiveMode(aggressiveTaintWrapper); app.setTaintWrapper(taintWrapper); app.calculateSourcesSinksEntrypoints("SourcesAndSinks.txt"); if (DEBUG) { app.printEntrypoints(); app.printSinks(); app.printSources(); } System.out.println("Running data flow analysis..."); final InfoflowResults res = app .runInfoflow(new FlowDroidResultsAvailableHandler()); System.out.println("Analysis has run for " + (System.nanoTime() - beforeRun) / 1E9 + " seconds"); if (mFlowDroidCallback != null) { mFlowDroidCallback.onExecutionDone(); } return res; } catch (IOException ex) { System.err.println("Could not read file: " + ex.getMessage()); ex.printStackTrace(); throw new RuntimeException(ex); } catch (XmlPullParserException ex) { System.err.println("Could not parse xml: " + ex.getMessage()); ex.printStackTrace(); throw new RuntimeException(ex); } } /* private static void printUsage() { System.out .println("FlowDroid (c) Secure Software Engineering Group @ EC SPRIDE"); System.out.println(); System.out .println("Incorrect arguments: [0] = apk-file, [1] = android-jar-directory"); System.out.println("Optional further parameters:"); System.out.println("\t--TIMEOUT n Time out after n seconds"); System.out .println("\t--SYSTIMEOUT n Hard time out (kill process) after n seconds, Unix only"); System.out.println("\t--SINGLEFLOW Stop after finding first leak"); System.out.println("\t--IMPLICIT Enable implicit flows"); System.out.println("\t--NOSTATIC Disable static field tracking"); System.out.println("\t--NOEXCEPTIONS Disable exception tracking"); System.out.println("\t--APLENGTH n Set access path length to n"); System.out.println("\t--CGALGO x Use callgraph algorithm x"); System.out.println("\t--NOCALLBACKS Disable callback analysis"); System.out .println("\t--LAYOUTMODE x Set UI control analysis mode to x"); System.out .println("\t--ALIASFLOWINS Use a flow insensitive alias search"); System.out.println("\t--NOPATHS Do not compute result paths"); System.out .println("\t--AGGRESSIVETW Use taint wrapper in aggressive mode"); System.out.println(); System.out.println("Supported callgraph algorithms: AUTO, RTA, VTA"); System.out.println("Supported layout mode algorithms: NONE, PWD, ALL"); } */ public void execute() { // mApkPath = new String( // "D:\\Documents\\Research\\eclipse_plugin\\Manal\\PhoneDataLeakTest\\bin\\PhoneDataLeakTest.apk"); // mAndroidSdkPath = new String( // "D:\\Documents\\LGE MC 5PM 1PL\\android-sdk_r10-windows\\platforms"); mApkPath = ProjectProperties.getApkNameVal(); mAndroidSdkPath = ProjectProperties.getAndroidPathVal(); if (mApkPath == null) { mApkPath = new String( "D:\\Workspaces\\SsSDK\\contest_dev\\Manal\\PhoneDataLeakTest\\bin\\PhoneDataLeakTest.apk"); } if (mAndroidSdkPath == null) { mAndroidSdkPath = new String( "D:\\Workspaces\\android-sdk\\platforms"); } if (mSinks != null) { mSinks.clear(); mSinks = null; } /* if (mSources != null) { mSources.clear(); mSources = null; } */ // start with cleanup: // TODO when store output /* BufferedReader outputDir = ResUtils.openProjectFile(OUTPUT_PATH + "JimpleOutput"); if (outputDir.isDirectory()) { boolean success = true; for (File f : outputDir.listFiles()) { success = success && f.delete(); } if (!success) { System.err.println("Cleanup of output directory " + outputDir + " failed!"); } outputDir.delete(); } */ File apkFile = new File(mApkPath); String extension = apkFile.getName().substring( apkFile.getName().lastIndexOf(".")); if (apkFile.isDirectory() || !extension.equalsIgnoreCase(".apk")) { System.err.println("Invalid input file format: " + extension); return; } // Run the analysis // TODO until do optimization // run with default parameters /* if (timeout > 0) runAnalysisTimeout(mApkPath, mAndroidSdkPath); else if (sysTimeout > 0) runAnalysisSysTimeout(mApkPath, mAndroidSdkPath); else */ prepareExec(); runAnalysis(mApkPath, mAndroidSdkPath); System.gc(); } public void setApkPath(String path) { mApkPath = path; } public void setAndroidSdkPath(String path) { mAndroidSdkPath = path; } /** * Copy FlowDroid files to the root directory of Eclipse in order to be open * by a relative name. */ private void prepareExec() { // Copy files from the plugin directory // to the root directory. try { File rootDir = new File(".").getAbsoluteFile(); File flowDroidFilesDir = new File(FileLocator.resolve(FileLocator.find( Platform.getBundle("com.dforensic.plugin.manal"), new Path("files/flowdroid/"), Collections.EMPTY_MAP)).getPath()); copyDirectory(flowDroidFilesDir, rootDir); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Remove FlowDroid related files from the root directory of Eclipse. */ private void cleanAfterExec() { try { File rootDir = new File(".").getAbsoluteFile(); String[] flowDroidFileNames = getFlowDroidFileNames(); String[] children = rootDir.list(); for (int j = 0; j < flowDroidFileNames.length; j++) { for (int i = 0; i < children.length; i++) { if (flowDroidFileNames[j].equals(children[i])) { Files.delete(new File(rootDir.getAbsolutePath() + "\\" + flowDroidFileNames[j]).toPath()); } } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private String[] getFlowDroidFileNames() { String[] fileNames = { "AndroidCallbacks.txt", "EasyTaintWrapperConversion.txt", "EasyTaintWrapperSource.txt", "SourcesAndSinks.txt", }; return fileNames; } private void copyDirectory(File sourceLocation, File targetLocation) throws IOException { if (sourceLocation.isDirectory()) { // if (!targetLocation.exists()) { // targetLocation.mkdir(); // } String[] children = sourceLocation.list(); for (int i = 0; i < children.length; i++) { copyDirectory(new File(sourceLocation, children[i]), new File( targetLocation, children[i])); } } else { InputStream in = new FileInputStream(sourceLocation); OutputStream out = new FileOutputStream(targetLocation); // Copy the bits from instream to outstream byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); } } }