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:
-
load
hellojni.apk
into IDA, selectclasses.dex
to analyze. -
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
-
change the default port in “Debugger/Process options” to any other value.
-
start the Dalvik debugger and wait until breakpoint is hit.
Then prepare the second IDA instance:
-
prepare to debug native ARM Android application (copy and start
android_server
and so on). -
load
hellojni.apk
into IDA, and now selectlib/armeabi-v7a/libhello-jni.so
to analyze. -
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
-
select “Remote ARM Linux/Android debugger” and attach to the application process.
-
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.