Since I haven’t done this before, I thought I could share some experiences with you. All this began during some APK analysis which was heavily using JNIs. In my particular case Java Native Interfaces were used to call functions inside libraries written in C/C++. At that time I was quite unfamiliar with JNIs and how they actually work. Besides that I haven’t debugged any native applications/libraries on Android before. So this was the perfect opportunity to have a closer look at Android NDK and its debugging features.
Create Eclipse project
In this post I’ll first create and build a simple Android project that includes native code using the JNI. As a main source I have used this extraordinary Android JNI tutorial which I highly appreciate. Following the instructions described in the post, I have managed to successfully create an empty Android project (File -> New -> Project -> Android Application Project) for my purposes.
|
|
.
├── AndroidManifest.xml
├── assets
├── build.xml
├── ic_launcher-web.png
├── jni
├── res
└── src
4 directories, 3 files
And in src
we have following classes:
|
|
src/com/example/jni_debug_demo:
total 8
-rw-r--r-- 1 victor users 1183 Jun 30 16:44 MainActivity.java
-rw-r--r-- 1 victor users 471 Jun 30 16:44 SquaredWrapper.java
Create Android project
In order to be able to build the APK, you’ll have to create a new Android project:
|
|
Updated and renamed default.properties to project.properties
Updated local.properties
Added file ./proguard-project.txt
Now you should be able to build the project and also generate the APK:
|
|
BUILD SUCCESSFUL
Add JNI functionalities
Now that we have the base Android project, let’s add some JNI functionalities to the project. To compile the shared library (using gcc/g++) we’ll need a valid C header which can be computed from SquaredWrapper (class used in previously mentioned tutorial).
C header
The compiled classes are now in “./bin/classes
”. Let’s generate the header files for SquaredWrapper
:
|
|
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jni_debug_demo_SquaredWrapper */
#ifndef _Included_com_example_jni_debug_demo_SquaredWrapper
#define _Included_com_example_jni_debug_demo_SquaredWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jni_debug_demo_SquaredWrapper
* Method: squared
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_example_jni_1debug_1demo_SquaredWrapper_squared
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
So there is a function Java_com_example_jni_1debug_1demo_SquaredWrapper_squared (pay attention to the naming convention) which has 3 arguments. I won’t discuss this further and I’ll simple copy the file into a new folder jni
inside the project:
|
|
C source
Now that we have the function definition and the prototype generated by javah
we can easily implement the C source as follows:
|
|
#include "square.h"
JNIEXPORT jint JNICALL Java_com_example_jni_1debug_1demo_SquaredWrapper_squared (JNIEnv * je, jclass jc, jint base)
{
return (base*base);
}
So nothing special about it. Due to the introductory aspect of this post I’ll try to keep things simple. You can of course go further and implement more complex functions.
Build the library
Create a Makefile for all the Android build tools.
|
|
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := squared
LOCAL_SRC_FILES := square.c
include $(BUILD_SHARED_LIBRARY)
And now build the library (remember to set the NDK_DEBUG flag otherwise you won’t be able to debug your native code):
|
|
Android NDK: WARNING: APP_PLATFORM android-19 is larger than android:minSdkVersion 16 in ./AndroidManifest.xml
[armeabi] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver
[armeabi] Gdbsetup : libs/armeabi/gdb.setup
[armeabi] Compile thumb : squared <= square.c
[armeabi] SharedLibrary : libsquared.so
[armeabi] Install : libsquared.so => libs/armeabi/libsquared.so
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: ARM
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 12564 (bytes into file)
Flags: 0x5000000, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 21
Section header string table index: 20
Call the library
In MainActivity
some static routines of the class SquaredWrapper
are being called:
|
|
Build the project again:
|
|
BUILD SUCCESSFUL
Run the demo application
First let’s build the APK with debug enabled and sign it using a debug key:
|
|
BUILD SUCCESSFUL
Now you can install ./bin/MainActivity-debug.apk
on your Android device (whether virtual or real)
|
|
List of devices attached
0123456789ABCDEF device
|
|
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
pkg: /data/local/tmp/MainActivity-debug.apk
Success
|
|
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
Starting: Intent { cmp=com.example.jni_debug_demo/.MainActivity }
Greping for the logcat messages shows:
|
|
Debug the application
For the next steps a rooted device is required. Besides that you should install the Android NDK if you haven’t done this yet.
Remount /system as rw
First you’ll have to mount /system
with read-write rights:
|
|
/emmc@android /system ext4 ro,seclabel,noatime,noauto_da_alloc,commit=1,data=ordered 0 0
|
|
|
|
/emmc@android /system ext4 rw,seclabel,relatime,noauto_da_alloc,commit=1,data=ordered 0 0
Copy gdbserver to device
Now you’ll have to copy the gdbserver from the Android NDK into /system/bin
:
|
|
/home/victor/work/android-ndk-r10e/prebuilt/android-mips/gdbserver/gdbserver
/home/victor/work/android-ndk-r10e/prebuilt/android-x86_64/gdbserver/gdbserver
/home/victor/work/android-ndk-r10e/prebuilt/android-arm64/gdbserver/gdbserver
/home/victor/work/android-ndk-r10e/prebuilt/android-x86/gdbserver/gdbserver
/home/victor/work/android-ndk-r10e/prebuilt/android-mips64/gdbserver/gdbserver
/home/victor/work/android-ndk-r10e/prebuilt/android-arm/gdbserver/gdbserver
|
|
Processor : ARMv7 Processor rev 3 (v7l)
|
|
Copy ARM libraries to your client
In order to be able to find debug information/symbols you’ll need all ARM libraries all your device/emulator to be copied to your PC. gdb
will need them later on.
|
|
Run the application
|
|
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
WARNING: linker: libvc1dec_sa.ca7.so has text relocations. This is wasting memory and is a security risk. Please fix.
Starting: Intent { cmp=com.example.jni_debug_demo/.MainActivity }
|
|
u0_a159 28054 135 554400 14484 ffffffff 00000000 S com.example.jni_debug_demo
Now that the app is running we’re ready to start the debugger and attach it to the process ID 28054.
Attach gdb to process
In project’s root directory you’ll run ndk-gdb
which is part of the Android NDK package.
|
|
warning: Could not load shared library symbols for 108 libraries, e.g. libstdc++.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Without paying attention to the warning message, here are the steps ndk-gdb
will do for you
- check if application is running
- setup network redirection (port forwarding)
|
|
- pull several utilities (app_process, linker) from the device/emulator
- start
gdb
- attach to the process
One could of course do all these steps manually.
- Do port forwarding:
|
|
- Attach
gdbserver
to the process (on the device)
|
|
- Connect gdb
client
to the server:
|
|
But I still recommend using ndk-gdb
.
Read debugging information
And now let’s go back to the previously mentioned warning
message:
|
|
gdb
is telling us that it can’t find any debugging symbols for the loaded (ARM) libraries. In that case we’ll have to specify the path where it can find that information:
system_lib
: contains all ARM libraries from the device (/system/lib)obj/local/armeabi
: contains debugging information aboutlibsquared.so
(our target)
|
|
You can now verify the debugging information via info sharedlibrary
:
|
|
Find target function
From last output you can see that libsquared.so
starts at address 0x60b5bbe4. Let’s see what we can find there:
Bingo! So Java_com_example_jni_1debug_1demo_SquaredWrapper_squared
starts at 0x60b5bc28. We’ll definitely set a breakpoint at that address:
|
|
Trigger and debug function
For the targeted function to be executed we’ll have to trigger its execution by clicking on the “Calculate” button in the UI. Before doing that you should tell gdb
to continue execution:
|
|
After having pressed the button in the UI, you should see sth similar to this:
|
|
You can see that the execution currently stopped at 0x60b5bc40. Now you can inspect the registers, set additional breakpoints, step into routines etc.
At this point you should now be equipped with enough knowledge to dissect shared libraries and get some reverse engineering job done. Although this was a quite easy one due to the fact that we had debug symbols and were able to compile the library, the same techniques should also work on stripped binaries. In the post I’ll some binary analysis on some random Android shared library using radare.