Using Static Analysis to Review File Access in Android Apps

The Android platform does some clever things to firewall apps off from one another. One of the important protections the platform provides is to install each application under its own Linux user account, and then use file ownership and permissions to keep one application from reading or modifying another app’s files. By default, files are set up so that they are only readable/writeable by their own app’s user account and you have to explicitly create files as world-readable and/or world writeable.

Because applications often misuse these platform protections, assessing the security of an Android application should include checks to verify that the application is taking proper advantage of them. There might be valid reasons for creating files that are world-readable and world-writeable, but finding these files is certainly cause for more inspection and probably a frank discussion with the developer about their threat model and design decisions. In this blog post we will look through some static analysis techniques and freely-available tools that can be used to perform some of this testing. All of the analysis scripts can be downloaded from the Google Code repository at www.smartphonesdumbapps.com. I’d recommend pulling down the latest from the Subversion repository rather than using one of the packaged downloads so you get the most updated code.

For the purposes of our testing, let’s assume that we have been given an Android app APK file – so all we have is what an attacker might pull down from an app store. Unbeknownst to us (at the current time), the application has a method that creates a bunch of files with a variety of permissions. The source code looks something like this:

privatevoid createFilesForNoReason()
{
      FileOutputStream stream1, stream2, stream3, stream4;
      String privateFile = “privateFile”;
      String worldReadableFile = “worldReadableFile”;
      String worldWriteableFile = “worldWriteableFile”;
      String worldReadableWriteableFile = “worldReadableWriteableFile”;
      Context context = getApplicationContext();
     
      try {
            stream1 = context.openFileOutput(privateFile, Context.MODE_PRIVATE);
            stream2 = context.openFileOutput(worldReadableFile, Context.MODE_WORLD_READABLE);
            stream3 = context.openFileOutput(worldWriteableFile, Context.MODE_WORLD_WRITEABLE);
            stream4 = context.openFileOutput(worldReadableWriteableFile,
                                    Context.MODE_WORLD_READABLE |
                                    Context.MODE_WORLD_WRITEABLE);
                 
      } catch (Exception e) {
            //    Serves you right for trying to open so many files with
            //    unjustified permissions…
      }

}

Before we got the APK file, this method would have been compiled down down to DEX bytecode. If we run the analyze_android.pl script from the analysis scripts we downloaded, one of the tasks that will be executed (among others) will be to disassemble the DEX code in the APK into {APP} /unpack/classes_dex_unpacked/ subdirectory. We can then go in and look at the DEX assembler for the different classes in the Android app. The DEX assembler for the createFilesForNoReason method looks like this:

.method private createFilesForNoReason()V
.limit registers 11
; this: v10 (Lcom/app/denim/group/Config;)
.catch java/lang/Exception from lf82 to lfa6 using lfaa
.var 2 is stream1 Ljava/io/FileOutputStream; from lf8a to lfa8
.var 3 is stream2 Ljava/io/FileOutputStream; from lf94 to lfa8
.var 4 is stream3 Ljava/io/FileOutputStream; from lf9e to lfa8
.line 97
        const-string    v1,”privateFile”
.line 98
        const-string    v6,”worldReadableFile”
.line 99
        const-string    v8,”worldWriteableFile”
.line 100
        const-string    v7,”worldReadableWriteableFile”
.line 101
        invoke-virtual  {v10},com/app/denim/group/Config/getApplicationContext  ; getApplicationContext()Landroid/content/Context;
        move-result-object      v0
.line 104
        const/4 v9,0
lf82:
        invoke-virtual  {v0,v1,v9},android/content/Context/openFileOutput       ; openFileOutput(Ljava/lang/String;I)Ljava/io/FileOutputStream;
        move-result-object      v2
.line 105
        const/4 v9,1
        invoke-virtual  {v0,v6,v9},android/content/Context/openFileOutput       ; openFileOutput(Ljava/lang/String;I)Ljava/io/FileOutputStream;
        move-result-object      v3
.line 106
        const/4 v9,2
        invoke-virtual  {v0,v8,v9},android/content/Context/openFileOutput       ; openFileOutput(Ljava/lang/String;I)Ljava/io/FileOutputStream;
       move-result-object      v4
.line 108
        const/4 v9,3
.line 107
        invoke-virtual  {v0,v7,v9},android/content/Context/openFileOutput       ; openFileOutput(Ljava/lang/String;I)Ljava/io/FileOutputStream;
lfa6:
        move-result-object      v5
lfa8:
.line 115
        return-void
lfaa:
.line 111
        move-exception  v9
        goto    lfa8
.end method

If we want to look at what an Android app is doing with files, a good starting place would be calls to Context.openFileOutput().  In the DEX assembly, that means we would look for invoke-virtual calls to android/content/Context/openFileOutput.  One example might look like this:

.line 107
        invoke-virtual  {v0,v7,v9},android/content/Context/openFileOutput

This shows us that the method was called, and the arguments for that method can be found in registers, v0, v7 and v9 (corresponding roughly to a Context “this” pointer, the filename to open for output and the file permissions (see openFileOutput() JavaDoc for more details). So now we know that this application is manipulating files for output and we need to track down the filename and file permissions. If we backtrack through the DEX assembler, starting at the invoke-virtual call and looking for where the v7 register was filled we find this:

.line 100
        const-string    v7,”worldReadableWriteableFile”

So in this case it looks like the file being opened is called (conveniently) “worldReadableWriteableFile”  Now that we know the file name, we also need to determine the file access. Starting at the same invoke-virtual call if we backtrack and look for where the v9 register was filled we find:

.line 108
        const/4 v9,3

So what we have determined is that this app essentially contains code in it somewhere that looks like:

context.openFileOutput(“worldReadableWriteableFile”, 3)

So what does the “3” mean?  Poking around the JavaDoc some more we can determine that the values of the constants are:

  • Context.MODE_PRIVATE: 0
  • Context.MODE_WORLD_READABLE: 1
  • Context.MODE_WORLD_WRITEABLE: 2

So it appears that this file is both world readable and world writeable (1 | 2). At this point it is probably a good time to sit down with the development team and find out from them why the files needs to have essentially no file protections and explore alternate designs for the application that avoids this.

As you see we have done some quick, grep-like analysis of the application DEX assembly code and identified this one specific instance. Obviously this can be automated to run similar checks for all places where Context.openFileOutput() is being called. As luck would have it, there is another script available in the download that just just this. It is called android_file_usage.pl and is intended to be run with an argument that points to a directory containing disassembled DEX code. The android_analyze.pl run before actually does this as well and puts the output into the file {APP}/file_usage.txt.

So if we run android_file_usage.pl on all of the DEX assembly code created from the Java code listed above we get:

privateFile,0,NONE,DenimGroupApp_apk/unpack/classes_dex_unpacked/com/app/denim/group/Config.ddx
worldReadableFile,1,WORLD_READ,DenimGroupApp_apk/unpack/classes_dex_unpacked/com/app/denim/group/Config.ddx
worldWriteableFile,2,WORLD_WRITE,DenimGroupApp_apk/unpack/classes_dex_unpacked/com/app/denim/group/Config.ddx
worldReadableWriteableFile,3,WORLD_READWRITE,DenimGroupApp_apk/unpack/classes_dex_unpacked/com/app/denim/group/Config.ddx

Again – probably a great time to talk with the developers about file permissions on the Android platform.

The “static analysis” that is being performed here is pretty crudimentary – there are a variety of ways that apps could make it harder to find the arguments being passed in. For example, the filename could be constructed from multiple strings or user inputs rather than on a constant in the code. However, in tests using this automated analysis both on the test code as well as APKs from the wild it seems to fit most programmer coding idioms and compiler/disassembler behavior to at least get a quick first-look at what an app is doing with local files that can be followed up with a manual review of any calls to Context.openFileOutput() where the script was unsuccessful in determining access permissions.

In this blog post we have walked through one way of using static analysis to look at how Android apps are accessing files.  This could also be done dynamically by looking at files created by the app on the device and looking at their file permissions, but we will save that for a future blog post.

Contact us for help testing the security of your mobile applications.

–Dan

dan _at_ denimgroup.com

@danielcornell

Posted via email from Denim Group’s Posterous

About dancornell

Dan Cornell has over fifteen years of experience architecting and developing web-based software systems. He leads Denim Group's security research team in investigating the application of secure coding and development techniques to improve web-based software development methodologies. Dan was the founding coordinator and chairman for the Java Users Group of San Antonio (JUGSA) and currently serves as the OWASP San Antonio chapter leader. Dan has speaks at such international conferences as RSA, ROOTs in Norway and OWASP AppSec EU.

4 Responses to "Using Static Analysis to Review File Access in Android Apps"

Leave a reply