Android Stack Overflow Exploitation (ARMv7) - MobileHackingLab

In this post, I will be walking through the solution to an Android Stack Overflow vulnerability while keeping security mechanisms enabled. The twist? I won’t be using Frida to bypass protections this time! 😉

Let’s dive right in and break things down step by step.

Initial Setup and Analysis

I’ve downloaded the target application and begun my analysis by reviewing the Java code. Since we’re dealing with ARM architecture, our approach will focus on analyzing and exploiting the vulnerability within that context.

Setting Up the AVD (Android Virtual Device)

To start, I installed and ran the app on a Pixel 3a device using API 25 on Android Studio’s AVD. This setup will allow us to simulate and observe the app’s behavior in a controlled environment.

Let’s see how the app looks and functions within this AVD before we dive deeper into the vulnerability analysis.

jadx-initialize

Application Overview

It appears that the application is a Server/Client Chat Messaging Application running on port 6000, as indicated by the line:

public static final int SERVERPORT = 6000;

The app also loads a native binary library called “native-lib”, which contains three interesting functions:

  • leakMemory()
  • overFlow()
  • stringFromJNI()

These functions are defined in the following snippet:

public native String leakMemory(byte[] bArr);

public native void overFlow(byte[] bArr, int i);

public native String stringFromJNI();

static {
    System.loadLibrary("native-lib");
}

Vulnerabilities in Native Functions

Upon further inspection, we can see that the memoryLeak() function is vulnerable to a Format String vulnerability, while the overFlow() function leverages the cp() function, which is vulnerable to a Stack Buffer Overflow.

ghidra-decompiled

Here’s the vulnerable cp() function:

/* cp(char const*, int) */
void cp(char *param_1, int param_2) {
  undefined auStack_d8[200];
  int local_10;
  char *local_c;

  local_10 = param_2;
  local_c = param_1;
  __aeabi_memclr(auStack_d8, 200);
  
  if (0 < local_10) {
    __aeabi_memcpy(auStack_d8, local_c, local_10);
  }
  return;
}

The buffer auStack_d8 has a fixed size of 200 bytes, and the function copies data into it without sufficient boundary checks, making it vulnerable to Stack Buffer Overflow when param_2 exceeds the buffer size.

Analyzing Behavior with Frida

To better understand the behavior, we can connect to port 6000, send some crafted data, and observe how the application responds. While doing this, I used Frida to trace which functions are called and in what order.

Steps to Run the Application and Attach Frida

First, launch the application via ADB:

adb shell am start -n com.example.mynativetest/.MainActivity

Next, execute the Frida server on the target device:

/data/local/tmp/frida-server-16.4.10-android-arm

Finally, attach to the application using Frida-Trace to monitor native function calls:

frida-trace -U -i "Java_*" mynativetest

At this point, we can start sending data to the server on port 6000 and monitor how the vulnerable functions (memoryLeak() and cp()) behave in real-time.

With Frida-Trace now ready to instrument the called functions, the next step is to connect to the server on port 6000. To make things easier for local analysis, I performed a little trick by using ADB port forwarding to map the server’s port to my localhost.

Here’s the command to forward port 6000 from the device to localhost:

adb forward tcp:6000 tcp:6000

By doing this, we can interact with the application locally as if it were running on the same machine, allowing us to send and receive data directly through localhost:6000.

frida-trace

Testing for Vulnerabilities

Now, it’s time to conduct some tests to confirm the vulnerabilities in the application. I will use %x as input to examine how snprintf behaves and to verify if it is indeed vulnerable to format string attacks.

Verifying Format String Vulnerability

By sending this input, we can observe how the application handles formatted data and if any unintended information is leaked, indicating a format string vulnerability.

memoryLeak Format String

Verifying Stack Overflow Vulnerability

Having confirmed the format string vulnerability, the next step is to check if the application is also vulnerable to stack overflow.

To do this, I will send a payload that exceeds the buffer size in the cp() function. By crafting an input larger than the 200-byte limit, we can observe if the application crashes or behaves unexpectedly, indicating a successful stack overflow.

Let’s proceed with this test and analyze the results.

overFlow Test

Analyzing Security Features

To understand the security posture of the application, I ran a check on the native library libnative-lib.so to see what kind of security mechanisms are in place. Here are the results:

$ checksec libnative-lib.so
[*] '/home/kousha/Desktop/tools/frida/bof/blog-files/apktool-out/lib/armeabi-v7a/libnative-lib.so'
    Arch:     arm-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Security Features Explained

  • Arch: arm-32-little

    • This indicates that the architecture of the library is ARM 32-bit, which is commonly used in Android devices.
  • RELRO (Read-Only Relocations): Full RELRO

    • This means that the library uses full RELRO, making it harder for attackers to exploit memory corruption vulnerabilities related to Global Offset Table (GOT) entries. It prevents modifications to the GOT during runtime.
  • Stack Protection: Canary found

    • The presence of a stack canary indicates that the application employs stack protection. This mechanism helps to detect stack buffer overflows by placing a known value (the canary) before the return address on the stack. If this value is altered during a buffer overflow, the program can detect the attack and terminate.
  • NX (No eXecute): NX enabled

    • NX (or DEP - Data Execution Prevention) prevents execution of code in certain regions of memory (like the stack). This mitigates the risk of executing shellcode injected through a buffer overflow.
  • PIE (Position Independent Executable): PIE enabled

    • PIE allows the application to be loaded at random addresses in memory, making it more difficult for an attacker to predict the location of specific functions or buffers, thus enhancing security against certain types of exploits.

These security features indicate a robust defense against common vulnerabilities, but the presence of vulnerabilities like format string and potential stack overflow suggests that additional care must be taken to secure the application.

Exploit Development: A Step-by-Step Approach

I developed an exploit using the pwntools library to target the vulnerabilities identified in the Android application running on port 6000. The approach involved the following steps:

  1. Establishing Connection

    • I started by establishing a connection to the application using a client-server model. The application was running locally after forwarding port 6000, which allowed me to communicate with the vulnerable server.
  2. Leaking the Stack Address

    • Using the format string vulnerability, I was able to leak a stack address. This provided insight into the memory layout and helped me identify key memory addresses necessary for building the exploit.
  3. Leaking the Libc Address

    • Next, I used the same format string vulnerability to leak a libc address. The libc base address is crucial for calculating the locations of important functions like system(), which I used later in the exploit.
  4. Calculating Gadgets and System Call

    • Once the libc base address was identified, I calculated the offsets for useful gadgets, such as pop r0, pc, and determined the address for the system() function. Additionally, I prepared the arguments for system() to execute a reverse shell.
  5. Crafting the Exploit Payload

    • I then crafted the payload to exploit the stack buffer overflow vulnerability. This payload included the necessary padding, followed by the memory addresses of the gadgets and the system() call, along with the arguments.
  6. Executing the Exploit

    • Finally, I sent the crafted payload to the server, triggering the overflow and executing the reverse shell. I maintained an interactive session with the target, gaining full control over the device.

This approach effectively exploited both the format string vulnerability and the stack buffer overflow, leading to full remote code execution on the vulnerable application.

1. Establishing Connection

For the first step, we are going to connect to port 6000 using pwntools. This allows us to interact with the vulnerable application running on the server. Here’s the code snippet for establishing the connection and receiving the welcome message:

from pwn import *

conn = remote('localhost', 6000)

welcome_message = conn.recvuntil(b"Welcome to Damn Exploitable Android App!")
print(f"Received: {welcome_message.decode()}")

In this code:

  • remote('localhost', 6000) establishes the connection to the application running on port 6000.
  • We use recvuntil to receive and display the welcome message, confirming that the connection is successful and ready for further interaction.

This sets up the initial communication with the server before moving on to exploiting the vulnerabilities.

2. Leaking the Stack Address

In the second step, the goal is to leak an address from the stack region. Why is this important? Leaking a stack address helps us locate the top of the stack, which will be crucial later when pointing to the arguments for the system() call.

Why Bother with This?

We need to perform this stack leak because modern security mechanisms, like stack canaries, NX, and RELRO, make it difficult to exploit vulnerabilities directly. These protections are designed to prevent traditional exploitation methods. However, by leaking memory through the format string vulnerability, we can gather enough information to bypass these defenses and continue with our exploit.

In short, the memory leak helps us bypass the security features that are otherwise preventing direct exploitation, allowing us to control execution flow with precision.