Friday, February 10, 2012

Debugging Apps with Native Code - part 1

If you've ever developed an application, you have probably used a debugger.  These tools can help developers find and eliminate bugs very effectively.  For developers on Android, the DDMS that the Android SDK provides makes debugging your Java code very straightforward, and debugging native code can be accomplished with the “ndk-gdb” debugger that is included with the Android NDK.  However, malicious users can also take advantage of debuggers.  Even without the source code of an application, applications containing native code can be debugged.  This is increasingly dangerous for applications that store secrets in the application itself.  This post will help you setup and debug native applications.  The following is a list of “requirements”:
  • A basic understanding of:
    • Android development and reverse engineering
    • Linux utilities
    • "gdb" and IDA Pro
  • rooted device or emulator running no lower than Android API 9 (Gingerbread)
  • A properly configured environment (Android SDK/NDK, apktool, signing utility, etc.) 
  • IDA Pro (IDA Pro demo works as well)
  • Patience :)
Note: I had to split this into two posts, so part one will contain setting up "ndk-gdb", and part two will be actually using it.

I’ve created a simple application and installed it on an emulator that simply prompts the user to enter a password.  Our goal is to obtain this password.

Our first step is to use "apktool" to decode this application and review its contents.  Doing some exploring, we quickly see that the application contains a shared library,, and further investigation shows that the “MainActivity” activity loads this library. 

We also can confirm that there are no passwords in the MainActivity.smali code (Smali code is disassembled Dalvik bytecode).  We do see, however, that a function from the native library titled “check()” is called, and passed the user supplied password.

We switch gears to IDA Pro and open up  Again, we don’t see any passwords sticking out, but we do see an export, “generatePassword.”  I’m by no means a master at ARM assembly (one of my goals is to learn more about ARM), so I need another way to obtain this password.  Our next step is setup “gdb” to debug the process.  Keep in mind this is just an example, and some people may be able to look at this function and know exactly what it does.  This may or may not be the case in a “real” application.

Now we can setup our environment to debug this application.  I will use the output directory of “apktool” as a starting point.  The goal is to launch the application, create a breakpoint around this function, and print the register containing the password.  If you have ever built apps with native code for Android, you should be familiar with the “ndk-build” command.  This tool is responsible for compiling your codes and placing them in the proper location.  When you compile code with the “NDK_DEBUG=1” option set, this tool will kindly package “gdbserver” (along with a gdb.setup configuration file) with your application so that when it is installed, the client script, “ndk-gdb,” can connect to the remote process.  Let’s see what happens if we try to launch the MainActivity activity of the Debug Native application:

The script errors out looking for a jni/ file.  The file is a Makefile that tells the compiler what it needs to do.  Obviously, it couldn’t find this, so let’s steal one from the “two-libs” sample provided with the Android NDK and modify it for our needs.

Great, now let’s try to launch the application again:

This time, it found the AndroidManifest.xml file decoded by “apktool”, but this application was not marked as “debuggable.”  We need to modify this, rebuild the application, and load our new application to the device.

Before we go ahead and reinstall the application, remember that I said that when you build your native code with “NDK_DEBUG=1”, it will include the “gdbserver” and configuration file with the application.  Let’s copy the “gdbserver” (found in the Android NDK directory) and create a fake gdb.setup.  The gdb.setup will tell “gdbserver” where it should look for shared libraries, and some other information.  When your native code is built, a copy of your shared library will be placed in the ./obj/local/armeabi*/ directory of the application, and a copy of “gdbserver” and gdb.setup will be placed in ./libs/armeabi*.  Let’s mimic this in our project directory:

Our libs/armeabi/gdb.setup looks like:

The “gdbserver” file will also get pushed to the applications directory on the device during installation, so we can do that after we install the new application.