diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/AndroidLibraryClassPatcher.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/AndroidLibraryClassPatcher.java new file mode 100644 index 000000000..6e06314a2 --- /dev/null +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/AndroidLibraryClassPatcher.java @@ -0,0 +1,222 @@ +package soot.jimple.infoflow.android; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import soot.BooleanConstant; +import soot.BooleanType; +import soot.Hierarchy; +import soot.Local; +import soot.LocalGenerator; +import soot.Modifier; +import soot.RefType; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Type; +import soot.UnitPatchingChain; +import soot.jimple.Jimple; +import soot.jimple.JimpleBody; +import soot.jimple.NopStmt; +import soot.jimple.StringConstant; +import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.cfg.LibraryClassPatcher; +import soot.jimple.infoflow.util.SootMethodRepresentationParser; +import soot.jimple.toolkits.scalar.NopEliminator; +import soot.util.Chain; + +/** + * In addition to the normal JVM library classes, this class also patches + * certain Android library classes. + */ +public class AndroidLibraryClassPatcher extends LibraryClassPatcher { + + @Override + public void patchLibraries() { + super.patchLibraries(); + + patchComponentFactory(); + } + + /** + * The generated implementation of this method are semantically equivalent to the AppComponentFactory in Android. + * @see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/AppComponentFactory.java + */ + protected void patchComponentFactory() { + SootClass sc = Scene.v().forceResolve(AndroidEntryPointConstants.APPCOMPONENTFACTORYCLASS, + SootClass.SIGNATURES); + + patchInstantiate(sc, AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEAPPLICATION, + getAllNames(AndroidEntryPointConstants.APPLICATIONCLASS)); + patchInstantiate(sc, AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEACTIVITY, + getAllNames(AndroidEntryPointConstants.ACTIVITYCLASS)); + patchInstantiate(sc, AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEPROVIDER, + getAllNames(AndroidEntryPointConstants.BROADCASTRECEIVERCLASS)); + patchInstantiate(sc, AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATERECEIVER, + getAllNames(AndroidEntryPointConstants.BROADCASTRECEIVERCLASS)); + + patchInstantiateClassLoader(sc); + + } + + /** + * Patches the instantiate classloader class. + * It returns the default class loader unmodified. + * @param sc the class of the app component factory + */ + private void patchInstantiateClassLoader(SootClass sc) { + SootMethod smInstantiate = getOrCreateMethod(sc, + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATECLASSLOADER); + JimpleBody body = Jimple.v().newBody(smInstantiate); + smInstantiate.setActiveBody(body); + body.insertIdentityStmts(); + body.getUnits().add(Jimple.v().newReturnStmt(body.getParameterLocal(0))); + + } + + /** + * Returns all class names that could be instantiated when + * instantiating a class with the given class name, i.e. all subclasses/implementers. + * @param className the class name (could also represent an interface) + * @return a string array of all possible names. + */ + protected String[] getAllNames(String className) { + List names = new ArrayList<>(); + SootClass sc = Scene.v().getSootClassUnsafe(className); + if (sc == null) + return new String[0]; + Hierarchy fh = Scene.v().getActiveHierarchy(); + List components; + if (sc.isInterface()) { + components = fh.getImplementersOf(sc); + } else { + components = fh.getSubclassesOf(sc); + + } + for (SootClass c : components) { + if (c.isConcrete()) + names.add(c.getName()); + } + return names.toArray(new String[names.size()]); + } + + /** + * Patches an instantiate method. Generates code equivalent to the following: + * + * + * public void instantiateActivity(ClassLoader cl, String className, Intent intent) + * { + * + * if (className.equals("foo.bar.MainActivity")) + * return new foo.bar.MainActivity(); //(1) + * if (className.equals("foo.bar.FooActivity")) + * return new foo.bar.FooActivity(); //(2) + * return cl.loadClass(className).newInstance(); //(3) + * + * } + * + * The instantiation statements (1) and (2) are used to help SPARK and other static algorithms to find + * allocation sites. (3) is the fallback that would normally be the implementation when using Android's default + * app component factory. + * @param sc the class of the app component factory + * @param subsig the sub signature of the method, in our example case instantiateActivity + * @param names the names for each possible class instantiation, in our example case "foo.bar.MainActivity", "foo.bar.FooActivity" + */ + protected void patchInstantiate(SootClass sc, String subsig, String... names) { + + if (!sc.isLibraryClass()) + sc.setLibraryClass(); + + // We sometimes seem to be missing the constructor + SootMethod smInstantiate = getOrCreateMethod(sc, subsig); + Jimple j = Jimple.v(); + JimpleBody body = j.newBody(smInstantiate); + if (smInstantiate.isPhantom()) + smInstantiate.setPhantom(false); + smInstantiate.setModifiers(Modifier.PUBLIC); + smInstantiate.setActiveBody(body); + body.insertIdentityStmts(); + Chain locals = body.getLocals(); + UnitPatchingChain units = body.getUnits(); + Scene scene = Scene.v(); + Local ret = j.newLocal("returnVal", smInstantiate.getReturnType()); + Local obj = j.newLocal("obj", scene.getObjectType()); + Local cls = j.newLocal("clazz", RefType.v("java.lang.Class")); + locals.add(ret); + locals.add(obj); + locals.add(cls); + LocalGenerator generator = Scene.v().createLocalGenerator(body); + + Local cmp = null; + NopStmt next = null; + for (String n : names) { + if (n != null) { + RefType p = RefType.v(n); + if (p.hasSootClass() && p.getSootClass().isApplicationClass()) { + SootMethod ctor = p.getSootClass().getMethodUnsafe("void ()"); + if (ctor != null) { + if (cmp == null) { + cmp = j.newLocal("bool", BooleanType.v()); + locals.add(cmp); + } + if (next != null) + units.add(next); + units.add(j.newAssignStmt(cmp, + j.newVirtualInvokeExpr(body.getParameterLocal(1), + scene.makeMethodRef(RefType.v("java.lang.String").getSootClass(), + "boolean equals(java.lang.Object)", false), + StringConstant.v(p.getClassName())))); + next = j.newNopStmt(); + units.add(j.newIfStmt(j.newEqExpr(cmp, BooleanConstant.v(false)), next)); + Local c = generator.generateLocal(p); + units.add(j.newAssignStmt(c, j.newNewExpr(p))); + units.add(j.newInvokeStmt(j.newSpecialInvokeExpr(c, ctor.makeRef()))); + units.add(j.newReturnStmt(c)); + } + } + } + } + if (next != null) + units.add(next); + units.add( + j.newAssignStmt(cls, + j.newVirtualInvokeExpr(body.getParameterLocal(0), + scene.makeMethodRef(RefType.v("java.lang.ClassLoader").getSootClass(), + "java.lang.Class loadClass(java.lang.String)", false), + body.getParameterLocal(1)))); + units.add(j.newAssignStmt(obj, j.newVirtualInvokeExpr(cls, scene + .makeMethodRef(RefType.v("java.lang.Class").getSootClass(), "java.lang.Object newInstance()", false)))); + units.add(j.newAssignStmt(ret, j.newCastExpr(obj, obj.getType()))); + units.add(j.newReturnStmt(ret)); + NopEliminator.v().transform(body); + + } + + /** + * Creates a method if it doesn't exist. Otherwise, it returns the existing method + * @param sc the class where the method is being looked for + * @param subsig the sub signature of the method + * @return the method + */ + private static SootMethod getOrCreateMethod(SootClass sc, String subsig) { + SootMethod p = sc.getMethodUnsafe(subsig); + if (p != null) + return p; + + SootMethodRepresentationParser parser = SootMethodRepresentationParser.v(); + String name = parser.getMethodNameFromSubSignature(subsig); + + Scene scene = Scene.v(); + String sreturnType = parser.getReturnTypeFromSubSignature(subsig); + + String[] paramTypes = parser.getParameterTypesFromSubSignature(subsig); + Type returnType = scene.getTypeUnsafe(sreturnType, false); + Type[] aparamTypes = new Type[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + aparamTypes[i] = scene.getTypeUnsafe(paramTypes[i], false); + } + p = Scene.v().makeSootMethod(name, Arrays.asList(aparamTypes), returnType, Modifier.PUBLIC); + return sc.getOrAddMethod(p); + } +} diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/InfoflowAndroidConfiguration.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/InfoflowAndroidConfiguration.java index a763f0a23..324f7ef49 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/InfoflowAndroidConfiguration.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/InfoflowAndroidConfiguration.java @@ -320,6 +320,7 @@ public static class CallbackConfiguration { private int maxCallbackAnalysisDepth = -1; private boolean serializeCallbacks = false; private String callbacksFile = ""; + private boolean excludeStubsFromCallGraph = true; /** * Copies the settings of the given configuration into this configuration object @@ -335,6 +336,7 @@ public void merge(CallbackConfiguration cbConfig) { this.maxCallbackAnalysisDepth = cbConfig.maxCallbackAnalysisDepth; this.serializeCallbacks = cbConfig.serializeCallbacks; this.callbacksFile = cbConfig.callbacksFile; + this.excludeStubsFromCallGraph = cbConfig.excludeStubsFromCallGraph; } /** @@ -510,6 +512,22 @@ public void setCallbacksFile(String callbacksFile) { this.callbacksFile = callbacksFile; } + /** + * Returns whether FlowDroid should exclude stub methods when computing the call graph + * @return true if stubs should be excluded + */ + public boolean getExcludeStubsFromCallGraph() { + return excludeStubsFromCallGraph; + } + + /** + * Sets whether FlowDroid should exclude stub methods when computing the call graph + * @param value true if stubs should be excluded + */ + public void setExcludeStubsFromCallGraph(boolean value) { + this.excludeStubsFromCallGraph = value; + } + @Override public int hashCode() { final int prime = 31; @@ -522,6 +540,7 @@ public int hashCode() { result = prime * result + maxCallbackAnalysisDepth; result = prime * result + maxCallbacksPerComponent; result = prime * result + (serializeCallbacks ? 1231 : 1237); + result = prime * result + (excludeStubsFromCallGraph ? 1231 : 1237); return result; } @@ -553,6 +572,8 @@ public boolean equals(Object obj) { return false; if (serializeCallbacks != other.serializeCallbacks) return false; + if (excludeStubsFromCallGraph != other.excludeStubsFromCallGraph) + return false; return true; } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/SetupApplication.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/SetupApplication.java index 00ba5ad96..84efb0a76 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/SetupApplication.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/SetupApplication.java @@ -1286,7 +1286,7 @@ private void initializeSoot() { } protected LibraryClassPatcher getLibraryClassPatcher() { - return new LibraryClassPatcher(); + return new AndroidLibraryClassPatcher(); } /** @@ -1913,6 +1913,9 @@ private Set getComponentsToAnalyze(SootClass component) { IAndroidApplication app = manifest.getApplication(); if (app != null) { + String factoryName = app.getAppComponentFactory(); + if (factoryName != null && !factoryName.isEmpty()) + components.add(Scene.v().getSootClassUnsafe(factoryName)); String applicationName = app.getName(); if (applicationName != null && !applicationName.isEmpty()) components.add(Scene.v().getSootClassUnsafe(applicationName)); diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/AbstractCallbackAnalyzer.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/AbstractCallbackAnalyzer.java index 84bd41461..d5fa81cfc 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/AbstractCallbackAnalyzer.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/AbstractCallbackAnalyzer.java @@ -18,6 +18,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -66,6 +67,7 @@ import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; import soot.jimple.infoflow.typing.TypeUtils; import soot.jimple.infoflow.util.SootMethodRepresentationParser; +import soot.jimple.infoflow.util.SootUtils; import soot.jimple.infoflow.util.SystemClassHandler; import soot.jimple.infoflow.values.IValueProvider; import soot.jimple.infoflow.values.SimpleConstantValueProvider; @@ -522,31 +524,27 @@ protected void analyzeMethodForFragmentTransaction(SootClass lifecycleElement, S // first check if there is a Fragment manager, a fragment transaction // and a call to the add method which adds the fragment to the transaction - boolean isFragmentManager = false; - boolean isFragmentTransaction = false; - boolean isAddTransaction = false; + boolean isAddOrReplaceTransaction = false; for (Unit u : method.getActiveBody().getUnits()) { Stmt stmt = (Stmt) u; if (stmt.containsInvokeExpr()) { final String methodName = stmt.getInvokeExpr().getMethod().getName(); - if (methodName.equals("getFragmentManager") || methodName.equals("getSupportFragmentManager")) - isFragmentManager = true; - else if (methodName.equals("beginTransaction")) - isFragmentTransaction = true; - else if (methodName.equals("add") || methodName.equals("replace")) - isAddTransaction = true; + if (methodName.equals("add") || methodName.equals("replace")) + isAddOrReplaceTransaction = true; else if (methodName.equals("inflate") && stmt.getInvokeExpr().getArgCount() > 1) { Value arg = stmt.getInvokeExpr().getArg(0); - Integer fragmentID = valueProvider.getValue(method, stmt, arg, Integer.class); - if (fragmentID != null) - fragmentIDs.put(lifecycleElement, fragmentID); + Set fragmentID = valueProvider.getValue(method, stmt, arg, Integer.class); + if (fragmentID != null) { + for (int f : fragmentID) + fragmentIDs.put(lifecycleElement, f); + } } } } // now get the fragment class from the second argument of the add method // from the transaction - if (isFragmentManager && isFragmentTransaction && isAddTransaction) + if (isAddOrReplaceTransaction) for (Unit u : method.getActiveBody().getUnits()) { Stmt stmt = (Stmt) u; if (stmt.containsInvokeExpr()) { @@ -556,34 +554,56 @@ else if (methodName.equals("inflate") && stmt.getInvokeExpr().getArgCount() > 1) // Make sure that we referring to the correct class and // method - isFragmentTransaction = scFragmentTransaction != null && Scene.v().getFastHierarchy() + boolean isFragmentTransaction = scFragmentTransaction != null && Scene.v().getFastHierarchy() .canStoreType(iinvExpr.getBase().getType(), scFragmentTransaction.getType()); isFragmentTransaction |= scSupportFragmentTransaction != null && Scene.v().getFastHierarchy() .canStoreType(iinvExpr.getBase().getType(), scSupportFragmentTransaction.getType()); isFragmentTransaction |= scAndroidXFragmentTransaction != null && Scene.v().getFastHierarchy() .canStoreType(iinvExpr.getBase().getType(), scAndroidXFragmentTransaction.getType()); - isAddTransaction = stmt.getInvokeExpr().getMethod().getName().equals("add") + isAddOrReplaceTransaction = stmt.getInvokeExpr().getMethod().getName().equals("add") || stmt.getInvokeExpr().getMethod().getName().equals("replace"); - if (isFragmentTransaction && isAddTransaction) { + if (isFragmentTransaction && isAddOrReplaceTransaction) { // We take all fragments passed to the method for (int i = 0; i < stmt.getInvokeExpr().getArgCount(); i++) { Value br = stmt.getInvokeExpr().getArg(i); - // Is this a fragment? - if (br.getType() instanceof RefType) { - RefType rt = (RefType) br.getType(); - if (br instanceof ClassConstant) - rt = (RefType) ((ClassConstant) br).toSootType(); - - boolean addFragment = scFragment != null - && Scene.v().getFastHierarchy().canStoreType(rt, scFragment.getType()); - addFragment |= scSupportFragment != null && Scene.v().getFastHierarchy() - .canStoreType(rt, scSupportFragment.getType()); - addFragment |= scAndroidXFragment != null && Scene.v().getFastHierarchy() - .canStoreType(rt, scAndroidXFragment.getType()); - if (addFragment) - checkAndAddFragment(method.getDeclaringClass(), rt.getSootClass()); + Type pt = stmt.getInvokeExpr().getMethodRef().getParameterType(i); + if (pt instanceof RefType) { + RefType rpt = (RefType) pt; + //skip tag parameter + if (rpt.getClassName().equals("java.lang.String")) + continue; + Set possibleTypes = Collections.emptySet(); + if (((RefType) pt).getSootClass().getName().equals("java.lang.Class")) { + Set ct = valueProvider.getValue(method, stmt, br, + ClassConstant.class); + if (ct != null) { + possibleTypes = new HashSet<>(); + for (ClassConstant p : ct) { + possibleTypes.add((RefType) (p.toSootType())); + } + } + + } else { + possibleTypes = valueProvider.getType(method, stmt, br); + } + + for (Type t : possibleTypes) { + if (t instanceof RefType) { + RefType frt = (RefType) t; + // Is this a fragment? + boolean addFragment = scFragment != null && Scene.v().getFastHierarchy() + .canStoreType(frt, scFragment.getType()); + addFragment |= scSupportFragment != null && Scene.v().getFastHierarchy() + .canStoreType(frt, scSupportFragment.getType()); + addFragment |= scAndroidXFragment != null && Scene.v().getFastHierarchy() + .canStoreType(frt, scAndroidXFragment.getType()); + if (addFragment) + checkAndAddFragment(method.getDeclaringClass(), frt.getSootClass()); + } + } + } } } @@ -845,7 +865,7 @@ protected void analyzeMethodOverrideCallbacks(SootClass sootClass) { for (SootClass parentClass : Scene.v().getActiveHierarchy().getSuperclassesOf(sootClass)) { if (SystemClassHandler.v().isClassInSystemPackage(parentClass)) for (SootMethod sm : parentClass.getMethods()) - if (!sm.isConstructor()) + if (!sm.isConstructor() && (sm.isProtected() || sm.isPublic()) && !sm.isStatic()) systemMethods.put(sm.getSubSignature(), sm); } @@ -869,16 +889,6 @@ protected void analyzeMethodOverrideCallbacks(SootClass sootClass) { } } - private SootMethod getMethodFromHierarchyEx(SootClass c, String methodSignature) { - SootMethod m = c.getMethodUnsafe(methodSignature); - if (m != null) - return m; - SootClass superClass = c.getSuperclassUnsafe(); - if (superClass != null) - return getMethodFromHierarchyEx(superClass, methodSignature); - return null; - } - protected void analyzeClassInterfaceCallbacks(SootClass baseClass, SootClass sootClass, SootClass lifecycleElement) { // We cannot create instances of abstract classes anyway, so there is no @@ -926,7 +936,7 @@ private void checkAndAddCallback(SootClass sc, SootClass baseClass, SootClass li if (androidCallbacks.contains(sc.getName())) { CallbackType callbackType = isUICallback(sc) ? CallbackType.Widget : CallbackType.Default; for (SootMethod sm : sc.getMethods()) { - SootMethod callbackImplementation = getMethodFromHierarchyEx(baseClass, sm.getSubSignature()); + SootMethod callbackImplementation = SootUtils.findMethod(baseClass, sm.getSubSignature()); if (callbackImplementation != null) checkAndAddMethod(callbackImplementation, sm, lifecycleElement, callbackType); } @@ -966,7 +976,8 @@ protected boolean checkAndAddMethod(SootMethod method, SootMethod parentMethod, return false; // Skip empty methods - if (method.isConcrete() && isEmpty(method.retrieveActiveBody())) + if (config.getCallbackConfig().getExcludeStubsFromCallGraph() && method.isConcrete() + && isEmpty(method.retrieveActiveBody())) return false; // Skip constructors diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/ComponentReachableMethods.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/ComponentReachableMethods.java index 704ff7f27..2e3483ff7 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/ComponentReachableMethods.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/ComponentReachableMethods.java @@ -17,7 +17,6 @@ import soot.jimple.toolkits.callgraph.Edge; import soot.jimple.toolkits.callgraph.EdgePredicate; import soot.jimple.toolkits.callgraph.Filter; -import soot.jimple.toolkits.callgraph.ReachableMethods; import soot.jimple.toolkits.callgraph.Targets; import soot.util.queue.ChunkedQueue; import soot.util.queue.QueueReader; diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/DefaultCallbackAnalyzer.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/DefaultCallbackAnalyzer.java index ad5ac3f05..f243349f6 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/DefaultCallbackAnalyzer.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/DefaultCallbackAnalyzer.java @@ -270,17 +270,19 @@ private void findClassLayoutMappings() { // the // fragments for (Value val : inv.getArgs()) { - Integer intValue = valueProvider.getValue(sm, stmt, val, Integer.class); - if (intValue != null) { - this.layoutClasses.put(sm.getDeclaringClass(), intValue); + Set intValues = valueProvider.getValue(sm, stmt, val, Integer.class); + if (intValues != null) { + for (int iv : intValues) + this.layoutClasses.put(sm.getDeclaringClass(), iv); } } } if (invokesInflate(inv)) { - Integer intValue = valueProvider.getValue(sm, stmt, inv.getArg(0), Integer.class); - if (intValue != null) { - this.layoutClasses.put(sm.getDeclaringClass(), intValue); + Set intValues = valueProvider.getValue(sm, stmt, inv.getArg(0), Integer.class); + if (intValues != null) { + for (int iv : intValues) + this.layoutClasses.put(sm.getDeclaringClass(), iv); } } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/FastCallbackAnalyzer.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/FastCallbackAnalyzer.java index f484b73fb..0a7db40bb 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/FastCallbackAnalyzer.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/FastCallbackAnalyzer.java @@ -76,9 +76,10 @@ private void findClassLayoutMappings() { InvokeExpr inv = stmt.getInvokeExpr(); if (invokesSetContentView(inv)) { for (Value val : inv.getArgs()) { - Integer intValue = valueProvider.getValue(sm, stmt, val, Integer.class); - if (intValue != null) { - this.layoutClasses.put(sm.getDeclaringClass(), intValue); + Set intValues = valueProvider.getValue(sm, stmt, val, Integer.class); + if (intValues != null) { + for (int iv : intValues) + this.layoutClasses.put(sm.getDeclaringClass(), iv); } } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/filters/ApplicationCallbackFilter.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/filters/ApplicationCallbackFilter.java index 8d18fcb75..5bd2f72d3 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/filters/ApplicationCallbackFilter.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/callbacks/filters/ApplicationCallbackFilter.java @@ -118,7 +118,6 @@ public boolean accepts(SootClass component, SootMethod callback) { return fh.canStoreType(callbackType, this.componentCallbacks); if (AndroidEntryPointConstants.getComponentCallback2Methods().contains(subSig)) return fh.canStoreType(callbackType, this.componentCallbacks2); - return true; } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AbstractAndroidEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AbstractAndroidEntryPointCreator.java index 958095d96..c3dbff40e 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AbstractAndroidEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AbstractAndroidEntryPointCreator.java @@ -5,14 +5,15 @@ import java.util.Set; import soot.Local; +import soot.RefType; import soot.SootClass; import soot.SootMethod; import soot.jimple.Jimple; import soot.jimple.NopStmt; import soot.jimple.Stmt; import soot.jimple.infoflow.android.manifest.IManifestHandler; -import soot.jimple.infoflow.cfg.FlowDroidEssentialMethodTag; import soot.jimple.infoflow.entryPointCreators.BaseEntryPointCreator; +import soot.jimple.infoflow.util.SootUtils; import soot.jimple.infoflow.util.SystemClassHandler; public abstract class AbstractAndroidEntryPointCreator extends BaseEntryPointCreator { @@ -33,27 +34,31 @@ public SootMethod createDummyMain() { return super.createDummyMain(); } - protected Stmt searchAndBuildMethod(String subsignature, SootClass currentClass, Local classLocal) { - return searchAndBuildMethod(subsignature, currentClass, classLocal, Collections.emptySet()); + protected Stmt searchAndBuildMethod(String subsignature, Local classLocal) { + return searchAndBuildMethod(subsignature, classLocal, Collections.emptySet()); } - protected Stmt searchAndBuildMethod(String subsignature, SootClass currentClass, Local classLocal, - Set parentClasses) { - if (currentClass == null || classLocal == null) + protected Stmt searchAndBuildMethod(String subsignature, Local classLocal, Set parentClasses) { + if (classLocal == null) return null; + SootClass currentClass = ((RefType) classLocal.getType()).getSootClass(); - SootMethod method = findMethod(currentClass, subsignature); + SootMethod method = SootUtils.findMethod(currentClass, subsignature); if (method == null) return null; // If the method is in one of the predefined Android classes, it cannot - // contain custom code, so we do not need to call it - if (AndroidEntryPointConstants.isLifecycleClass(method.getDeclaringClass().getName())) + // contain custom code, so we do not need to call it (unless directly requested) + if (AndroidEntryPointConstants.isLifecycleClass(method.getDeclaringClass().getName()) + && currentClass != method.getDeclaringClass()) return null; // If this method is part of the Android framework, we don't need to - // call it - if (SystemClassHandler.v().isClassInSystemPackage(method.getDeclaringClass())) + // call it, unless it was explicitly requested. Due to virtual method + // invocations + // application code could be called! + if (SystemClassHandler.v().isClassInSystemPackage(method.getDeclaringClass()) + && currentClass.isApplicationClass()) return null; assert method.isStatic() || classLocal != null diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointConstants.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointConstants.java index ad10c425a..96d42026b 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointConstants.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointConstants.java @@ -107,6 +107,13 @@ public class AndroidEntryPointConstants { public static final String SERVICECONNECTION_ONSERVICECONNECTED = "void onServiceConnected(android.content.ComponentName,android.os.IBinder)"; public static final String SERVICECONNECTION_ONSERVICEDISCONNECTED = "void onServiceDisconnected(android.content.ComponentName)"; + public static final String APPCOMPONENTFACTORYCLASS = "android.app.AppComponentFactory"; + public static final String APPCOMPONENTFACTORY_INSTANTIATEACTIVITY = "android.app.Activity instantiateActivity(java.lang.ClassLoader,java.lang.String,android.content.Intent)"; + public static final String APPCOMPONENTFACTORY_INSTANTIATERECEIVER = "android.content.BroadcastReceiver instantiateReceiver(java.lang.ClassLoader,java.lang.String,android.content.Intent)"; + public static final String APPCOMPONENTFACTORY_INSTANTIATEAPPLICATION = "android.app.Application instantiateApplication(java.lang.ClassLoader,java.lang.String)"; + public static final String APPCOMPONENTFACTORY_INSTANTIATECLASSLOADER = "java.lang.ClassLoader instantiateClassLoader(java.lang.ClassLoader,android.content.pm.ApplicationInfo)"; + public static final String APPCOMPONENTFACTORY_INSTANTIATEPROVIDER = "android.content.ContentProvider instantiateProvider(java.lang.ClassLoader,java.lang.String)"; + public static final String ACTIVITYLIFECYCLECALLBACKSINTERFACE = "android.app.Application$ActivityLifecycleCallbacks"; public static final String ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSTARTED = "void onActivityStarted(android.app.Activity)"; public static final String ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSTOPPED = "void onActivityStopped(android.app.Activity)"; @@ -172,6 +179,11 @@ public class AndroidEntryPointConstants { ACTIVITYLIFECYCLECALLBACK_ONACTIVITYDESTROYED, ACTIVITYLIFECYCLECALLBACK_ONACTIVITYCREATED }; private static final List activityLifecycleMethodList = Arrays.asList(activityLifecycleMethods); + private static final String[] componentFactoryLifecycleMethods = { APPCOMPONENTFACTORY_INSTANTIATEACTIVITY, + APPCOMPONENTFACTORY_INSTANTIATEAPPLICATION, APPCOMPONENTFACTORY_INSTANTIATECLASSLOADER, + APPCOMPONENTFACTORY_INSTANTIATEPROVIDER, APPCOMPONENTFACTORY_INSTANTIATERECEIVER }; + private static final List componentFactoryMethodList = Arrays.asList(componentFactoryLifecycleMethods); + private static final String[] componentCallbackMethods = { COMPONENTCALLBACKS_ONCONFIGURATIONCHANGED, COMPONENTCALLBACKS_ONLOWMEMORY }; private static final List componentCallbackMethodList = Arrays.asList(componentCallbackMethods); @@ -182,6 +194,8 @@ public class AndroidEntryPointConstants { private static final String[] serviceConnectionMethods = { SERVICECONNECTION_ONSERVICECONNECTED, SERVICECONNECTION_ONSERVICEDISCONNECTED }; private static final List serviceConnectionMethodList = Arrays.asList(serviceConnectionMethods); + public static final String ATTACH_BASE_CONTEXT = "void attachBaseContext(android.content.Context)"; + public static final String CONTEXT_WRAPPER = "android.content.ContextWrapper"; /* * ======================================================================== */ @@ -226,6 +240,10 @@ public static List getActivityLifecycleCallbackMethods() { return activityLifecycleMethodList; } + public static List getComponentFactoryCallbackMethods() { + return componentFactoryMethodList; + } + public static List getComponentCallbackMethods() { return componentCallbackMethodList; } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointCreator.java index 21564f71d..4db8cae17 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointCreator.java @@ -10,7 +10,9 @@ ******************************************************************************/ package soot.jimple.infoflow.android.entryPointCreators; +import java.util.AbstractCollection; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -24,6 +26,7 @@ import org.slf4j.LoggerFactory; import soot.Body; +import soot.Hierarchy; import soot.Local; import soot.Modifier; import soot.RefType; @@ -31,17 +34,22 @@ import soot.SootClass; import soot.SootField; import soot.SootMethod; +import soot.SootMethodRef; import soot.Type; import soot.Unit; import soot.UnitPatchingChain; import soot.Value; +import soot.VoidType; import soot.jimple.AssignStmt; +import soot.jimple.ClassConstant; import soot.jimple.IfStmt; import soot.jimple.InvokeStmt; import soot.jimple.Jimple; +import soot.jimple.JimpleBody; import soot.jimple.NopStmt; import soot.jimple.NullConstant; import soot.jimple.Stmt; +import soot.jimple.StringConstant; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointUtils.ComponentType; import soot.jimple.infoflow.android.entryPointCreators.components.AbstractComponentEntryPointCreator; import soot.jimple.infoflow.android.entryPointCreators.components.ActivityEntryPointCreator; @@ -59,9 +67,12 @@ import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; import soot.jimple.infoflow.typing.TypeUtils; import soot.jimple.infoflow.util.SootMethodRepresentationParser; +import soot.jimple.infoflow.util.SootUtils; import soot.jimple.infoflow.util.SystemClassHandler; import soot.jimple.toolkits.scalar.NopEliminator; import soot.options.Options; +import soot.tagkit.ExpectedTypeTag; +import soot.toDex.SootToDexUtils; import soot.util.HashMultiMap; import soot.util.MultiMap; @@ -86,7 +97,7 @@ public class AndroidEntryPointCreator extends AbstractAndroidEntryPointCreator i private static final boolean DEBUG = false; - protected MultiMap callbackFunctions = new HashMultiMap<>();; + protected MultiMap callbackFunctions = new HashMultiMap<>(); private SootClass applicationClass = null; private Local applicationLocal = null; @@ -102,6 +113,26 @@ public class AndroidEntryPointCreator extends AbstractAndroidEntryPointCreator i private MultiMap javascriptInterfaceStmts; + private SootClass applicationComponentFactoryClass; + + private SootField classLoaderField; + + private SootField instantiatorField; + + // Contains *all* potential component classes, irregular of whether + // they are defined in the manifest or not. Note that the app component + // factory might create other classes than those listed in the manifest, which + // makes + // everything quite complex. + // In other words, this set contains *all* possible components + private Set allComponentClasses = new HashSet<>(); + + private static final String DEFAULT_COMPONENTDATAEXCHANGENAME = "ComponentDataExchangeInterface"; + + private SootClass componentDataExchangeInterface; + + private AbstractCollection additionalMethods; + /** * Creates a new instance of the {@link AndroidEntryPointCreator} class and * registers a list of classes to be automatically scanned for Android lifecycle @@ -114,6 +145,67 @@ public AndroidEntryPointCreator(IManifestHandler manifest, Collection super(manifest); this.components = components; this.overwriteDummyMainMethod = true; + Hierarchy h = Scene.v().getActiveHierarchy(); + for (String clzName : new String[] { AndroidEntryPointConstants.ACTIVITYCLASS, + AndroidEntryPointConstants.BROADCASTRECEIVERCLASS, AndroidEntryPointConstants.CONTENTPROVIDERCLASS, + AndroidEntryPointConstants.SERVICECLASS }) { + SootClass sc = Scene.v().getSootClassUnsafe(clzName, false); + if (sc != null && !sc.isPhantom()) { + allComponentClasses.addAll(h.getSubclassesOf(sc)); + } + } + + ComponentExchangeInfo info = generateComponentDataExchangeInterface(); + initializeComponentDataTransferMethods(info); + + } + + private ComponentExchangeInfo generateComponentDataExchangeInterface() { + SootClass s = getOrCreateClass(DEFAULT_COMPONENTDATAEXCHANGENAME); + s.setModifiers(Modifier.PUBLIC | Modifier.INTERFACE); + componentDataExchangeInterface = s; + + RefType intent = RefType.v("android.content.Intent"); + Scene sc = Scene.v(); + String getResultIntentName = findUniqueMethodName("getResultIntent", allComponentClasses); + String setResultIntentName = findUniqueMethodName("setResultIntent", allComponentClasses); + // just choose a different name other than "getIntent" + String getIntentName = findUniqueMethodName("getDataIntent", allComponentClasses); + String setIntentName = findUniqueMethodName("setDataIntent", allComponentClasses); + + SootMethod getResultIntentMethod = sc.makeSootMethod(getResultIntentName, Collections.emptyList(), intent, + Modifier.PUBLIC | Modifier.ABSTRACT); + componentDataExchangeInterface.addMethod(getResultIntentMethod); + SootMethod getIntentMethod = sc.makeSootMethod(getIntentName, Collections.emptyList(), intent, + Modifier.PUBLIC | Modifier.ABSTRACT); + componentDataExchangeInterface.addMethod(getIntentMethod); + SootMethod setIntentMethod = sc.makeSootMethod(setIntentName, Arrays.asList(intent), VoidType.v(), + Modifier.PUBLIC | Modifier.ABSTRACT); + componentDataExchangeInterface.addMethod(setIntentMethod); + SootMethod setResultIntentMethod = sc.makeSootMethod(setResultIntentName, Arrays.asList(intent), VoidType.v(), + Modifier.PUBLIC | Modifier.ABSTRACT); + componentDataExchangeInterface.addMethod(setResultIntentMethod); + + ComponentExchangeInfo info = new ComponentExchangeInfo(componentDataExchangeInterface, getIntentMethod, + setIntentMethod, getResultIntentMethod, setResultIntentMethod); + componentToInfo.setComponentExchangeInfo(info); + return info; + + } + + private String findUniqueMethodName(String name, Collection classes) { + String tryName = name; + int i = 1; + nextTry: while (true) { + for (SootClass clz : classes) { + if (clz.getMethodByNameUnsafe(tryName) != null) { + i++; + tryName = name + i; + continue nextTry; + } + } + return tryName; + } } @Override @@ -121,12 +213,89 @@ protected SootMethod createDummyMainInternal() { // Make sure that we don't have any leftover state // from previous runs reset(); + additionalMethods = new HashSet<>(); logger.info(String.format("Creating Android entry point for %d components...", components.size())); + // If the application tag in the manifest specifies a appComponentFactory, it + // needs to be called first + initializeApplComponentFactory(); + + // If we have an implementation of android.app.Application, this needs + // special treatment + initializeApplicationClass(); + Jimple j = Jimple.v(); + + // due to app component factories, that could be another application class! + // The application class gets instantiated first. Note that although it's + // created now, it's onCreate method gets called *after* + // all content providers. + SootClass applicationClassUse = Scene.v().getSootClass(AndroidEntryPointConstants.APPLICATIONCLASS); + boolean generateApplicationCode = applicationClass != null || applicationComponentFactoryClass != null; + if (generateApplicationCode) { + if (applicationComponentFactoryClass != null) { + Local factory = generateClassConstructor(applicationComponentFactoryClass); + SootMethodRef mrInstantiate = Scene.v().makeMethodRef( + Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.APPCOMPONENTFACTORYCLASS), + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEAPPLICATION, false); + SootMethodRef mrInstantiateClassLoader = Scene.v().makeMethodRef( + Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.APPCOMPONENTFACTORYCLASS), + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATECLASSLOADER, false); + SootMethodRef mrGetClassLoader = Scene.v().makeMethodRef(Scene.v().getSootClass("java.lang.Class"), + "java.lang.ClassLoader getClassLoader()", false); + applicationLocal = j.newLocal("application", RefType.v(AndroidEntryPointConstants.APPLICATIONCLASS)); + body.getLocals().add(applicationLocal); + String classAppl = "android.app.Application"; + if (applicationClass != null) + classAppl = applicationClass.getName(); + applicationClass = Scene.v().forceResolve(classAppl, SootClass.SIGNATURES); + Local clazzL = j.newLocal("clazz", RefType.v("java.lang.Class")); + body.getLocals().add(clazzL); + Local classLoader = j.newLocal("classLoader", RefType.v("java.lang.ClassLoader")); + body.getLocals().add(classLoader); + + body.getUnits() + .add(j.newAssignStmt(clazzL, ClassConstant.v(SootToDexUtils.getDexClassName(dummyClassName)))); + body.getUnits().add(j.newAssignStmt(classLoader, j.newVirtualInvokeExpr(clazzL, mrGetClassLoader))); + + AssignStmt instantiateCL = j.newAssignStmt(classLoader, j.newVirtualInvokeExpr(factory, + mrInstantiateClassLoader, Arrays.asList(classLoader, createApplicationInfo()))); + body.getUnits().add(instantiateCL); + + if (classLoaderField == null) + classLoaderField = createField(RefType.v("java.lang.ClassLoader"), "cl"); + if (instantiatorField == null) + instantiatorField = createField(RefType.v("android.app.AppComponentFactory"), "cl"); + + AssignStmt instantiate = j.newAssignStmt(applicationLocal, j.newVirtualInvokeExpr(factory, + mrInstantiate, Arrays.asList(classLoader, StringConstant.v(classAppl)))); + instantiate.addTag(new ExpectedTypeTag(applicationClass.getType())); + body.getUnits().add(instantiate); + body.getUnits().add(j.newAssignStmt(j.newStaticFieldRef(classLoaderField.makeRef()), classLoader)); + body.getUnits().add(j.newAssignStmt(j.newStaticFieldRef(instantiatorField.makeRef()), factory)); + } else { + // Create the application + applicationLocal = generateClassConstructor(applicationClass); + // we know for sure that there is no other application class in question + applicationClassUse = applicationClass; + } + localVarsForClasses.put(applicationClass, applicationLocal); + SootClass cw = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.CONTEXT_WRAPPER); + if (cw == null) { + //use application local type as a fallback, since this class also implements context wrapper + cw = ((RefType) applicationLocal.getType()).getSootClass(); + } + if (cw != null) + body.getUnits().add(j.newInvokeStmt(j.newVirtualInvokeExpr(applicationLocal, + Scene.v().makeMethodRef(cw, AndroidEntryPointConstants.ATTACH_BASE_CONTEXT, false)))); + } + + Map cpComponents = new HashMap<>(); // For some weird reason unknown to anyone except the flying spaghetti // monster, the onCreate() methods of content providers run even before - // the application object's onCreate() is called. + // the application object's onCreate() is called (but after the creation of the + // application). + // See https://issuetracker.google.com/issues/36917845#comment4 { boolean hasContentProviders = false; NopStmt beforeContentProvidersStmt = Jimple.v().newNopStmt(); @@ -134,16 +303,15 @@ protected SootMethod createDummyMainInternal() { for (SootClass currentClass : components) { if (entryPointUtils.getComponentType(currentClass) == ComponentType.ContentProvider) { // Create an instance of the content provider - Local localVal = generateClassConstructor(currentClass); - if (localVal == null) - continue; - localVarsForClasses.put(currentClass, localVal); + ContentProviderEntryPointCreator cpc = new ContentProviderEntryPointCreator(currentClass, + applicationClassUse, this.manifest, instantiatorField, classLoaderField, + componentToInfo.getComponentExchangeInfo()); + SootMethod m = cpc.createInit(); + Local cpLocal = generator.generateLocal(RefType.v(AndroidEntryPointConstants.CONTENTPROVIDERCLASS)); + body.getUnits().add(Jimple.v().newAssignStmt(cpLocal, Jimple.v().newStaticInvokeExpr(m.makeRef()))); + localVarsForClasses.put(currentClass, cpLocal); + cpComponents.put(currentClass, cpc); - // Conditionally call the onCreate method - NopStmt thenStmt = Jimple.v().newNopStmt(); - createIfStmt(thenStmt); - searchAndBuildMethod(AndroidEntryPointConstants.CONTENTPROVIDER_ONCREATE, currentClass, localVal); - body.getUnits().add(thenStmt); hasContentProviders = true; } } @@ -153,18 +321,9 @@ protected SootMethod createDummyMainInternal() { createIfStmt(beforeContentProvidersStmt); } - // If we have an implementation of android.app.Application, this needs - // special treatment - initializeApplicationClass(); - // If we have an application, we need to start it in the very beginning - if (applicationClass != null) { - // Create the application - applicationLocal = generateClassConstructor(applicationClass); - localVarsForClasses.put(applicationClass, applicationLocal); + if (generateApplicationCode) { if (applicationLocal != null) { - localVarsForClasses.put(applicationClass, applicationLocal); - boolean hasApplicationCallbacks = applicationCallbackClasses != null && !applicationCallbackClasses.isEmpty(); boolean hasActivityLifecycleCallbacks = activityLifecycleCallbacks != null @@ -196,8 +355,7 @@ protected SootMethod createDummyMainInternal() { } // Call the onCreate() method - searchAndBuildMethod(AndroidEntryPointConstants.APPLICATION_ONCREATE, applicationClass, - applicationLocal); + searchAndBuildMethod(AndroidEntryPointConstants.APPLICATION_ONCREATE, applicationLocal); ////////////// // Initializes the ApplicationHolder static field with the @@ -226,7 +384,7 @@ protected SootMethod createDummyMainInternal() { Set fragments = fragmentClasses.get(parentActivity); for (SootClass fragment : fragments) { FragmentEntryPointCreator entryPointCreator = new FragmentEntryPointCreator(fragment, applicationClass, - this.manifest); + this.manifest, instantiatorField, classLoaderField, componentToInfo.getComponentExchangeInfo()); entryPointCreator.setDummyClassName(mainMethod.getDeclaringClass().getName()); entryPointCreator.setCallbacks(callbackFunctions.get(fragment)); @@ -250,6 +408,7 @@ protected SootMethod createDummyMainInternal() { // Generate the lifecycles for the different kinds of Android // classes AbstractComponentEntryPointCreator componentCreator = null; + List params = Collections.singletonList(NullConstant.v()); switch (componentType) { case Activity: Map curActivityToFragmentMethod = new HashMap<>(); @@ -260,25 +419,29 @@ protected SootMethod createDummyMainInternal() { curActivityToFragmentMethod.put(fragment, fragmentToMainMethod.get(fragment)); } } - componentCreator = new ActivityEntryPointCreator(currentClass, applicationClass, - activityLifecycleCallbacks, callbackClassToField, curActivityToFragmentMethod, this.manifest); + componentCreator = new ActivityEntryPointCreator(currentClass, applicationClassUse, + activityLifecycleCallbacks, callbackClassToField, curActivityToFragmentMethod, this.manifest, + instantiatorField, classLoaderField, componentToInfo.getComponentExchangeInfo()); break; case Service: case GCMBaseIntentService: case GCMListenerService: case HostApduService: - componentCreator = new ServiceEntryPointCreator(currentClass, applicationClass, this.manifest); + componentCreator = new ServiceEntryPointCreator(currentClass, applicationClassUse, this.manifest, + instantiatorField, classLoaderField, componentToInfo.getComponentExchangeInfo()); break; case ServiceConnection: - componentCreator = new ServiceConnectionEntryPointCreator(currentClass, applicationClass, - this.manifest); + componentCreator = new ServiceConnectionEntryPointCreator(currentClass, applicationClassUse, + this.manifest, instantiatorField, classLoaderField, componentToInfo.getComponentExchangeInfo()); break; case BroadcastReceiver: - componentCreator = new BroadcastReceiverEntryPointCreator(currentClass, applicationClass, - this.manifest); + componentCreator = new BroadcastReceiverEntryPointCreator(currentClass, applicationClassUse, + this.manifest, instantiatorField, classLoaderField, componentToInfo.getComponentExchangeInfo()); break; case ContentProvider: - componentCreator = new ContentProviderEntryPointCreator(currentClass, applicationClass, this.manifest); + componentCreator = cpComponents.get(currentClass); + //We need to pass on the content provider instance + params = Arrays.asList(NullConstant.v(), localVarsForClasses.get(currentClass)); break; default: componentCreator = null; @@ -295,11 +458,11 @@ protected SootMethod createDummyMainInternal() { SootMethod lifecycleMethod = componentCreator.createDummyMain(); componentToInfo.put(currentClass, componentCreator.getComponentInfo()); + additionalMethods.addAll(componentCreator.getAdditionalMethods()); // dummyMain(component, intent) if (shouldAddLifecycleCall(currentClass)) { - body.getUnits() - .add(Jimple.v().newInvokeStmt(Jimple.v().newStaticInvokeExpr(lifecycleMethod.makeRef(), - Collections.singletonList(NullConstant.v())))); + body.getUnits().add(Jimple.v() + .newInvokeStmt(Jimple.v().newStaticInvokeExpr(lifecycleMethod.makeRef(), params))); } } @@ -321,8 +484,7 @@ protected SootMethod createDummyMainInternal() { // Add a call to application.onTerminate() if (applicationLocal != null) - searchAndBuildMethod(AndroidEntryPointConstants.APPLICATION_ONTERMINATE, applicationClass, - applicationLocal); + searchAndBuildMethod(AndroidEntryPointConstants.APPLICATION_ONTERMINATE, applicationLocal); body.getUnits().add(Jimple.v().newReturnVoidStmt()); @@ -337,6 +499,149 @@ protected SootMethod createDummyMainInternal() { return mainMethod; } + /** + * Initializes the methods intended for transferring data (usually intents) between components. + * @param info contains information about the commonly used method names for the interface methods + */ + private void initializeComponentDataTransferMethods(ComponentExchangeInfo info) { + + for (SootClass s : allComponentClasses) { + + s.addInterface(componentDataExchangeInterface); + Scene sc = Scene.v(); + Jimple j = Jimple.v(); + + // Create a name for a field for the result intent of this component + String fieldName = "ipcResultIntent"; + int fieldIdx = 0; + while (s.declaresFieldByName(fieldName)) + fieldName = "ipcResultIntent_" + fieldIdx++; + + // Create the field itself + SootField resultIntentField = Scene.v().makeSootField(fieldName, RefType.v("android.content.Intent"), + Modifier.PUBLIC); + resultIntentField.addTag(SimulatedCodeElementTag.TAG); + s.addField(resultIntentField); + SootMethod getResultIntentMethod = sc.makeSootMethod(info.getResultIntentMethod.getName(), + info.getResultIntentMethod.getParameterTypes(), info.getResultIntentMethod.getReturnType(), + Modifier.PUBLIC); + getResultIntentMethod.addTag(SimulatedCodeElementTag.TAG); + JimpleBody jb = j.newBody(getResultIntentMethod); + getResultIntentMethod.setActiveBody(jb); + s.addMethod(getResultIntentMethod); + + jb.insertIdentityStmts(); + Local lcl = j.newLocal("ret", getResultIntentMethod.getReturnType()); + jb.getLocals().add(lcl); + jb.getUnits() + .add(j.newAssignStmt(lcl, j.newInstanceFieldRef(jb.getThisLocal(), resultIntentField.makeRef()))); + jb.getUnits().add(j.newReturnStmt(lcl)); + + // Create a name for a field for the intent with which the component is started + fieldName = "ipcIntent"; + fieldIdx = 0; + while (s.declaresFieldByName(fieldName)) + fieldName = "ipcIntent_" + fieldIdx++; + + // Create the field itself + SootField intentField = Scene.v().makeSootField(fieldName, RefType.v("android.content.Intent"), + Modifier.PUBLIC); + intentField.addTag(SimulatedCodeElementTag.TAG); + s.addField(intentField); + + SootMethod setResultIntentMethod = sc.makeSootMethod(info.setResultIntentMethod.getName(), + info.setResultIntentMethod.getParameterTypes(), info.setResultIntentMethod.getReturnType(), + Modifier.PUBLIC); + + jb = j.newBody(setResultIntentMethod); + setResultIntentMethod.setActiveBody(jb); + s.addMethod(setResultIntentMethod); + setResultIntentMethod.addTag(SimulatedCodeElementTag.TAG); + jb.insertIdentityStmts(); + jb.getUnits().add(j.newAssignStmt(j.newInstanceFieldRef(jb.getThisLocal(), resultIntentField.makeRef()), + jb.getParameterLocal(0))); + jb.getUnits().add(j.newReturnVoidStmt()); + SootMethod getIntentMethod = sc.makeSootMethod(info.getIntentMethod.getName(), + info.getIntentMethod.getParameterTypes(), info.getIntentMethod.getReturnType(), Modifier.PUBLIC); + jb = j.newBody(getIntentMethod); + getIntentMethod.addTag(SimulatedCodeElementTag.TAG); + getIntentMethod.setActiveBody(jb); + s.addMethod(getIntentMethod); + jb.insertIdentityStmts(); + lcl = j.newLocal("retValue", getIntentMethod.getReturnType()); + jb.getLocals().add(lcl); + jb.getUnits().add(j.newAssignStmt(lcl, j.newInstanceFieldRef(jb.getThisLocal(), intentField.makeRef()))); + jb.getUnits().add(j.newReturnStmt(lcl)); + + SootMethod setIntentMethod = sc.makeSootMethod(info.setIntentMethod.getName(), + info.setIntentMethod.getParameterTypes(), info.setIntentMethod.getReturnType(), Modifier.PUBLIC); + jb = j.newBody(setIntentMethod); + setIntentMethod.setActiveBody(jb); + s.addMethod(setIntentMethod); + setIntentMethod.addTag(SimulatedCodeElementTag.TAG); + jb.insertIdentityStmts(); + jb.getUnits().add(j.newAssignStmt(j.newInstanceFieldRef(jb.getThisLocal(), intentField.makeRef()), + jb.getParameterLocal(0))); + jb.getUnits().add(j.newReturnVoidStmt()); + + } + } + + private Value createApplicationInfo() { + SootClass p = Scene.v().getSootClassUnsafe("android.content.pm.ApplicationInfo"); + if (p != null) { + return generateClassConstructor(p); + } else { + return NullConstant.v(); + } + } + + private void initializeApplComponentFactory() { + + IAndroidApplication app = manifest.getApplication(); + if (app != null) { + String componentFactoryName = app.getAppComponentFactory(); + // We can only look for callbacks if we have an application class + if (componentFactoryName == null || componentFactoryName.isEmpty()) + return; + + // Find the application class + for (SootClass currentClass : components) { + // Is this the application class? + if (entryPointUtils.isComponentFactoryClass(currentClass) + && currentClass.getName().equals(componentFactoryName)) { + applicationComponentFactoryClass = currentClass; + break; + } + } + } + + // We can only look for callbacks if we have an application class + if (applicationClass == null) + return; + + // Look into the application class' callbacks + Collection callbacks = callbackFunctions.get(applicationClass); + if (callbacks != null) { + for (SootMethod smCallback : callbacks) { + if (smCallback != null) { + applicationCallbackClasses.put(smCallback.getDeclaringClass(), smCallback.getSignature()); + } + } + } + + // Create fields for the activity lifecycle classes + for (SootClass callbackClass : activityLifecycleCallbacks.keySet()) { + String baseName = callbackClass.getName(); + if (baseName.contains(".")) + baseName = baseName.substring(baseName.lastIndexOf(".") + 1); + + // Generate a fresh field name + SootField fld = createField(RefType.v(callbackClass), baseName); + callbackClassToField.put(callbackClass, fld); + } + } + private void createJavascriptCallbacks() { Jimple j = Jimple.v(); for (SootMethod m : javascriptInterfaceStmts.keySet()) { @@ -350,7 +655,7 @@ private void createJavascriptCallbacks() { f = mainMethod.getDeclaringClass().getFieldByNameUnsafe(dm.getFieldName()); } if (f == null) { - //create field + // create field f = createField(arg.getType(), "jsInterface"); AssignStmt assign = j.newAssignStmt(j.newStaticFieldRef(f.makeRef()), arg); assign.addTag(SimulatedCodeElementTag.TAG); @@ -515,7 +820,7 @@ private void addApplicationCallbackMethods() { for (String methodSig : applicationCallbackClasses.get(sc)) { SootMethodAndClass methodAndClass = SootMethodRepresentationParser.v().parseSootMethodString(methodSig); String subSig = methodAndClass.getSubSignature(); - SootMethod method = findMethod(Scene.v().getSootClass(sc.getName()), subSig); + SootMethod method = SootUtils.findMethod(Scene.v().getSootClass(sc.getName()), subSig); // We do not consider lifecycle methods which are directly // inserted at their respective positions @@ -567,7 +872,10 @@ public void setFragments(MultiMap fragments) { @Override public Collection getAdditionalMethods() { - return componentToInfo.getLifecycleMethods(); + List r = new ArrayList<>(componentToInfo.getLifecycleMethods()); + if (additionalMethods != null) + r.addAll(additionalMethods); + return r; } @Override diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointUtils.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointUtils.java index 0a68b52dd..8e51584d9 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointUtils.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/AndroidEntryPointUtils.java @@ -16,6 +16,8 @@ import soot.Scene; import soot.SootClass; import soot.SootMethod; +import soot.Type; +import soot.jimple.infoflow.util.SootMethodRepresentationParser; import soot.jimple.infoflow.util.SystemClassHandler; import soot.tagkit.AnnotationTag; import soot.tagkit.Tag; @@ -34,6 +36,7 @@ public class AndroidEntryPointUtils { private Map componentTypeCache = new HashMap<>(); private SootClass osClassApplication; + private SootClass osClassComponentFactory; private SootClass osClassActivity; private SootClass osClassMapActivity; private SootClass osClassService; @@ -64,6 +67,7 @@ public enum ComponentType { public AndroidEntryPointUtils() { // Get some commonly used OS classes osClassApplication = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.APPLICATIONCLASS); + osClassComponentFactory = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.APPCOMPONENTFACTORYCLASS); osClassActivity = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.ACTIVITYCLASS); osClassService = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.SERVICECLASS); osClassFragment = Scene.v().getSootClassUnsafe(AndroidEntryPointConstants.FRAGMENTCLASS); @@ -167,6 +171,18 @@ public boolean isApplicationClass(SootClass clazz) { && Scene.v().getOrMakeFastHierarchy().canStoreType(clazz.getType(), osClassApplication.getType()); } + /** + * Checks whether the given class is derived from android.app.AppComponentFactory + * + * @param clazz The class to check + * @return True if the given class is derived from android.app.AppComponentFactory, + * otherwise false + */ + public boolean isComponentFactoryClass(SootClass clazz) { + return osClassComponentFactory != null + && Scene.v().getOrMakeFastHierarchy().canStoreType(clazz.getType(), osClassComponentFactory.getType()); + } + /** * Checks whether the given method is an Android entry point, i.e., a lifecycle * method @@ -272,15 +288,42 @@ public static Collection getLifecycleMethods(Co */ private static Collection getLifecycleMethods(SootClass sc, List methods) { Set lifecycleMethods = new HashSet<>(); - SootClass currentClass = sc; - while (currentClass != null) { - for (String sig : methods) { - SootMethod sm = currentClass.getMethodUnsafe(sig); - if (sm != null) - if (!SystemClassHandler.v().isClassInSystemPackage(sm.getDeclaringClass())) - lifecycleMethods.add(sm); + SootMethodRepresentationParser parser = SootMethodRepresentationParser.v(); + + Scene scene = Scene.v(); + FastHierarchy fh = Scene.v().getOrMakeFastHierarchy(); + nextMethod: for (String sig : methods) { + SootClass currentClass = sc; + String name = parser.getMethodNameFromSubSignature(sig); + String[] params = parser.getParameterTypesFromSubSignature(sig); + String sreturnType = parser.getReturnTypeFromSubSignature(sig); + + Type returnType = scene.getTypeUnsafe(sreturnType, false); + if (returnType != null) { + while (currentClass != null) { + Collection sms = currentClass.getMethodsByNameAndParamCount(name, + params == null ? 0 : params.length); + for (SootMethod sm : sms) { + if (!fh.canStoreType(sm.getReturnType(), returnType)) { + continue; + } + if (params != null) { + for (int i = 0; i < params.length; i++) { + Type pType = scene.getTypeUnsafe(params[i], false); + if (pType == null) + continue nextMethod; + if (pType != sm.getParameterType(i)) + continue nextMethod; + } + } + if (!SystemClassHandler.v().isClassInSystemPackage(sm.getDeclaringClass())) { + lifecycleMethods.add(sm); + } + continue nextMethod; + } + currentClass = currentClass.getSuperclassUnsafe(); + } } - currentClass = currentClass.hasSuperclass() ? currentClass.getSuperclass() : null; } return lifecycleMethods; } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/ComponentExchangeInfo.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/ComponentExchangeInfo.java new file mode 100644 index 000000000..533b52549 --- /dev/null +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/ComponentExchangeInfo.java @@ -0,0 +1,52 @@ +package soot.jimple.infoflow.android.entryPointCreators; + +import java.util.Objects; + +import soot.SootClass; +import soot.SootMethod; + +/** + * Contains information used for data transfers between arbitrary components. + * These components implement common interface methods for setting and retrieving an intent. + * In the case of activities, there are also methods to save and retrieve an result intent. + */ +public class ComponentExchangeInfo { + + public final SootClass componentDataExchangeInterface; + public final SootMethod getResultIntentMethod; + public final SootMethod setResultIntentMethod; + public final SootMethod getIntentMethod; + public final SootMethod setIntentMethod; + + @Override + public int hashCode() { + return Objects.hash(componentDataExchangeInterface, getIntentMethod, getResultIntentMethod, setIntentMethod, + setResultIntentMethod); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ComponentExchangeInfo other = (ComponentExchangeInfo) obj; + return Objects.equals(componentDataExchangeInterface, other.componentDataExchangeInterface) + && Objects.equals(getIntentMethod, other.getIntentMethod) + && Objects.equals(getResultIntentMethod, other.getResultIntentMethod) + && Objects.equals(setIntentMethod, other.setIntentMethod) + && Objects.equals(setResultIntentMethod, other.setResultIntentMethod); + } + + public ComponentExchangeInfo(SootClass componentDataExchangeInterface, SootMethod getIntentMethod, + SootMethod setIntentMethod, SootMethod getResultIntentMethod, SootMethod setResultIntentMethod) { + this.componentDataExchangeInterface = componentDataExchangeInterface; + this.getIntentMethod = getIntentMethod; + this.setIntentMethod = setIntentMethod; + this.getResultIntentMethod = getResultIntentMethod; + this.setResultIntentMethod = setResultIntentMethod; + } + +} diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/AbstractComponentEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/AbstractComponentEntryPointCreator.java index cf143ec4d..eb86ffdfe 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/AbstractComponentEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/AbstractComponentEntryPointCreator.java @@ -2,6 +2,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -25,6 +26,7 @@ import soot.Unit; import soot.UnitPatchingChain; import soot.Value; +import soot.jimple.AssignStmt; import soot.jimple.IdentityStmt; import soot.jimple.InvokeExpr; import soot.jimple.Jimple; @@ -32,10 +34,13 @@ import soot.jimple.NopStmt; import soot.jimple.NullConstant; import soot.jimple.Stmt; +import soot.jimple.StringConstant; import soot.jimple.infoflow.android.entryPointCreators.AbstractAndroidEntryPointCreator; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; import soot.jimple.toolkits.scalar.NopEliminator; +import soot.tagkit.ExpectedTypeTag; import soot.util.HashMultiMap; import soot.util.MultiMap; @@ -56,16 +61,25 @@ public abstract class AbstractComponentEntryPointCreator extends AbstractAndroid protected Local thisLocal = null; protected Local intentLocal = null; - protected SootField intentField = null; private RefType INTENT_TYPE = RefType.v("android.content.Intent"); + protected final SootField instantiatorField; + protected final SootField classLoaderField; + + protected ComponentExchangeInfo componentExchangeInfo; + public AbstractComponentEntryPointCreator(SootClass component, SootClass applicationClass, - IManifestHandler manifest) { + IManifestHandler manifest, SootField instantiatorField, SootField classLoaderField, + ComponentExchangeInfo componentExchangeInfo) { super(manifest); this.component = component; this.applicationClass = applicationClass; this.overwriteDummyMainMethod = true; + this.instantiatorField = instantiatorField; + this.classLoaderField = classLoaderField; + this.componentExchangeInfo = componentExchangeInfo; + } public void setCallbacks(Set callbacks) { @@ -76,16 +90,6 @@ public void setCallbacks(Set callbacks) { protected void createAdditionalFields() { super.createAdditionalFields(); - // Create a name for a field for the intent with which the component is started - String fieldName = "ipcIntent"; - int fieldIdx = 0; - while (component.declaresFieldByName(fieldName)) - fieldName = "ipcIntent_" + fieldIdx++; - - // Create the field itself - intentField = Scene.v().makeSootField(fieldName, RefType.v("android.content.Intent"), Modifier.PUBLIC); - intentField.addTag(SimulatedCodeElementTag.TAG); - component.addField(intentField); } @Override @@ -118,7 +122,7 @@ protected void createEmptyMainMethod() { List argList = new ArrayList<>(defaultParams); if (additionalParams != null && !additionalParams.isEmpty()) argList.addAll(additionalParams); - mainMethod = Scene.v().makeSootMethod(methodName, argList, component.getType()); + mainMethod = Scene.v().makeSootMethod(methodName, argList, getModelledClass().getType()); // Create the body JimpleBody body = Jimple.v().newBody(); @@ -148,6 +152,8 @@ protected void createEmptyMainMethod() { } } + protected abstract SootClass getModelledClass(); + /** * Gets the default parameter types that every component main method shall have * @@ -187,13 +193,14 @@ protected SootMethod createDummyMainInternal() { createIfStmt(endClassStmt); // Create a new instance of the component - thisLocal = generateClassConstructor(component); + if (thisLocal == null) + thisLocal = generateClassConstructor(component); if (thisLocal != null) { localVarsForClasses.put(component, thisLocal); // Store the intent - body.getUnits().add(Jimple.v() - .newAssignStmt(Jimple.v().newInstanceFieldRef(thisLocal, intentField.makeRef()), intentLocal)); + body.getUnits().add(Jimple.v().newInvokeStmt(Jimple.v().newInterfaceInvokeExpr(thisLocal, + componentExchangeInfo.setIntentMethod.makeRef(), Arrays.asList(intentLocal)))); // Create calls to the lifecycle methods generateComponentLifecycle(); @@ -260,7 +267,7 @@ public void assignIntent(SootClass hostComponent, SootMethod method, int indexOf JimpleBody body = (JimpleBody) method.retrieveActiveBody(); // Some component types such as fragments don't have a getIntent() method - SootMethod m = hostComponent.getMethodUnsafe("android.content.Intent getIntent()"); + SootMethod m = hostComponent.getMethodUnsafe(componentExchangeInfo.getIntentMethod.getSubSignature()); if (m != null) { UnitPatchingChain units = body.getUnits(); Local thisLocal = body.getThisLocal(); @@ -270,7 +277,8 @@ public void assignIntent(SootClass hostComponent, SootMethod method, int indexOf for (Iterator iter = units.snapshotIterator(); iter.hasNext();) { Stmt stmt = (Stmt) iter.next(); if (stmt.getTag(SimulatedCodeElementTag.TAG_NAME) != null) { - if (stmt.containsInvokeExpr() && stmt.getInvokeExpr().getMethod().equals(m)) + if (stmt.containsInvokeExpr() + && stmt.getInvokeExpr().getMethod().equals(componentExchangeInfo.getIntentMethod)) return; } } @@ -286,7 +294,7 @@ public void assignIntent(SootClass hostComponent, SootMethod method, int indexOf * com.google.android.gcm.GCMBroadcastReceiver */ Unit setIntentU = Jimple.v().newAssignStmt(intentV, - Jimple.v().newVirtualInvokeExpr(thisLocal, m.makeRef())); + Jimple.v().newVirtualInvokeExpr(thisLocal, componentExchangeInfo.getIntentMethod.makeRef())); setIntentU.addTag(SimulatedCodeElementTag.TAG); units.insertBefore(setIntentU, stmt); @@ -307,10 +315,7 @@ public Collection getAdditionalMethods() { @Override public Collection getAdditionalFields() { - if (intentField == null) - return Collections.emptySet(); - - return Collections.singleton(intentField); + return Collections.emptySet(); } /** @@ -460,24 +465,13 @@ private void addSingleCallbackMethod(Set referenceClasses, SetcreatorMethodSubset method of the factory class + * @return the local (might be new or reused) + */ + public Local generateInstantiator(SootClass createdClass, String creatorMethodSubset, Value... values) { + + // If we already have a class local of that type, we re-use it + Local existingLocal = localVarsForClasses.get(createdClass); + if (existingLocal != null) + return existingLocal; + + RefType rt = (RefType) instantiatorField.getType(); + SootMethod m = rt.getSootClass().getMethod(creatorMethodSubset); + Local instantiatorLocal = generator.generateLocal(instantiatorField.getType()); + Local classLoaderLocal = generator.generateLocal(classLoaderField.getType()); + + Local varLocal = generator.generateLocal(m.getReturnType()); + Jimple j = Jimple.v(); + body.getUnits().add(j.newAssignStmt(instantiatorLocal, j.newStaticFieldRef(instantiatorField.makeRef()))); + body.getUnits().add(j.newAssignStmt(classLoaderLocal, j.newStaticFieldRef(classLoaderField.makeRef()))); + List params = new ArrayList<>(); + params.add(classLoaderLocal); + params.add(StringConstant.v(createdClass.getName())); + for (Value v : values) { + params.add(v); + } + AssignStmt s = j.newAssignStmt(varLocal, j.newVirtualInvokeExpr(instantiatorLocal, m.makeRef(), params)); + s.addTag(new ExpectedTypeTag(createdClass.getType())); + body.getUnits().add(s); + localVarsForClasses.put(createdClass, varLocal); + return varLocal; + } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointCreator.java index 87669977b..50d45e94f 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointCreator.java @@ -1,6 +1,7 @@ package soot.jimple.infoflow.android.entryPointCreators.components; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -25,6 +26,7 @@ import soot.jimple.Stmt; import soot.jimple.infoflow.android.InfoflowAndroidConfiguration; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; import soot.jimple.infoflow.cfg.LibraryClassPatcher; import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; @@ -42,19 +44,28 @@ public class ActivityEntryPointCreator extends AbstractComponentEntryPointCreato private final Map callbackClassToField; private final Map fragmentToMainMethod; - protected SootField resultIntentField = null; - public ActivityEntryPointCreator(SootClass component, SootClass applicationClass, MultiMap activityLifecycleCallbacks, Map callbackClassToField, - Map fragmentToMainMethod, IManifestHandler manifest) { - super(component, applicationClass, manifest); + Map fragmentToMainMethod, IManifestHandler manifest, SootField instantiatorField, + SootField classLoaderField, ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); this.activityLifecycleCallbacks = activityLifecycleCallbacks; this.callbackClassToField = callbackClassToField; this.fragmentToMainMethod = fragmentToMainMethod; } + @Override + protected Local generateClassConstructor(SootClass createdClass) { + if (createdClass == component && instantiatorField != null) { + return super.generateInstantiator(createdClass, + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEACTIVITY, body.getParameterLocal(0)); + } + return super.generateClassConstructor(createdClass); + } + @Override protected void generateComponentLifecycle() { + SootClass activityClass = getModelledClass(); Set currentClassSet = Collections.singleton(component); final Body body = mainMethod.getActiveBody(); @@ -84,12 +95,15 @@ protected void generateComponentLifecycle() { localVarsForClasses.put(sc, callbackLocal); } - // 1. onCreate: + // 1. attachBaseContext + searchAndBuildMethod(AndroidEntryPointConstants.ATTACH_BASE_CONTEXT, thisLocal); + + // 2. onCreate: { - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONCREATE, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONCREATE, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYCREATED, - callbackClass, localVarsForClasses.get(callbackClass), currentClassSet); + localVarsForClasses.get(callbackClass), currentClassSet); } } @@ -107,13 +121,13 @@ protected void generateComponentLifecycle() { } } - // 2. onStart: + // 3. onStart: Stmt onStartStmt; { - onStartStmt = searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSTART, component, thisLocal); + onStartStmt = searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSTART, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { Stmt s = searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSTARTED, - callbackClass, localVarsForClasses.get(callbackClass), currentClassSet); + localVarsForClasses.get(callbackClass), currentClassSet); if (onStartStmt == null) onStartStmt = s; } @@ -130,23 +144,23 @@ protected void generateComponentLifecycle() { { Stmt afterOnRestore = Jimple.v().newNopStmt(); createIfStmt(afterOnRestore); - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESTOREINSTANCESTATE, component, thisLocal, + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESTOREINSTANCESTATE, thisLocal, currentClassSet); body.getUnits().add(afterOnRestore); } - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPOSTCREATE, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPOSTCREATE, thisLocal); - // 3. onResume: + // 4. onResume: Stmt onResumeStmt = Jimple.v().newNopStmt(); body.getUnits().add(onResumeStmt); { - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESUME, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESUME, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYRESUMED, - callbackClass, localVarsForClasses.get(callbackClass), currentClassSet); + localVarsForClasses.get(callbackClass), currentClassSet); } } - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPOSTRESUME, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPOSTRESUME, thisLocal); // Scan for other entryPoints of this class: if (this.callbacks != null && !this.callbacks.isEmpty()) { @@ -162,17 +176,17 @@ protected void generateComponentLifecycle() { createIfStmt(startWhileStmt); } - // 4. onPause: - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPAUSE, component, thisLocal); + // 5. onPause: + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONPAUSE, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYPAUSED, callbackClass, + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYPAUSED, localVarsForClasses.get(callbackClass), currentClassSet); } - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONCREATEDESCRIPTION, component, thisLocal); - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSAVEINSTANCESTATE, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONCREATEDESCRIPTION, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSAVEINSTANCESTATE, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSAVEINSTANCESTATE, - callbackClass, localVarsForClasses.get(callbackClass), currentClassSet); + localVarsForClasses.get(callbackClass), currentClassSet); } // goTo Stop, Resume or Create: @@ -180,12 +194,12 @@ protected void generateComponentLifecycle() { createIfStmt(onResumeStmt); // createIfStmt(onCreateStmt); // no, the process gets killed in between - // 5. onStop: - Stmt onStop = searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSTOP, component, thisLocal); + // 6. onStop: + Stmt onStop = searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONSTOP, thisLocal); boolean hasAppOnStop = false; for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { Stmt onActStoppedStmt = searchAndBuildMethod( - AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSTOPPED, callbackClass, + AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYSTOPPED, localVarsForClasses.get(callbackClass), currentClassSet); hasAppOnStop |= onActStoppedStmt != null; } @@ -198,37 +212,22 @@ protected void generateComponentLifecycle() { createIfStmt(stopToDestroyStmt); // createIfStmt(onCreateStmt); // no, the process gets killed in between - // 6. onRestart: - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESTART, component, thisLocal); + // 7. onRestart: + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONRESTART, thisLocal); body.getUnits().add(Jimple.v().newGotoStmt(onStartStmt)); // jump to onStart() - // 7. onDestroy + // 8. onDestroy body.getUnits().add(stopToDestroyStmt); - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONDESTROY, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONDESTROY, thisLocal); for (SootClass callbackClass : this.activityLifecycleCallbacks.keySet()) { searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITYLIFECYCLECALLBACK_ONACTIVITYDESTROYED, - callbackClass, localVarsForClasses.get(callbackClass), currentClassSet); + localVarsForClasses.get(callbackClass), currentClassSet); } } - @Override - protected void createAdditionalFields() { - super.createAdditionalFields(); - - // Create a name for a field for the result intent of this component - String fieldName = "ipcResultIntent"; - int fieldIdx = 0; - while (component.declaresFieldByName(fieldName)) - fieldName = "ipcResultIntent_" + fieldIdx++; - - // Create the field itself - resultIntentField = Scene.v().makeSootField(fieldName, RefType.v("android.content.Intent"), Modifier.PUBLIC); - resultIntentField.addTag(SimulatedCodeElementTag.TAG); - component.addField(resultIntentField); - } - @Override protected void createAdditionalMethods() { + super.createAdditionalMethods(); if (InfoflowAndroidConfiguration.getCreateActivityEntryMethods()) { createGetIntentMethod(); @@ -242,7 +241,7 @@ protected void createAdditionalMethods() { * the correct field */ private void createSetIntentMethod() { - // We need to create an implementation of "getIntent". If there is already such + // We need to create an implementation of "setIntent". If there is already such // an implementation, we don't touch it. if (component.declaresMethod("void setIntent(android.content.Intent)")) return; @@ -258,8 +257,8 @@ private void createSetIntentMethod() { b.insertIdentityStmts(); Local lcIntent = b.getParameterLocal(0); - b.getUnits().add(Jimple.v() - .newAssignStmt(Jimple.v().newInstanceFieldRef(b.getThisLocal(), intentField.makeRef()), lcIntent)); + b.getUnits().add(Jimple.v().newInvokeStmt(Jimple.v().newInterfaceInvokeExpr(b.getThisLocal(), + componentExchangeInfo.setIntentMethod.makeRef(), Arrays.asList(lcIntent)))); b.getUnits().add(Jimple.v().newReturnVoidStmt()); } @@ -286,8 +285,8 @@ private void createSetResultMethod() { b.insertIdentityStmts(); Local lcIntent = b.getParameterLocal(1); - b.getUnits().add(Jimple.v().newAssignStmt( - Jimple.v().newInstanceFieldRef(b.getThisLocal(), resultIntentField.makeRef()), lcIntent)); + b.getUnits().add(Jimple.v().newInvokeStmt(Jimple.v().newInterfaceInvokeExpr(b.getThisLocal(), + componentExchangeInfo.setResultIntentMethod.makeRef(), Arrays.asList(lcIntent)))); b.getUnits().add(Jimple.v().newReturnVoidStmt()); // Activity.setResult() is final. We need to change that @@ -298,19 +297,8 @@ private void createSetResultMethod() { } @Override - protected void reset() { - super.reset(); - - component.removeField(resultIntentField); - resultIntentField = null; - } - - @Override - public ComponentEntryPointInfo getComponentInfo() { - ActivityEntryPointInfo activityInfo = new ActivityEntryPointInfo(mainMethod); - activityInfo.setIntentField(intentField); - activityInfo.setResultIntentField(resultIntentField); - return activityInfo; + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.ACTIVITYCLASS); } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointInfo.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointInfo.java deleted file mode 100644 index 19a3a2f12..000000000 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ActivityEntryPointInfo.java +++ /dev/null @@ -1,58 +0,0 @@ -package soot.jimple.infoflow.android.entryPointCreators.components; - -import java.util.HashSet; -import java.util.Set; - -import soot.SootField; -import soot.SootMethod; - -public class ActivityEntryPointInfo extends ComponentEntryPointInfo { - - private SootField resultIntentField; - - public ActivityEntryPointInfo(SootMethod entryPoint) { - super(entryPoint); - } - - void setResultIntentField(SootField resultIntentField) { - this.resultIntentField = resultIntentField; - } - - public SootField getResultIntentField() { - return resultIntentField; - } - - @Override - public Set getAdditionalFields() { - Set fields = new HashSet<>(super.getAdditionalFields()); - if (resultIntentField != null) - fields.add(resultIntentField); - return fields; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((resultIntentField == null) ? 0 : resultIntentField.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - ActivityEntryPointInfo other = (ActivityEntryPointInfo) obj; - if (resultIntentField == null) { - if (other.resultIntentField != null) - return false; - } else if (!resultIntentField.equals(other.resultIntentField)) - return false; - return true; - } - -} diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/BroadcastReceiverEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/BroadcastReceiverEntryPointCreator.java index ae6b8de5a..b1d8910f7 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/BroadcastReceiverEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/BroadcastReceiverEntryPointCreator.java @@ -1,10 +1,14 @@ package soot.jimple.infoflow.android.entryPointCreators.components; +import soot.Local; +import soot.Scene; import soot.SootClass; +import soot.SootField; import soot.jimple.Jimple; import soot.jimple.NopStmt; import soot.jimple.Stmt; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; /** @@ -16,13 +20,23 @@ public class BroadcastReceiverEntryPointCreator extends AbstractComponentEntryPointCreator { public BroadcastReceiverEntryPointCreator(SootClass component, SootClass applicationClass, - IManifestHandler manifest) { - super(component, applicationClass, manifest); + IManifestHandler manifest, SootField instantiatorField, SootField classLoaderField, + ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); + } + + @Override + protected Local generateClassConstructor(SootClass createdClass) { + if (createdClass == component && instantiatorField != null) { + return super.generateInstantiator(createdClass, + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATERECEIVER, body.getParameterLocal(0)); + } + return super.generateClassConstructor(createdClass); } @Override protected void generateComponentLifecycle() { - Stmt onReceiveStmt = searchAndBuildMethod(AndroidEntryPointConstants.BROADCAST_ONRECEIVE, component, thisLocal); + Stmt onReceiveStmt = searchAndBuildMethod(AndroidEntryPointConstants.BROADCAST_ONRECEIVE, thisLocal); // methods NopStmt startWhileStmt = Jimple.v().newNopStmt(); @@ -43,4 +57,8 @@ protected void createAdditionalMethods() { createGetIntentMethod(); } + @Override + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.BROADCASTRECEIVERCLASS); + } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointCollection.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointCollection.java index bc69fdb2d..5b105c766 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointCollection.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointCollection.java @@ -9,6 +9,7 @@ import soot.SootClass; import soot.SootField; import soot.SootMethod; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; /** * Mapping class for associating components with the data object for their @@ -20,6 +21,11 @@ public class ComponentEntryPointCollection { protected Map componentToEntryPointInfo = new HashMap<>(); + private ComponentExchangeInfo componentExchangeInfo; + + public ComponentExchangeInfo getComponentExchangeInfo() { + return componentExchangeInfo; + } public void put(SootClass component, ComponentEntryPointInfo info) { componentToEntryPointInfo.put(component, info); @@ -60,4 +66,9 @@ public boolean hasEntryPointForComponent(SootClass component) { return componentToEntryPointInfo.containsKey(component); } + public void setComponentExchangeInfo(ComponentExchangeInfo n) { + this.componentExchangeInfo = n; + + } + } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointInfo.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointInfo.java index 4b1c0579c..47af6c65b 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointInfo.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ComponentEntryPointInfo.java @@ -16,7 +16,6 @@ public class ComponentEntryPointInfo { private final SootMethod entryPoint; - private SootField intentField; public ComponentEntryPointInfo(SootMethod entryPoint) { this.entryPoint = entryPoint; @@ -26,18 +25,8 @@ public SootMethod getEntryPoint() { return entryPoint; } - void setIntentField(SootField intentField) { - this.intentField = intentField; - } - - public SootField getIntentField() { - return intentField; - } - public Set getAdditionalFields() { - if (intentField == null) - return Collections.emptySet(); - return Collections.singleton(intentField); + return Collections.emptySet(); } @Override @@ -45,7 +34,6 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((entryPoint == null) ? 0 : entryPoint.hashCode()); - result = prime * result + ((intentField == null) ? 0 : intentField.hashCode()); return result; } @@ -63,11 +51,6 @@ public boolean equals(Object obj) { return false; } else if (!entryPoint.equals(other.entryPoint)) return false; - if (intentField == null) { - if (other.intentField != null) - return false; - } else if (!intentField.equals(other.intentField)) - return false; return true; } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ContentProviderEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ContentProviderEntryPointCreator.java index e40bb2234..2eab61f9f 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ContentProviderEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ContentProviderEntryPointCreator.java @@ -1,12 +1,28 @@ package soot.jimple.infoflow.android.entryPointCreators.components; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import soot.Body; +import soot.Local; +import soot.RefType; +import soot.Scene; import soot.SootClass; +import soot.SootField; import soot.SootMethod; +import soot.Type; import soot.UnitPatchingChain; +import soot.javaToJimple.DefaultLocalGenerator; import soot.jimple.Jimple; import soot.jimple.NopStmt; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; +import soot.jimple.infoflow.util.SootUtils; /** * Entry point creator for content providers @@ -16,9 +32,85 @@ */ public class ContentProviderEntryPointCreator extends AbstractComponentEntryPointCreator { - public ContentProviderEntryPointCreator(SootClass component, SootClass applicationClass, - IManifestHandler manifest) { - super(component, applicationClass, manifest); + private SootMethod initMethod; + private String initCPMethodName = "initCP"; + + public ContentProviderEntryPointCreator(SootClass component, SootClass applicationClass, IManifestHandler manifest, + SootField instantiatorField, SootField classLoaderField, ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); + } + + public SootMethod createInit() { + SootMethod m = createEmptyInitMethod(); + body = m.retrieveActiveBody(); + generator = new DefaultLocalGenerator(body); + Local l = generateClassConstructor(component); + searchAndBuildMethod(AndroidEntryPointConstants.CONTENTPROVIDER_ONCREATE, l); + + body.getUnits().add(Jimple.v().newReturnStmt(l)); + //make sure the body doesn't get used when creating the actual dummy main + body = null; + generator = null; + return m; + } + + @Override + protected List getAdditionalMainMethodParams() { + return Arrays.asList(RefType.v(AndroidEntryPointConstants.CONTENTPROVIDERCLASS)); + } + + private SootMethod createEmptyInitMethod() { + int methodIndex = 0; + String methodName = initCPMethodName; + SootClass mainClass = getOrCreateDummyMainClass(); + while (mainClass.declaresMethodByName(methodName)) + methodName = initCPMethodName + "_" + methodIndex++; + + Body body; + + // Create the method + SootMethod initMethod = Scene.v().makeSootMethod(methodName, Collections.emptyList(), + RefType.v(AndroidEntryPointConstants.CONTENTPROVIDERCLASS)); + + // Create the body + body = Jimple.v().newBody(); + body.setMethod(initMethod); + initMethod.setActiveBody(body); + + // Add the method to the class + mainClass.addMethod(initMethod); + + // First add class to scene, then make it an application class + // as addClass contains a call to "setLibraryClass" + mainClass.setApplicationClass(); + initMethod.setModifiers(Modifier.PUBLIC | Modifier.STATIC); + + this.initMethod = initMethod; + return initMethod; + } + + @Override + public Collection getAdditionalMethods() { + List list = new ArrayList<>(super.getAdditionalMethods()); + if (initMethod != null) + list.add(initMethod); + return list; + } + + @Override + protected void createEmptyMainMethod() { + super.createEmptyMainMethod(); + //the parameter with the content provider local + thisLocal = mainMethod.getActiveBody().getParameterLocal(1); + } + + @Override + protected Local generateClassConstructor(SootClass createdClass) { + if (createdClass == component && instantiatorField != null) { + return super.generateInstantiator(createdClass, + AndroidEntryPointConstants.APPCOMPONENTFACTORY_INSTANTIATEPROVIDER); + } + return super.generateClassConstructor(createdClass); } @Override @@ -43,8 +135,9 @@ protected void generateComponentLifecycle() { NopStmt beforeCallbacksStmt = Jimple.v().newNopStmt(); units.add(beforeCallbacksStmt); + SootClass providerClass = getModelledClass(); for (String methodSig : AndroidEntryPointConstants.getContentproviderLifecycleMethods()) { - SootMethod sm = findMethod(component, methodSig); + SootMethod sm = SootUtils.findMethod(providerClass, methodSig); if (sm != null && !sm.getSubSignature().equals(AndroidEntryPointConstants.CONTENTPROVIDER_ONCREATE)) { NopStmt afterMethodStmt = Jimple.v().newNopStmt(); createIfStmt(afterMethodStmt); @@ -57,4 +150,8 @@ protected void generateComponentLifecycle() { units.add(endWhileStmt); } + @Override + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.CONTENTPROVIDERCLASS); + } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/FragmentEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/FragmentEntryPointCreator.java index e20199a9c..2d2713fae 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/FragmentEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/FragmentEntryPointCreator.java @@ -6,13 +6,16 @@ import heros.TwoElementSet; import soot.Local; import soot.RefType; +import soot.Scene; import soot.SootClass; +import soot.SootField; import soot.Type; import soot.jimple.Jimple; import soot.jimple.NopStmt; import soot.jimple.NullConstant; import soot.jimple.Stmt; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; /** @@ -23,8 +26,9 @@ */ public class FragmentEntryPointCreator extends AbstractComponentEntryPointCreator { - public FragmentEntryPointCreator(SootClass component, SootClass applicationClass, IManifestHandler manifest) { - super(component, applicationClass, manifest); + public FragmentEntryPointCreator(SootClass component, SootClass applicationClass, IManifestHandler manifest, + SootField instantiatorField, SootField classLoaderField, ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); } @Override @@ -41,8 +45,7 @@ protected void generateComponentLifecycle() { TwoElementSet classAndFragment = new TwoElementSet(component, scActivity); Stmt afterOnAttachFragment = Jimple.v().newNopStmt(); createIfStmt(afterOnAttachFragment); - searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONATTACHFRAGMENT, component, thisLocal, - classAndFragment); + searchAndBuildMethod(AndroidEntryPointConstants.ACTIVITY_ONATTACHFRAGMENT, thisLocal, classAndFragment); body.getUnits().add(afterOnAttachFragment); // Render the fragment lifecycle @@ -61,68 +64,64 @@ private void generateFragmentLifecycle(SootClass currentClass, Local classLocal, createIfStmt(endFragmentStmt); // 1. onAttach: - Stmt onAttachStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONATTACH, currentClass, classLocal, + Stmt onAttachStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONATTACH, classLocal, Collections.singleton(activity)); if (onAttachStmt == null) body.getUnits().add(onAttachStmt = Jimple.v().newNopStmt()); // 2. onCreate: - Stmt onCreateStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONCREATE, currentClass, - classLocal); + Stmt onCreateStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONCREATE, classLocal); if (onCreateStmt == null) body.getUnits().add(onCreateStmt = Jimple.v().newNopStmt()); // 3. onCreateView: - Stmt onCreateViewStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONCREATEVIEW, currentClass, - classLocal); + Stmt onCreateViewStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONCREATEVIEW, classLocal); if (onCreateViewStmt == null) body.getUnits().add(onCreateViewStmt = Jimple.v().newNopStmt()); - Stmt onViewCreatedStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONVIEWCREATED, currentClass, - classLocal); + Stmt onViewCreatedStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONVIEWCREATED, classLocal); if (onViewCreatedStmt == null) body.getUnits().add(onViewCreatedStmt = Jimple.v().newNopStmt()); // 0. onActivityCreated: - Stmt onActCreatedStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONACTIVITYCREATED, - currentClass, classLocal); + Stmt onActCreatedStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONACTIVITYCREATED, classLocal); if (onActCreatedStmt == null) body.getUnits().add(onActCreatedStmt = Jimple.v().newNopStmt()); // 4. onStart: - Stmt onStartStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSTART, currentClass, classLocal); + Stmt onStartStmt = searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSTART, classLocal); if (onStartStmt == null) body.getUnits().add(onStartStmt = Jimple.v().newNopStmt()); // 5. onResume: Stmt onResumeStmt = Jimple.v().newNopStmt(); body.getUnits().add(onResumeStmt); - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONRESUME, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONRESUME, classLocal); // Add the fragment callbacks addCallbackMethods(); // 6. onPause: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONPAUSE, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONPAUSE, classLocal); createIfStmt(onResumeStmt); // 7. onSaveInstanceState: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSAVEINSTANCESTATE, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSAVEINSTANCESTATE, classLocal); // 8. onStop: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSTOP, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONSTOP, classLocal); createIfStmt(onCreateViewStmt); createIfStmt(onStartStmt); // 9. onDestroyView: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDESTROYVIEW, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDESTROYVIEW, classLocal); createIfStmt(onCreateViewStmt); // 10. onDestroy: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDESTROY, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDESTROY, classLocal); // 11. onDetach: - searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDETACH, currentClass, classLocal); + searchAndBuildMethod(AndroidEntryPointConstants.FRAGMENT_ONDETACH, classLocal); createIfStmt(onAttachStmt); body.getUnits().add(Jimple.v().newAssignStmt(classLocal, NullConstant.v())); @@ -134,4 +133,9 @@ protected List getAdditionalMainMethodParams() { return Collections.singletonList((Type) RefType.v(AndroidEntryPointConstants.ACTIVITYCLASS)); } + @Override + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.FRAGMENTCLASS); + } + } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceConnectionEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceConnectionEntryPointCreator.java index 8be27ddf0..14507a4b0 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceConnectionEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceConnectionEntryPointCreator.java @@ -1,9 +1,12 @@ package soot.jimple.infoflow.android.entryPointCreators.components; +import soot.Scene; import soot.SootClass; +import soot.SootField; import soot.jimple.Jimple; import soot.jimple.NopStmt; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; /** @@ -15,13 +18,14 @@ public class ServiceConnectionEntryPointCreator extends AbstractComponentEntryPointCreator { public ServiceConnectionEntryPointCreator(SootClass component, SootClass applicationClass, - IManifestHandler manifest) { - super(component, applicationClass, manifest); + IManifestHandler manifest, SootField instantiatorField, SootField classLoaderField, + ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); } @Override protected void generateComponentLifecycle() { - searchAndBuildMethod(AndroidEntryPointConstants.SERVICECONNECTION_ONSERVICECONNECTED, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICECONNECTION_ONSERVICECONNECTED, thisLocal); // methods NopStmt startWhileStmt = Jimple.v().newNopStmt(); @@ -32,7 +36,12 @@ protected void generateComponentLifecycle() { body.getUnits().add(endWhileStmt); createIfStmt(startWhileStmt); - searchAndBuildMethod(AndroidEntryPointConstants.SERVICECONNECTION_ONSERVICEDISCONNECTED, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICECONNECTION_ONSERVICEDISCONNECTED, thisLocal); + } + + @Override + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.SERVICECONNECTIONINTERFACE); } } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceEntryPointCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceEntryPointCreator.java index 58f67fee1..bb311e8b3 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceEntryPointCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/entryPointCreators/components/ServiceEntryPointCreator.java @@ -3,15 +3,25 @@ import java.util.Collections; import java.util.List; -import soot.*; +import soot.Local; +import soot.Modifier; +import soot.RefType; +import soot.Scene; +import soot.SootClass; +import soot.SootField; +import soot.SootMethod; +import soot.Type; +import soot.Unit; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.NopStmt; import soot.jimple.Stmt; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointConstants; import soot.jimple.infoflow.android.entryPointCreators.AndroidEntryPointUtils.ComponentType; +import soot.jimple.infoflow.android.entryPointCreators.ComponentExchangeInfo; import soot.jimple.infoflow.android.manifest.IManifestHandler; import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; +import soot.jimple.infoflow.util.SootUtils; /** * Entry point creator for Android services @@ -23,19 +33,24 @@ public class ServiceEntryPointCreator extends AbstractComponentEntryPointCreator protected SootField binderField = null; - public ServiceEntryPointCreator(SootClass component, SootClass applicationClass, IManifestHandler manifest) { - super(component, applicationClass, manifest); + public ServiceEntryPointCreator(SootClass component, SootClass applicationClass, IManifestHandler manifest, + SootField instantiatorField, SootField classLoaderField, ComponentExchangeInfo componentExchangeInfo) { + super(component, applicationClass, manifest, instantiatorField, classLoaderField, componentExchangeInfo); } @Override protected void generateComponentLifecycle() { - // 1. onCreate: - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONCREATE, component, thisLocal); + + // 1. attachBaseContext + searchAndBuildMethod(AndroidEntryPointConstants.ATTACH_BASE_CONTEXT, thisLocal); + + // 2. onCreate: + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONCREATE, thisLocal); // service has two different lifecycles: // lifecycle1: - // 2. onStart: - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONSTART1, component, thisLocal); + // 3. onStart: + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONSTART1, thisLocal); // onStartCommand can be called an arbitrary number of times, or never NopStmt beforeStartCommand = Jimple.v().newNopStmt(); @@ -43,7 +58,7 @@ protected void generateComponentLifecycle() { body.getUnits().add(beforeStartCommand); createIfStmt(afterStartCommand); - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONSTART2, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONSTART2, thisLocal); createIfStmt(beforeStartCommand); body.getUnits().add(afterStartCommand); @@ -78,7 +93,7 @@ protected void generateComponentLifecycle() { // lifecycle2 start // onBind: - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONBIND, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONBIND, thisLocal); NopStmt beforemethodsStmt = Jimple.v().newNopStmt(); body.getUnits().add(beforemethodsStmt); @@ -89,7 +104,7 @@ protected void generateComponentLifecycle() { hasAdditionalMethods = false; if (componentType == ComponentType.GCMBaseIntentService) for (String sig : AndroidEntryPointConstants.getGCMIntentServiceMethods()) { - SootMethod sm = findMethod(component, sig); + SootMethod sm = SootUtils.findMethod(component, sig); if (sm != null && !sm.getName().equals(AndroidEntryPointConstants.GCMBASEINTENTSERVICECLASS)) if (createPlainMethodCall(thisLocal, sm)) hasAdditionalMethods = true; @@ -101,18 +116,18 @@ protected void generateComponentLifecycle() { // onUnbind: Stmt onDestroyStmt = Jimple.v().newNopStmt(); - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONUNBIND, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONUNBIND, thisLocal); createIfStmt(onDestroyStmt); // fall through to rebind or go to destroy // onRebind: - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONREBIND, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONREBIND, thisLocal); createIfStmt(beforemethodsStmt); // lifecycle2 end // onDestroy: body.getUnits().add(onDestroyStmt); - searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONDESTROY, component, thisLocal); + searchAndBuildMethod(AndroidEntryPointConstants.SERVICE_ONDESTROY, thisLocal); } /** @@ -128,7 +143,7 @@ protected void generateComponentLifecycle() { protected boolean createSpecialServiceMethodCalls(List methodSigs, String parentClass) { boolean hasAdditionalMethods = false; for (String sig : methodSigs) { - SootMethod sm = findMethod(component, sig); + SootMethod sm = SootUtils.findMethod(component, sig); if (sm != null && !sm.getDeclaringClass().getName().equals(parentClass)) if (createPlainMethodCall(thisLocal, sm)) hasAdditionalMethods = true; @@ -166,7 +181,7 @@ protected void createAdditionalMethods() { * passed in as an argument, in the global field */ private void instrumentOnBind() { - SootMethod sm = component.getMethodUnsafe("android.os.IBinder onBind(android.content.Intent)"); + SootMethod sm = SootUtils.findMethod(component, "android.os.IBinder onBind(android.content.Intent)"); final Type intentType = RefType.v("android.content.Intent"); final Type binderType = RefType.v("android.os.IBinder"); if (sm == null || !sm.isConcrete()) { @@ -216,9 +231,13 @@ protected void reset() { @Override public ComponentEntryPointInfo getComponentInfo() { ServiceEntryPointInfo serviceInfo = new ServiceEntryPointInfo(mainMethod); - serviceInfo.setIntentField(intentField); serviceInfo.setBinderField(binderField); return serviceInfo; } + @Override + protected SootClass getModelledClass() { + return Scene.v().getSootClass(AndroidEntryPointConstants.SERVICECLASS); + } + } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccInstrumentDestination.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccInstrumentDestination.java index 637293bc8..8e699141f 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccInstrumentDestination.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccInstrumentDestination.java @@ -43,7 +43,7 @@ public static IccInstrumentDestination v() { return s; } - private static RefType INTENT_TYPE = RefType.v("android.content.Intent"); + private RefType INTENT_TYPE = RefType.v("android.content.Intent"); private IccLink iccLink = null; public SootClass instrumentDestinationForContentProvider(String destination) { diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccRedirectionCreator.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccRedirectionCreator.java index 14223b186..af6e1be45 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccRedirectionCreator.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/iccta/IccRedirectionCreator.java @@ -33,10 +33,10 @@ import soot.jimple.JimpleBody; import soot.jimple.NullConstant; import soot.jimple.Stmt; -import soot.jimple.infoflow.android.entryPointCreators.components.ActivityEntryPointInfo; import soot.jimple.infoflow.android.entryPointCreators.components.ComponentEntryPointCollection; import soot.jimple.infoflow.android.entryPointCreators.components.ServiceEntryPointInfo; import soot.jimple.infoflow.entryPointCreators.SimulatedCodeElementTag; +import soot.jimple.infoflow.util.SootUtils; import soot.jimple.infoflow.util.SystemClassHandler; import soot.tagkit.Tag; import soot.util.HashMultiMap; @@ -196,7 +196,6 @@ protected SootMethod generateRedirectMethodForStartActivityForResult(SootClass o // call onCreate Local componentLocal = lg.generateLocal(destComp.getType()); - ActivityEntryPointInfo entryPointInfo = (ActivityEntryPointInfo) componentToEntryPoint.get(destComp); { SootMethod targetDummyMain = componentToEntryPoint.getEntryPoint(destComp); if (targetDummyMain == null) @@ -208,12 +207,14 @@ protected SootMethod generateRedirectMethodForStartActivityForResult(SootClass o // Get the activity result Local arIntentLocal = lg.generateLocal(INTENT_TYPE); - b.getUnits().add(Jimple.v().newAssignStmt(arIntentLocal, - Jimple.v().newInstanceFieldRef(componentLocal, entryPointInfo.getResultIntentField().makeRef()))); + b.getUnits().add(Jimple.v().newAssignStmt(arIntentLocal, Jimple.v().newInterfaceInvokeExpr(componentLocal, + componentToEntryPoint.getComponentExchangeInfo().getResultIntentMethod.makeRef()))); // some apps do not have an onActivityResult method even they use // startActivityForResult to communicate with other components. - SootMethod method = originActivity.getMethodUnsafe("void onActivityResult(int,int,android.content.Intent)"); + + SootMethod method = SootUtils.findMethod(originActivity, + "void onActivityResult(int,int,android.content.Intent)"); if (method != null) { List args = new ArrayList<>(); args.add(IntConstant.v(-1)); diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IAndroidApplication.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IAndroidApplication.java index b829996b0..344f08534 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IAndroidApplication.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IAndroidApplication.java @@ -45,4 +45,10 @@ public interface IAndroidApplication { */ public Boolean isUsesCleartextTraffic(); + /** + * Returns the value of appComponentFactory, which specifies a factory used to create the android components + * @return The fully-qualified class name of the factory class or null + */ + public String getAppComponentFactory(); + } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IManifestHandler.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IManifestHandler.java index 157b68458..e2dc420e3 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IManifestHandler.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/IManifestHandler.java @@ -105,16 +105,20 @@ public default Set getEntryPointClasses() { for (IAndroidComponent node : getAllComponents()) checkAndAddComponent(entryPoints, node); - if (entryPoints.isEmpty()){ + if (entryPoints.isEmpty()) { //if no entry point is detected at all, the app is likely be malware, add all components - List allEnabled = getAllComponents().stream().filter(c -> c.isEnabled()).collect(Collectors.toList()); - allEnabled.forEach(e->entryPoints.add(e.getNameString())); + List allEnabled = getAllComponents().stream().filter(c -> c.isEnabled()) + .collect(Collectors.toList()); + allEnabled.forEach(e -> entryPoints.add(e.getNameString())); } if (app != null) { String appName = app.getName(); if (appName != null && !appName.isEmpty()) entryPoints.add(appName); + String factory = app.getAppComponentFactory(); + if (factory != null && !factory.isEmpty()) + entryPoints.add(factory); } return entryPoints; diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/binary/BinaryAndroidApplication.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/binary/BinaryAndroidApplication.java index e524140e3..a9f902242 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/binary/BinaryAndroidApplication.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/manifest/binary/BinaryAndroidApplication.java @@ -22,6 +22,7 @@ public class BinaryAndroidApplication implements IAndroidApplication { protected boolean allowBackup; protected String name; protected Boolean usesCleartextTraffic; + private String appComponentFactory; public BinaryAndroidApplication(AXmlNode node, BaseProcessManifest manifest) { this.node = node; @@ -41,7 +42,8 @@ public BinaryAndroidApplication(AXmlNode node, BaseProcessManifest m if (attrCleartextTraffic != null) usesCleartextTraffic = attrCleartextTraffic.asBoolean(arscParser); - this.name = loadApplicationName(); + this.name = expandClassAttribute("name"); + this.appComponentFactory = expandClassAttribute("appComponentFactory"); } @Override @@ -50,12 +52,28 @@ public boolean isEnabled() { } /** - * Gets the name of the Android application class - * - * @return The name of the Android application class + * Returns the value of an attribute describing a class + * @param attrName the attribute name + * @return the value (or null if not set) */ - private String loadApplicationName() { - AXmlAttribute attr = node.getAttribute("name"); + private String expandClassAttribute(String attrName) { + String n = expandAttribute(attrName); + if (n != null) { + if (n.startsWith(".")) { + n = manifest.getPackageName() + n; + } + } + return n; + } + + /** + * Returns the value of an attribute while resolving resources, e.g. if a + * string resource is referenced + * @param attrName the attribute name + * @return the value (or null if not set) + */ + private String expandAttribute(String attrName) { + AXmlAttribute attr = node.getAttribute(attrName); if (attr != null) return attr.asString(manifest.getArscParser()); return null; @@ -85,4 +103,9 @@ public Boolean isUsesCleartextTraffic() { return usesCleartextTraffic; } + @Override + public String getAppComponentFactory() { + return appComponentFactory; + } + } diff --git a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java index 66bfe7166..536c1fadd 100644 --- a/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java +++ b/soot-infoflow-android/src/soot/jimple/infoflow/android/source/AndroidSourceSinkManager.java @@ -320,7 +320,7 @@ private String findLastStringAssignment(Stmt stmt, Local local, BiDiInterprocedu * @return The layout control that is being accessed at the given statement, or * null if no such control could be found */ - protected AndroidLayoutControl getLayoutControl(Stmt sCallSite, IInfoflowCFG cfg) { + protected Set getLayoutControl(Stmt sCallSite, IInfoflowCFG cfg) { // If we don't have a layout control list, we cannot perform any // more specific checks if (this.layoutControls == null) @@ -340,21 +340,27 @@ protected AndroidLayoutControl getLayoutControl(Stmt sCallSite, IInfoflowCFG cfg return null; } - Integer id = valueProvider.getValue(uiMethod, sCallSite, iexpr.getArg(0), Integer.class); - if (id == null && iexpr.getArg(0) instanceof Local) { - id = findLastResIDAssignment(sCallSite, (Local) iexpr.getArg(0), cfg, + Set ids = valueProvider.getValue(uiMethod, sCallSite, iexpr.getArg(0), Integer.class); + if ((ids == null || ids.isEmpty()) && iexpr.getArg(0) instanceof Local) { + Integer id = findLastResIDAssignment(sCallSite, (Local) iexpr.getArg(0), cfg, new HashSet(cfg.getMethodOf(sCallSite).getActiveBody().getUnits().size())); + if (id != null) + ids = Collections.singleton(id); } - if (id == null) { + if (ids == null || ids.isEmpty()) { logger.debug("Could not find assignment to local " + ((Local) iexpr.getArg(0)).getName() + " in method " + cfg.getMethodOf(sCallSite).getSignature()); return null; } - AndroidLayoutControl control = this.layoutControls.get(id); - if (control == null) - return null; - return control; + Set set = new HashSet(); + for (int id : ids) { + AndroidLayoutControl control = this.layoutControls.get(id); + if (control != null) + set.add(control); + } + + return set; } private boolean isResourceCall(SootMethod callee) { @@ -403,11 +409,13 @@ protected ISourceSinkDefinition getUISourceDefinition(Stmt sCallSite, IInfoflowC return MethodSourceSinkDefinition.createReturnSource(CallType.MethodCall); } - AndroidLayoutControl control = getLayoutControl(sCallSite, cfg); - if (control != null) { - if (sourceSinkConfig.getLayoutMatchingMode() == LayoutMatchingMode.MatchSensitiveOnly - && control.isSensitive()) { - return control.getSourceDefinition(); + Set control = getLayoutControl(sCallSite, cfg); + if (control != null && !control.isEmpty()) { + for (AndroidLayoutControl c : control) { + if (sourceSinkConfig.getLayoutMatchingMode() == LayoutMatchingMode.MatchSensitiveOnly + && c.isSensitive()) { + return c.getSourceDefinition(); + } } } } diff --git a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/BaseEntryPointCreator.java b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/BaseEntryPointCreator.java index 294ba3659..c57e8300f 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/BaseEntryPointCreator.java +++ b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/BaseEntryPointCreator.java @@ -28,7 +28,9 @@ import soot.ArrayType; import soot.Body; import soot.BooleanType; +import soot.ByteConstant; import soot.ByteType; +import soot.CharConstant; import soot.CharType; import soot.DoubleType; import soot.FloatType; @@ -39,6 +41,7 @@ import soot.PrimType; import soot.RefType; import soot.Scene; +import soot.ShortConstant; import soot.ShortType; import soot.SootClass; import soot.SootMethod; @@ -177,13 +180,31 @@ public SootMethod createDummyMain() { * @return The class tha contains the dummy main method */ protected SootClass getOrCreateDummyMainClass() { - SootClass mainClass = Scene.v().getSootClassUnsafe(dummyClassName); - if (mainClass == null) { - mainClass = Scene.v().makeSootClass(dummyClassName); - mainClass.setResolvingLevel(SootClass.BODIES); - Scene.v().addClass(mainClass); + SootClass sc = getOrCreateClass(dummyClassName); + dummyClassName = sc.getName(); + return sc; + } + + protected SootClass getOrCreateClass(String name) { + SootClass mainClass = Scene.v().getSootClassUnsafe(name, false); + int i = 1; + String n = name; + while (true) { + if (mainClass != null) { + if (mainClass.hasTag(SimulatedCodeElementTag.TAG_NAME)) { + return mainClass; + } + i++; + n = name + i; + mainClass = Scene.v().getSootClassUnsafe(n, false); + } else { + mainClass = Scene.v().makeSootClass(n); + mainClass.setResolvingLevel(SootClass.BODIES); + mainClass.addTag(SimulatedCodeElementTag.TAG); + Scene.v().addClass(mainClass); + return mainClass; + } } - return mainClass; } /** @@ -519,6 +540,10 @@ protected boolean acceptClass(SootClass clazz) { // We cannot create instances of phantom classes as we do not have any // constructor information for them if (!clazz.getName().equals("android.view.View")) { + if (clazz.isPhantom() || clazz.isPhantomClass()) { + clazz.setLibraryClass(); + } + Scene.v().forceResolve(clazz.getName(), SootClass.SIGNATURES); if (clazz.isPhantom() || clazz.isPhantomClass()) { logger.warn("Cannot generate constructor for phantom class {}", clazz.getName()); return false; @@ -745,11 +770,11 @@ protected Value getSimpleDefaultValue(Type t) { if (t == RefType.v("java.lang.String")) return StringConstant.v(""); if (t instanceof CharType) - return IntConstant.v(0); + return CharConstant.v(0); if (t instanceof ByteType) - return IntConstant.v(0); + return ByteConstant.v(0); if (t instanceof ShortType) - return IntConstant.v(0); + return ShortConstant.v(0); if (t instanceof IntType) return IntConstant.v(0); if (t instanceof FloatType) @@ -765,26 +790,6 @@ protected Value getSimpleDefaultValue(Type t) { return NullConstant.v(); } - /** - * Finds a method with the given signature in the given class or one of its - * super classes - * - * @param currentClass The current class in which to start the search - * @param subsignature The subsignature of the method to find - * @return The method with the given signature if it has been found, otherwise - * null - */ - protected SootMethod findMethod(SootClass currentClass, String subsignature) { - SootMethod m = currentClass.getMethodUnsafe(subsignature); - if (m != null) { - return m; - } - if (currentClass.hasSuperclass()) { - return findMethod(currentClass.getSuperclass(), subsignature); - } - return null; - } - /** * Checks whether an object of type "actual" can be inserted where an object of * type "expected" is required. diff --git a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/DefaultEntryPointCreator.java b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/DefaultEntryPointCreator.java index d79e8b08a..4c02f6667 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/DefaultEntryPointCreator.java +++ b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/DefaultEntryPointCreator.java @@ -34,6 +34,7 @@ import soot.jimple.NopStmt; import soot.jimple.infoflow.data.SootMethodAndClass; import soot.jimple.infoflow.util.SootMethodRepresentationParser; +import soot.jimple.infoflow.util.SootUtils; import soot.jimple.toolkits.scalar.NopEliminator; /** @@ -93,7 +94,7 @@ protected SootMethod createDummyMainInternal() { Local classLocal = localVarsForClasses.get(entry.getKey()); for (String method : entry.getValue()) { SootMethodAndClass methodAndClass = SootMethodRepresentationParser.v().parseSootMethodString(method); - SootMethod currentMethod = findMethod(Scene.v().getSootClass(methodAndClass.getClassName()), + SootMethod currentMethod = SootUtils.findMethod(Scene.v().getSootClass(methodAndClass.getClassName()), methodAndClass.getSubSignature()); if (currentMethod == null) { logger.warn("Entry point not found: {}", method); diff --git a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/SequentialEntryPointCreator.java b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/SequentialEntryPointCreator.java index b33594569..56a679e03 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/SequentialEntryPointCreator.java +++ b/soot-infoflow/src/soot/jimple/infoflow/entryPointCreators/SequentialEntryPointCreator.java @@ -12,6 +12,7 @@ import soot.jimple.Jimple; import soot.jimple.infoflow.data.SootMethodAndClass; import soot.jimple.infoflow.util.SootMethodRepresentationParser; +import soot.jimple.infoflow.util.SootUtils; /** * Simple entry point creator that builds a sequential list of method @@ -57,7 +58,7 @@ protected SootMethod createDummyMainInternal() { // Create the method calls for (String method : classMap.get(className)) { SootMethodAndClass methodAndClass = SootMethodRepresentationParser.v().parseSootMethodString(method); - SootMethod methodToInvoke = findMethod(Scene.v().getSootClass(methodAndClass.getClassName()), + SootMethod methodToInvoke = SootUtils.findMethod(Scene.v().getSootClass(methodAndClass.getClassName()), methodAndClass.getSubSignature()); if (methodToInvoke == null) diff --git a/soot-infoflow/src/soot/jimple/infoflow/util/SootMethodRepresentationParser.java b/soot-infoflow/src/soot/jimple/infoflow/util/SootMethodRepresentationParser.java index b49ce8292..25e340af6 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/util/SootMethodRepresentationParser.java +++ b/soot-infoflow/src/soot/jimple/infoflow/util/SootMethodRepresentationParser.java @@ -171,4 +171,13 @@ public String[] getParameterTypesFromSubSignature(String subSignature) { } + /** + * Returns the return type of the sub signature + * @param subsig the sub signature + * @return the return type + */ + public String getReturnTypeFromSubSignature(String subsig) { + return subsig.substring(0, subsig.indexOf(" ")); + } + } diff --git a/soot-infoflow/src/soot/jimple/infoflow/util/SootUtils.java b/soot-infoflow/src/soot/jimple/infoflow/util/SootUtils.java index e92994137..dfc484570 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/util/SootUtils.java +++ b/soot-infoflow/src/soot/jimple/infoflow/util/SootUtils.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; import soot.Unit; import soot.Value; import soot.ValueBox; @@ -30,4 +33,19 @@ public static List getUseAndDefValues(Unit u) { return valueList; } + /** + * Finds a method with the given signature in the given class or one of its + * super classes + * + * @param currentClass The current class in which to start the search + * @param subsignature The subsignature of the method to find + * @return The method with the given signature if it has been found, otherwise + * null + */ + public static SootMethod findMethod(SootClass currentClass, String subsignature) { + Scene sc = Scene.v(); + return sc.getOrMakeFastHierarchy().resolveMethod(currentClass, + sc.makeMethodRef(currentClass, subsignature, false), true); + } + } diff --git a/soot-infoflow/src/soot/jimple/infoflow/values/IValueProvider.java b/soot-infoflow/src/soot/jimple/infoflow/values/IValueProvider.java index 3a5efad30..9083a8e7e 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/values/IValueProvider.java +++ b/soot-infoflow/src/soot/jimple/infoflow/values/IValueProvider.java @@ -1,6 +1,9 @@ package soot.jimple.infoflow.values; +import java.util.Set; + import soot.SootMethod; +import soot.Type; import soot.Value; import soot.jimple.Stmt; @@ -12,6 +15,24 @@ */ public interface IValueProvider { - public T getValue(SootMethod sm, Stmt stmt, Value value, Class type); + /** + * Tries to find pseudo-constants for a given value at a given statement in a method + * @param the type the pseudo-constant should be + * @param sm the method the statement is within + * @param stmt the statement for which to inquire a value + * @param value the value the analysis is interested in + * @param type the type the pseudo-constant should be + * @return the value as type T or null + */ + public Set getValue(SootMethod sm, Stmt stmt, Value value, Class type); + + /** + * Tries to find a set of concrete types of a value + * @param sm the method the statement is within + * @param stmt the statement for which to inquire the type + * @param value the value the analysis is interested in + * @return a set of possible types for value + */ + public Set getType(SootMethod sm, Stmt stmt, Value value); } diff --git a/soot-infoflow/src/soot/jimple/infoflow/values/SimpleConstantValueProvider.java b/soot-infoflow/src/soot/jimple/infoflow/values/SimpleConstantValueProvider.java index 2c0117105..8c3723630 100644 --- a/soot-infoflow/src/soot/jimple/infoflow/values/SimpleConstantValueProvider.java +++ b/soot-infoflow/src/soot/jimple/infoflow/values/SimpleConstantValueProvider.java @@ -1,10 +1,14 @@ package soot.jimple.infoflow.values; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import soot.Local; import soot.SootField; import soot.SootMethod; +import soot.Type; import soot.Unit; import soot.Value; import soot.jimple.AssignStmt; @@ -16,7 +20,6 @@ import soot.jimple.LongConstant; import soot.jimple.Stmt; import soot.jimple.StringConstant; -import soot.tagkit.ConstantValueTag; import soot.tagkit.DoubleConstantValueTag; import soot.tagkit.FloatConstantValueTag; import soot.tagkit.IntegerConstantValueTag; @@ -37,14 +40,19 @@ public class SimpleConstantValueProvider implements IValueProvider { @SuppressWarnings("unchecked") @Override - public Object getValue(SootMethod sm, Stmt stmt, Value value, Class type) { - if (value instanceof Constant) - return getConstantOfType(value, type); + public Set getValue(SootMethod sm, Stmt stmt, Value value, Class type) { + if (value instanceof Constant) { + Object c = getConstantOfType(value, type); + if (c != null) + return Collections.singleton(c); + return null; + } if (value instanceof Local) { // Find the defs BriefUnitGraph ug = new BriefUnitGraph(sm.getActiveBody()); SimpleLocalDefs du = new SimpleLocalDefs(ug); + Set ret = new HashSet<>(); List defs = du.getDefsOfAt((Local) value, stmt); for (Unit def : defs) { if (!(def instanceof AssignStmt)) @@ -58,8 +66,9 @@ public Object getValue(SootMethod sm, Stmt stmt, Value value, Class type) { // Use the ConstantTag to retrieve the constant value Object constant = getConstantFromTag(((FieldRef) rightOp).getField(), type); if (constant != null) - return constant; + ret.add(constant); } + return ret; } return null; @@ -82,6 +91,8 @@ private Object getConstantOfType(Value value, Class type) { if (value instanceof StringConstant) return ((StringConstant) value).value; } + if (type.isInstance(value)) + return value; return null; } @@ -104,4 +115,9 @@ private Object getConstantFromTag(SootField f, Class type) { } return null; } + + @Override + public Set getType(SootMethod sm, Stmt stmt, Value value) { + return Collections.singleton(value.getType()); + } }