Hex-Rays' blog

IDA Dalvik debugger: tips and tricks – Hex Rays

Written by   Nikolay Logvinov | Jul 10, 2014

One of the new features of IDA 6.6 is the Dalvik debugger, which allows us to debug Dalvik binaries on the bytecode level.

Let us see how it can help when analysing Dalvik files.

Encoded strings

Let us consider the package with the encrypted strings:

STRINGS:0001F143 unk_1F143:.byte 0x30 # 0  # DATA XREF: STR_IDS:off_70
STRINGS:0001F144 aFda8sohchnidgh:
.string "FDA8sOhCHNidghM2hzFxMXUsivl2k7hFOhkJrW7O2ml8qLVM",0
STRINGS:0001F144                           # DATA XREF: q_b@V
STRINGS:0001F144                           # String #277 (0x115)
STRINGS:0001F175 unk_1F175:.byte 0x3C # <  # DATA XREF: STR_IDS:off_70
STRINGS:0001F176 aCgv01n8li2s3ok:
.string "CGv01N8li2s3OKN29j6exe6-rvzgIRaCcWoOt5y30zjP1k43-f7WVOtXjbg="
STRINGS:0001F176                           # DATA XREF: q_b@V+C

There is a data reference, let us see where this string is used (e.g. using Ctrl-X).

CODE:000090C0 const-string        v0, aFda8sohchnidgh # "FDA8sOhCH"...
CODE:000090C4 invoke-static       {v0}, <ref RC4.decryptBase64(ref)
                                         RC4_decryptBase64@LL>
CODE:000090CA move-result-object  v0

So, apparently the strings are encrypted with RC4+Base64.

Let us set a breakpoint after the RC4.decryptBase64() call and start the debugger.

After hitting the breakpoint, open the “Locals” debugger window. Even if the application was stripped of debug information, IDA makes the best effort to show function’s input arguments and return value.

Note the local variable named retval. It is a synthetic variable created IDA to show the function return value.

This is how we managed to decode the string contents.

How to debug Dalvik and ARM code together

Let us have a look at application that uses a native library. On a button press, the function stringFromJNI() implemented in the native library is called.

package ida.debug.hellojni;
public class MainActivity extends Activity {
    public native String stringFromJNI();
    static {
        System.loadLibrary("hello-jni");
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final TextView tv = new TextView(this);
        final Button btn = new Button(this);
        btn.setText("Press me to call the native code");
        btn.setOnClickListener(new Button.OnClickListener() {
                public void onClick(View v) {
                        tv.setText(stringFromJNI());
                }
        });
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.setLayoutParams(new LayoutParams(
            LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        layout.addView(btn);
        layout.addView(tv);
        setContentView(layout);
    }
}

Native library function returns a well-known string.

jstring Java_ida_debug_hellojni_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject thiz)
{
  return (*env)->NewStringUTF(env, "Hello, JNI world!");
}

So, we have application packaged in hellojni.apk file and installed in Android Emulator.

Because IDA cannot analyse or debug both Dalvik and native (ARM) code at the same time, we’ll need to use two IDA instances to perform debugging in turns.

To prepare the first IDA instance:

  1. load hellojni.apk into IDA, select classes.dex to analyze.

  2. go to the native function call and set breakpoint there.

    CODE:0000051C  iget-object         v0, this, MainActivity$1_val$tv
    CODE:00000520  iget-object         v1, this, MainActivity$1_this$0
    CODE:00000524  invoke-virtual      {v1},
    <ref MainActivity.stringFromJNI() imp. @ _def_MainActivity_stringFromJNI@L>
    CODE:0000052A  move-result-object  v1
    
  3. change the default port in “Debugger/Process options” to any other value.

  4. start the Dalvik debugger and wait until breakpoint is hit.

Then prepare the second IDA instance:

  1. prepare to debug native ARM Android application (copy and start android_server and so on).

  2. load hellojni.apk into IDA, and now select lib/armeabi-v7a/libhello-jni.so to analyze.

  3. the name of the native function was formed by special rules and in our case it is Java_ida_debug_hellojni_MainActivity_stringFromJNI(), so go to to it and set a breakpoint:

    .text:00000BC4 EXPORT Java_ida_debug_hellojni_MainActivity_stringFromJNI
    .text:00000BC4 Java_ida_debug_hellojni_MainActivity_stringFromJNI
    .text:00000BC4
    .text:00000BC4 var_C  = -0xC
    .text:00000BC4 var_8  = -8
    .text:00000BC4
    .text:00000BC4        STMFD   SP!, {R11,LR}
    .text:00000BC8        ADD     R11, SP, #4
    .text:00000BCC        SUB     SP, SP, #8
    .text:00000BD0        STR     R0, [R11,#var_8]
    .text:00000BD4        STR     R1, [R11,#var_C]
    .text:00000BD8        LDR     R3, [R11,#var_8]
    .text:00000BDC        LDR     R3, [R3]
    .text:00000BE0        LDR     R2, [R3,#0x29C]
    .text:00000BE4        LDR     R0, [R11,#var_8]
    .text:00000BE8        LDR     R3, =(aHelloJniWorld - 0xBF4)
    .text:00000BEC        ADD     R3, PC, R3 ; "Hello, JNI world!"
    .text:00000BF0        MOV     R1, R3
    .text:00000BF4        BLX     R2
    .text:00000BF8        MOV     R3, R0
    .text:00000BFC        MOV     R0, R3
    .text:00000C00        SUB     SP, R11, #4
    .text:00000C04        LDMFD   SP!, {R11,PC}
    .text:00000C04 ; End of function
                     Java_ida_debug_hellojni_MainActivity_stringFromJNI
    
  4. select “Remote ARM Linux/Android debugger” and attach to the application process.

  5. press F9 to continue.

Now switch to the first IDA session and press, for example, F8 to call native function. If we return back to the second IDA session then we can notice the breakpoint event.

Now we can continue to debug the native code. When we finish, press F9 and return to the first IDA session.

The full source code of the example you can download from our site.