Friday, August 31, 2012

Maliciously Loading an Application's Native Libraries

I recently reviewed an interesting Android application that was distributed on a per-device basis.  The reason for this is that the application used a unique identifier that was hard-coded into the application.  Generally speaking this is an unsafe process.  However, usually I see secret keys hard-coded into the Java files, which would make it difficult for a malicious application to access.  This application, however, contained a per-device compiled native shared library that contained the key.  How does this affect the overall security of the key? Lets find out.

Some of the guys over at Intrepidus Group posted a few months ago about some of the "gotchas" with using native libraries in your Android application, and this post should complement it.  The first interesting thing about native libraries is that, by default, they have lax read permissions when installed on your device.



The fact that it is world-readable introduces an interesting question - is a third party application able to load this library?  The answer to the question is, well, yes!

For anyone who has read my post on debugging native code, I'm going to use the code from that post here as well (application "DebugNative").  As a refresher for those who have not read it, the application contained a native library which checked a user supplied password against a password generated within the native shared library.  The Java section of the code passed the supplied password to the native library using the JNI, the verification occurred, and the return value indicated if the password was correct.

Lets suppose this password is dynamic generated, or is a per-device password, and we want to access it.  We have a copy of the application so we can investigate!

First I pulled the application APK file off the device and decoded using "apktool".  Next I inspected the "lib/armeabi/" directory for the native libraries.  Running the "file" command on "lib/armeabi/libnative.so" shows that it is stripped, meaning that the symbol table (.symtab) has been removed, but that doesn't mean we cannot call functions from this library by viewing the dynamic symbol table.

 

"generatePassword" seems like a good start.  Using IDA, we can derive the prototype of this function.



Pretty straightforward - it doesn't look like anything is being passed to the function, and the return value (R0) and UI supplied password are compared with the "strcmp" function.  We can assume this function looks like:  char *generatePassword();

Next, we write our own malicious application that trie to load the DebugNative's shared library and use this function.  Our application will start, load its own shared library, execute a function using JNI, and print the result to the log.  All the magic will take place from within our shared library.  The main activity looks like the following (imports omitted):



Now we implement our shared library, "aunative.c".



I'll explain what this function does:
  • First we attempt to open the shared library "/data/data/com.jakev.debugnative/lib/libnative.so" using dlopen().
  • Next we attempt to resolve the symbol for the function "generatePassword" using dlsym().  We store the return in a function pointer, getpassword, that mimics the prototype of the "generatePassword" function.
  • We call the getpassword() function pointer and store the return in the buffer password.
  • We open the file "/mnt/sdcard/password.txt" for binary writing (just in case) and write the contents of password to the file.  We then close the file.
  • Finally, we unload the "libnative.so" shared library with dlclose().
If all works properly, we should see the file "/mnt/sdcard/password.txt" with our password (I hid the password just in case people wanted to try to figure it out themselves using this method or debugging the application).  It's worth noting that this application would require the "WRITE_EXTERNAL_STORAGE" permission.




So that's that problem, what can we do about this?  Googling around you can find a couple of options to help prevent this.  One I have seen used effectively is the keyword static when defining your functions.  This will effectively limit the scope of function and will help restrict access.  Thus our password generating function would look like: static char *generatePassword();

When we attempt to inspect the new shared library we notice that our string is no longer present in the file, anywhere.



Then, when I attempt to run the malicious application with the new DebugNative installed, I notice a return value of -2 in the Android logs, which indicated our call to dlsym() has failed.

I hope this post demonstrates the risk associated with inadequately protecting Android native libraries!

-jakev