secure-software-engineering / FlowDroid

FlowDroid Static Data Flow Tracker
GNU Lesser General Public License v2.1
1.04k stars 296 forks source link

Data-flow analysis on system services #388

Open abdawoud opened 2 years ago

abdawoud commented 2 years ago

First, thank you for your valuable work and support:

Is it possible to run FlowDroid on binaries of system services, e.g., to taint one parameter across methods of the application framework and also across ICC?

A concrete example would be to trace the packageName parameter of the following API of the LocationManagerService and see whether it reaches a sink method of my choice: public Location getLastLocation(LocationRequest request, String packageName, String featureId)

Specifically, I would like to be able to run: java -jar soot-infoflow-cmd-2.9.0-jar-with-dependencies.jar -a services.jar -p android.jar -s SourcesAndSinks.txt and interpret the flow of the packageName across methods.

The services.jar is the binary of the application framework that hosts the LocationManagerService, and SourcesAndSinks.txt contains a dummy API that calls the getLastLocation as a source and another arbitrary sink method of my choice (e.g., checkPackagePermissions)

Currently, executing this command fails due to services.jar not containing an AndroidManifest.xml file, which makes sense., but how to go beyond this error and enable such analysis.

If this feature is not immediately available, how do you think this could be achieved using the FlowDroid tools-chain.

StevenArzt commented 2 years ago

FlowDroid can run on apps as well as normal Java code. The data flow tracker has different modules:

Therefore, you need to decide whether you want to start with an APK and the Android semantics such as Intents, or whether you are interested in a normal Java implementation of the Android OS as a JAR. You can also extend the Android analyzer with additional JARs on the classpath to provide implementations of functions that would otherwise be just interfaces to libraries pre-installed on the phone.

This gives you two choices.

1) Start with the pure-Java implementation of the system service and define the interface as source or sink, e.g., take the getLastKnownLocation method, put it into a DefaultEntryPointCreator and write an ISourceSinkManager that registers the second parameter as a source. In this case, there is no IPC left, because your analysis runs entirely on the side of the system service implementation, regardless of how apps interact with the service. This would be a purely Java-based analysis.

2) Start with an app that exercises the system service. You would then provide the JAR file with the system service implementation on the Soot classpath, and an IC3 model for the IccTA component in FlowDroid to generate the mapping between the ICC calls and the system service. Keep in mind that FlowDroid checks for calls to startActivity and the like, so you need to have the implemenation of the Android framework method behind getLastKnownLocation, which sends the Intent to the system service. This is not part of the app, so you need a real framework JAR, not the stub JAR from the SDK.

In any case, you can't use the command-line application. For approach 1), you can use the Infoflow class. For approach 2), you can use the SetupApplication class.

abdawoud commented 2 years ago

Thank you for your prompt and detailed response.

I opted for choice #1 and wrote the following code accordingly.

String targetPath = "{..}/services.jar";
String libPath = "~/Android/Sdk/platforms/android-30/android.jar";

IInfoflow infoflow = new Infoflow();
List<String> epoints = new ArrayList<String>();
epoints.add("com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)");
DefaultEntryPointCreator entryPoints = new DefaultEntryPointCreator(epoints);

ISourceSinkManager sourceSinkMgr = new ISourceSinkManager() {

    @Override
    public void initialize() {
        // TODO Auto-generated method stub
    }

    @Override
    public SourceInfo getSourceInfo(Stmt sCallSite, InfoflowManager manager) {
        // SootMethod callee = sCallSite.containsInvokeExpr() ? sCallSite.getInvokeExpr().getMethod() : null;
        // IdentityStmt istmt = (IdentityStmt) sCallSite;
        // SootMethod currentMethod = manager.getICFG().getMethodOf(istmt);
        // AccessPath targetAP = manager.getAccessPathFactory()
        //      .createAccessPath(currentMethod.getActiveBody().getParameterLocal(2), true);
        // return new SourceInfo(new MethodSourceSinkDefinition(new SootMethodAndClass(callee)), targetAP);
        return null;
    }

    @Override
    public SinkInfo getSinkInfo(Stmt sCallSite, InfoflowManager manager, AccessPath ap) {
        return null;
    }
};

infoflow.computeInfoflow(targetPath, libPath, entryPoints, sourceSinkMgr);

The commented logic in getSourceInfo was driven from one of the unit tests. I have no experience in developing applications based on FlowDroid so excuse me if something is utterly stupid here. Anyway, it seems I'm doing something wrong because the log shows that no callgraph has been created and the source was not recognized.

SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:{..}/.m2/repository/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:{..}/soot-4.3.0-20210909.064646-208-jar-with-dependencies.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory] [main] INFO soot.jimple.infoflow.Infoflow - Resetting Soot... [main] INFO soot.jimple.infoflow.Infoflow - Basic class loading done. [main] ERROR soot.jimple.infoflow.Infoflow - Only phantom classes loaded, skipping analysis... [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Implicit flow tracking is NOT enabled [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Exceptional flow tracking is enabled [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Running with a maximum access path length of 5 [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Using path-agnostic result collection [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Recursive access path shortening is enabled [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Taint analysis enabled: true [main] INFO soot.jimple.infoflow.InfoflowConfiguration - Using alias algorithm FlowSensitive [main] INFO soot.jimple.infoflow.memory.MemoryWarningSystem - Registered a memory warning system for 9,637.2 MiB [main] INFO soot.jimple.infoflow.Infoflow - Callgraph construction took 0 seconds [main] INFO soot.jimple.infoflow.codeOptimization.InterproceduralConstantValuePropagator - Removing side-effect free methods is disabled [main] INFO soot.jimple.infoflow.Infoflow - Dead code elimination took 0.011735565 seconds [main] INFO soot.jimple.infoflow.Infoflow - Callgraph has 0 edges [main] INFO soot.jimple.infoflow.Infoflow - Starting Taint Analysis [main] INFO soot.jimple.infoflow.data.FlowDroidMemoryManager - Initializing FlowDroid memory manager... [main] INFO soot.jimple.infoflow.Infoflow - Using context- and flow-sensitive solver [main] INFO soot.jimple.infoflow.Infoflow - Using context- and flow-sensitive solver [main] WARN soot.jimple.infoflow.Infoflow - Running with limited join point abstractions can break context-sensitive path builders [main] INFO soot.jimple.infoflow.Infoflow - Looking for sources and sinks... [main] ERROR soot.jimple.infoflow.Infoflow - No sources found, aborting analysis [main] INFO soot.jimple.infoflow.memory.MemoryWarningSystem - Shutting down the memory warning system... [main] WARN soot.jimple.infoflow.Infoflow - No results found. [main] INFO soot.jimple.infoflow.Infoflow - Data flow solver took 0 seconds. Maximum memory consumption: 69 MB

It's worth noting that I can analyze the services.jar using native soot in another application I have written myself. So I'm confident that the target API and class exist there.

Any insight from you on why this is possibly happening is much appreciated.

StevenArzt commented 2 years ago

Your ISourceSinkManager always returns null. So whenever FlowDroid asks whether a given statement is a source or a sink, you always reply with "no". That's why FlowDroid tells you that there are no sources. If you want to mark a parameter as a source, you should create a SourceInfo object for the parameter local. Your commented-out code seems fine at first glance, so I wonder whether you gave that code a try.

The callgraph is a different story. Your entry point seems to be missing the < and > around a Soot method signature. That might be a part of the problem. The DefaultEntryPointCreator is fairly simple, so you can simply debug into it - or you look at the dummy main method that it creates. I'd assume it's empty in your case, maybe due to the broken signature.

abdawoud commented 2 years ago

Thank you very much for the hints! I managed to get it working after updating some of SOOT options:

diff --git a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java
index 2be4908..34ca797 100644
--- a/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java
+++ b/soot-infoflow/src/soot/jimple/infoflow/AbstractInfoflow.java
@@ -191,6 +191,8 @@ public abstract class AbstractInfoflow implements IInfoflow {

                        Options.v().set_no_bodies_for_excluded(true);
                        Options.v().set_allow_phantom_refs(true);
+                       Options.v().set_process_multiple_dex(true);
+                       Options.v().set_search_dex_in_archives(true);
                        if (config.getWriteOutputFiles())
                                Options.v().set_output_format(Options.output_format_jimple);
                        else

Based on my limited experience with SOOT, this is because my target (i.e., services.jar) is composed of multiple dex files and of jar type. Thus, set_process_multiple_dex and set_search_dex_in_archives options should be enabled.

I also updated my implementation of the ISourceSinkManager to:

ISourceSinkManager sourceSinkMgr = new ISourceSinkManager() {

    @Override
    public void initialize() {
        // TODO Auto-generated method stub
    }

    @Override
    public SourceInfo getSourceInfo(Stmt sCallSite, InfoflowManager manager) {
        SootMethod currentMethod = manager.getICFG().getMethodOf(sCallSite);
        if (currentMethod.toString().contains("getLastLocation")) {
            AccessPath targetAP = manager.getAccessPathFactory()
                    .createAccessPath(currentMethod.retrieveActiveBody().getParameterLocal(1), true);
            SourceInfo targetInfo = new SourceInfo(null, targetAP);
            return targetInfo;
        }
        return null;
    }

    @Override
    public SinkInfo getSinkInfo(Stmt sCallSite, InfoflowManager manager, AccessPath ap) {
        return new SinkInfo(null);
    }
};

I was too excited to get the following results, but found something strange given the current implementation of the getLastLocation API from the LocationManagerService.

FlowDroid managed to get the first and second sinks rights. The third sink doesn't act as a sink for the packagName or any of its aliases, instead, it gets the provider name via request.getProvider(). Maybe I'm interpreting the results wrong but is it also possible that this is a false positive?

[main] INFO soot.jimple.infoflow.Infoflow - The sink $z0 = virtualinvoke $r6.<com.android.server.location.SettingsHelper: boolean isLocationPackageBlacklisted(int,java.lang.String)>($i0, $r1) in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> was called with values from the following sources: [main] INFO soot.jimple.infoflow.Infoflow - - $r1 = $r5. in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - The sink $r5 = staticinvoke <com.android.server.location.CallerIdentity: com.android.server.location.CallerIdentity fromBinderUnsafe(android.content.Context,java.lang.String,java.lang.String)>(r4, $r1, $r2) in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> was called with values from the following sources: [main] INFO soot.jimple.infoflow.Infoflow - - $r3 = in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - $r1 := @parameter1: java.lang.String in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - r0 := @this: com.android.server.location.LocationManagerService in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - if $r3 != null goto r4 = r0. in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - $r2 := @parameter2: java.lang.String in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - r4 = r0. in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - - $r3 := @parameter0: android.location.LocationRequest in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> [main] INFO soot.jimple.infoflow.Infoflow - The sink $r10 = specialinvoke r0.<com.android.server.location.LocationManagerService: com.android.server.location.LocationManagerService$LocationProviderManager getLocationProviderManager(java.lang.String)>($r1) in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)> was called with values from the following sources: [main] INFO soot.jimple.infoflow.Infoflow - - $r1 = virtualinvoke $r3.<android.location.LocationRequest: java.lang.String getProvider()>() in method <com.android.server.location.LocationManagerService: android.location.Location getLastLocation(android.location.LocationRequest,java.lang.String,java.lang.String)>

StevenArzt commented 2 years ago

You're currently marking every statement in the whole code as a sink. This may lead to funny results, e.g., when a method is a source and a sink at the same time for the same access path. I'd be more precise here.

Also, you're registering the same sources over and over again, since getSourceInfo and getSinkInfo are calles for each statement and are supposed to check that statement whether it is a source or sink. I think FlowDroid does a set union in the end, so the duplicates should get kicked out, but the interface was meant to be used differently. It would be better to check whether you have an IdentityStmt that references the correct parameter, i.e., has a ParameterRef on the right side with the correct index. In that case, the current statement is the correct one for which to return the left side of the assignment (which is the parameter local - have a look at getParameterLocal(), that's exactly how they are found). Again, this should not be the problem here, but it is worth noting.

In your issue, you seem to have a wrong source. You could print out all SourceInfo objects that you return and see if you have one that you didn't want.

abdawoud commented 2 years ago

Thanks a lot! You have been very patient with me.

Just one last question, given the example of the getLastLocation API, is it possible, using FlowDroid, to detect that identity.packageName has been assigned the second parameter (i.e., packageName) of the getLastLocation API via the CallerIdentity.fromBinderUnsafe? This way, identity.packageName should be automatically treated as a source and then it should be traced across sinks.

Is this possible by default? or do I have to encode this logic myself?

StevenArzt commented 2 years ago

There is no assignment from packageName to identity.packageName in this method. I would have to look into the implementation of fromBinderUnsafe to see whether there is an assignment somewhere in this calle tree. If so, FlowDroid should automatically taintt the field while propagating the taint.

If there is no such assignment in the code, but you have external domain knowledge to know that after the call to fromBinderUnsafe, the field inherits the taint from the second parameter, you can write a summary for fromBinderUnsafe. Have a look at the summariesManual folder in soot-infoflow-summaries for examples and make sure to register the SummaryTaintWrapper with your Infoflow instance.

flankerhqd commented 2 years ago

There will be other quirks as FlowDroid heavily relies on the isClassInSystemPackage function, which by default ignores flows from and within android.* and com.android.*, which definitely will cause problems if you run it on AOSP source.