Reverse engineering

A Guide to Debugging Android Binaries

M G
March 10, 2014 by
M G

In this paper, I'll describe how to start reverse code engineering in Android devices. In this tutorial, you'll learn:

  1. Installation & configuration of Android Virtual Device.
  2. How to build your debugging environment.
  3. Short ARM assembly description.
  4. Debugging with GDB inside your Android device.
  5. Remote Debugging using gdbserver.
  6. Remote debugging using IDA.

1. Installation & configuration of Android Virtual Device

The first thing to do is to download Android SDK and NDK. We will use GDB, other binutils, and also GCC and LD cross compiling chains. The cross compilers are able to compile binaries for other architectures. In our case, we want to compile ARM binaries from x64 architecture, since we are working on Linux x86_64, and we want to compile binaries for ARM android, so we have to use them.

If you are curious about how to build these cross compiling chain tools,here are the commands:

# wget -c ftp://ftp.gnu.org/gnu/binutils/binutils-2.11.2.tar.gz

# tar xvf binutils-2.11.2.tar.gz

# cd binutils-2.11.2

# ./configure –target=arm-linux

In our case, we don't need to do this, as NDK contains all the things we need. Let's go back and download Android SDK and Android NDK from here:

# https://developer.android.com/tools/sdk/ndk/index.html
# https://developer.android.com/sdk/index.html

or we can download the pre-compiled arm-linux-gnueab- toolchain .

tarball these file in /opt/ and then add its path in $PATH variable environment.

After that we have to install and configure an Android Virtual Device (AVD).This is where our binaries will run.

Type :

# android avd

Click to New :

Click OK, then start your Virtual Device. However, we don't need its grapical user interface, we will connect to it using shell. AVD gives as a rooted device, so we can do everything, which will be great when we debug Linux internals and keep tracking syscalls.

Once you click on the start button, your Virtual Device appears like this:

In my case, I used Android 4.2 as a target and Nexus 7 as device,though there is nothing wrong with using other targets or devices.

Let's run our device shell:

[plain]

$ adb shell

# id

uid=0(root) gid=0(root)

[/plain]

2.How to build your debugging environment

Nothing's new here, just mentionning that we will show three ways to debug an Android binary. The first one is to put GDB inside the device and start debugging as you are on a Linux box, thus we can keep track of several things like symbols, GOT, linked libraries, etc.

The second one is by using gdbserver and opening a port in the device and forwarding it to an external port to gain access into the device using a GDB client.

The third is debugging with IDA Pro.

Debugging Android binaries without understanding ARM Assembly is worthless. We will show in the next chapter some basic stuff in ARM Assembly.

3.Short ARM description

Personally, I like ARM assembly because it's very easy to learn and dive into its programming. We will show some basic instructions and conventions:

3.1.Registers

ARM Assembly has 16 registers. Some of them are for function arguments, others for local variables, program counter, stack pointer, and other registers.

R0 to R3 : for function arguments. Alternative names are a0-a3.

R4 to R9 : for local variables.

R7 : almost holds the syscall number.

R10 (sl) : Stack Limit.

R11 (fp) : Fame Pointer.

R12 (ip) : Intra Procedure.

R13 (sp) : used as Stack Pointer like RSP in x86_64.

R14 (lr) : Link Register.

R15 (pc) : Program Counter (like RIP in x86_64 & EIP in x86).

3.2.Branching

Branching instructions are used when the program needs some loops, procedures and functions. The behaviour of the calling function in ARM is different from x86 assembly .

Here are the basic branching instructions:

B Branch

BL Branch with Link

BX Branch with Exchange

BLX Branch with Link and Exchange

The B (Branch) doesn't affect LR. That means if we jump to a subroutine we don't have any traceback for where we were. It's like JMP instruction in x86 assembly.

The BL (Branch with Link) instruction makes a subroutine call by storing PC-4 in LR of the current place, and to return from subroutine, we simply need to restore PC from LR like: mov pc,lr.

BX and BLX instructions are used in THUMB MODE which we don't dive into in this part.

3.3.Data Processing

As we know, ARM is a LOAD/STORE architecture it contains 4 main instructions classes:

- Arithmetic operations:

ADD op1+op2

ADC op1+op2+carry

SUB op1-op2+carry-1

syntax : <operation> {<cond>}{S} Rd,Rn,operand

examples :

ADD r0,r1,r2
SUB R1,R2,#1

- Comparison:

CMP op1-op2

TST op1 & op2

TEQ op1 ^ op2

By the way, the results of these operations are not written.

Syntax : <operation> {<cond>} Rn,Op

examples :

CMP R0,R1
CMP R0,#2

- Logical operations:

AND op1,op2

EOR op1,op2

ORR op1,op2

- Data movement between registers:

MOV op1,op2
syntax : <Operation>{<cond>}{S} Rn, Op2

Examples:
MOV r0, r1

We have shown some basic ARM instructions and as we said, it is easy to learn by practicing with some small examples.

4.Debugging with GDB inside your Android device

You should download GDB ARM version statically linked. After we have GDB for arm targets, we have to push it on the Android device.

# adb push ~/Bureau/arm-gdb /data

So we make a small ARM binary as an example inside the device and we'll keep track of its behaviour:

[c]

#include <stdio.h>

#include <string.h>

int main(int argc,char **argv)

{

char buf[16];

if(argc < 2)

return -1;

strcpy(buf,argv[1]);

printf("Hello : %s n",buf);

return 0;

}

[/c]

Let's compile it:

# arm-linux-gnueabi-gcc -o s s.c -static -zexecstack -fno-stack-protector

# adb shell

root@generic:/ # /data/gdb -q /data/s

WARNING: generic atexit() called from legacy shared library

Reading symbols from /data/s...(no debugging symbols found)...done.

[c]

(gdb) disas main

Dump of assembler code for function main:

0x00008c24 <+0>: push {r7, lr}

0x00008c26 <+2>: sub sp, #24

0x00008c28 <+4>: add r7, sp, #0

0x00008c2a <+6>: str r0, [r7, #4]

0x00008c2c <+8>: str r1, [r7, #0]

0x00008c2e <+10>: ldr r3, [r7, #4]

0x00008c30 <+12>: cmp r3, #1

0x00008c32 <+14>: bgt.n 0x8c3a <main+22>

0x00008c34 <+16>: mov.w r3, #4294967295

0x00008c38 <+20>: b.n 0x8c5e <main+58>

0x00008c3a <+22>: ldr r3, [r7, #0]

0x00008c3c <+24>: add.w r3, r3, #4

0x00008c40 <+28>: ldr r3, [r3, #0]

0x00008c42 <+30>: add.w r2, r7, #8

0x00008c46 <+34>: mov r0, r2

0x00008c48 <+36>: mov r1, r3

0x00008c4a <+38>: blx 0x12e00 <strcpy>

0x00008c4e <+42>: movw r0, #51124 ; 0xc7b4

0x00008c52 <+46>: movt r0, #6

0x00008c56 <+50>: blx 0x99a8 <puts>

0x00008c5a <+54>: mov.w r3, #0

0x00008c5e <+58>: mov r0, r3

0x00008c60 <+60>: add.w r7, r7, #24

0x00008c64 <+64>: mov sp, r7

0x00008c66 <+66>: pop {r7, pc}

End of assembler dump.

(gdb) r aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Starting program: /data/s aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Program received signal SIGSEGV, Segmentation fault.

0x61616160 in ?? ()

[/c]

5.Remote debugging using gdbserver

This is another cool way to debug outside your device , so we copy gdbserver into /data directory or whatever you want. In the latest Android version, gdbserver has been included by default. Next, you choose between either attaching to a running process or executing a new process.

The first thing we should do is port forwarding:

[c]

adb forward tcp:<PC port> tcp:<device port>.

[/c]

Example:

[c]

# adb forward tcp:1234 tcp:1234

# adb shell

[/c]

We choose any running process:

[c]

root@generic:/ # gdbserver :1234 --attach 1436

Attached; pid = 1436

Listening on port 1234

Remote debugging from host 127.0.0.1

[/c]

In our box we use:

[c]

# cd /android-ndk-r9/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin # ./arm-linux-androideabi-gdb

GNU gdb (GDB) 7.3.1-gg2

Copyright (C) 2011 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law. Type "show copying"

and "show warranty" for details.

This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".

For bug reporting instructions, please see:

<http://source.android.com/source/report-bugs.html>.

(gdb) target remote :1234

Remote debugging using :1234

Remote communication error. Target disconnected.: Connection reset by peer.

(gdb)

(gdb) target remote :1234

Remote debugging using :1234

0xb6eb1f9c in ?? ()

(gdb) x/10i $pc

Cannot access memory at address 0x5b6

=> 0xb6eb1f9c: svc 0x00000000

0xb6eb1fa0: mov r7, r12

0xb6eb1fa4: cmn r0, #4096 ; 0x1000

0xb6eb1fa8: bxls lr

0xb6eb1fac: rsb r0, r0, #0

0xb6eb1fb0: b 0xb6ecdb28

0xb6eb1fb4: mov r12, r7

0xb6eb1fb8: mov r7, #174 ; 0xae

0xb6eb1fbc: svc 0x00000000

0xb6eb1fc0: mov r7, r12

(gdb)

[/c]

6. Remote debugging using IDA Pro:

This is like gdbserver, but we should push android_server in the device machine.

[c]

# adb push android_server /data

[/c]

In IDA go to Debbugger → Remote ARMLinux / Android server debugger, and set the host and the port with the path of the application.

That's all, we have described all the techniques. Moreover, I made this tutorial practical and as a reference for those of you who are interested.