Android App Penetration Testing & Frida Hooks

Android App Data Exfiltration Lab: Memory Dumping Live Processes with Frida

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android Memory Dumping with Frida

In the realm of Android application penetration testing, gaining access to data residing in an app’s live memory can be a goldmine for uncovering sensitive information. This technique, known as memory dumping, allows security researchers to extract critical data like API keys, session tokens, user credentials, or even encryption keys that are temporarily stored in volatile memory. While traditional forensics often involves static analysis or disk imaging, live memory analysis provides a snapshot of the application’s state during runtime, revealing data that might never be written to persistent storage.

Frida, a dynamic instrumentation toolkit, stands out as an indispensable tool for this purpose. Its powerful JavaScript API allows for injecting custom code into running processes on Android, enabling sophisticated memory manipulation and data exfiltration. This guide will walk you through setting up a lab environment and crafting Frida scripts to effectively dump and analyze the memory of a live Android application.

Prerequisites for Your Exfiltration Lab

Before diving into the technical steps, ensure you have the following prerequisites in place:

  • Rooted Android Device or Emulator: Frida requires root privileges to attach to and instrument most applications.
  • ADB (Android Debug Bridge): Essential for interacting with your Android device from your host machine.
  • Frida-server on Android device: The agent that runs on the Android device, listening for commands from the Frida-tools on your host.
  • Frida-tools on Host Machine (Python): The Python client library for interacting with the Frida-server.
  • Python 3: Required to run the host-side Frida script.

Understanding Android Process Memory Regions

Every running process on an operating system, including Android apps, is allocated its own virtual memory space. This space is divided into various regions, each with specific permissions (read, write, execute) and purposes (code, data, stack, heap). Understanding these regions is crucial for effective memory dumping:

  • Code/Text Segment: Contains the executable instructions of the program and its loaded libraries. Typically read-only and executable (r-x).
  • Data Segment: Stores global and static variables. Can be read-only (initialized data, r--) or read-write (uninitialized data, rw-).
  • Heap: Dynamically allocated memory during runtime (e.g., objects created with new in Java/Kotlin). This is a primary target for sensitive application data and is typically read-write (rw-).
  • Stack: Used for local variables, function call frames, and return addresses. Also dynamically allocated and typically read-write (rw-).

For data exfiltration, we are primarily interested in memory regions with read permissions, particularly r-- and rw-, as these are most likely to contain application data rather than executable code.

Setting Up Frida-Server on Android

1. Download Frida-server

First, identify the architecture of your Android device. Then, download the corresponding Frida-server binary from the official Frida releases page on GitHub.

adb shell getprop ro.product.cpu.abi
# Example output: arm64-v8a
wget https://github.com/frida/frida/releases/download/16.1.4/frida-server-16.1.4-android-arm64 -O frida-server

2. Push to Device and Execute

Push the downloaded frida-server binary to a writable directory on your device (e.g., /data/local/tmp/), set executable permissions, and then run it in the background.

adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"

Identifying the Target Process

Before attaching Frida, you need to know the package name or Process ID (PID) of your target Android application. Ensure the app is running on your device.

adb shell ps -A | grep com.example.targetapp
# Example output (PID is the second column):
# u0_a123   12345 1234  ... com.example.targetapp

Note down the PID (e.g., 12345) or the package name (e.g., com.example.targetapp).

Crafting the Frida Memory Dumping Script

Our memory dumping solution will consist of two parts: a Python host script that manages the Frida connection and saves the dumped data, and a JavaScript payload that Frida injects into the target process to perform the actual memory enumeration and dumping.

1. Python Host Script (frida_dump.py)

Create a Python file named frida_dump.py:

import frida
import sys
import os

# --- CONFIGURATION ---
PACKAGE_NAME = "com.example.targetapp" # Replace with your target app package
PID = 12345 # Or specify PID directly if preferred
OUTPUT_DIR = "memory_dumps"
# ---------------------

def on_message(message, data):
    if message['type'] == 'send':
        print(f"[+] {message['payload']}")
        if data:
            # Sanitize region name for filename
            region_info = message['payload'].split("Dumping region: ")[1].strip()
            filename = os.path.join(OUTPUT_DIR, f"dump_{region_info.replace('/', '_').replace('[', '').replace(']', '').replace(' ', '-')}.bin")
            os.makedirs(OUTPUT_DIR, exist_ok=True)
            with open(filename, "wb") as f:
                f.write(data)
            print(f"    Dumped {len(data)} bytes to {filename}")
    elif message['type'] == 'error':
        print(f"[-] Script Error: {message['description']}n{message['stack']}")

def main():
    try:
        device = frida.get_usb_device(timeout=10) # Connect to USB device
    except frida.TimedOutError:
        print("[-] Device not found. Ensure Frida-server is running and device is connected.")
        sys.exit(1)

    try:
        # Attach to the application by package name or PID
        if PID:
            process = device.attach(PID)
            print(f"[+] Attached to PID {PID} (Name: {process.name})")
        else:
            process = device.attach(PACKAGE_NAME)
            print(f"[+] Attached to {PACKAGE_NAME} (PID: {process.pid})")

    except frida.ProcessNotFoundError:
        print(f"[-] Process '{PACKAGE_NAME}' (or PID {PID}) not found. Is the app running?")
        sys.exit(1)

    # The JavaScript payload for memory enumeration and dumping
    frida_js_code = """
        console.log("[*] Frida payload injected. Enumerating memory regions...");

        // Define a minimum and maximum size for regions to dump (adjust as needed)
        // This helps in filtering out very small or excessively large (e.g., system) regions
        var MIN_DUMP_SIZE = 0x100; // 256 bytes
        var MAX_DUMP_SIZE = 0x20000000; // 512 MB (adjust based on expected memory usage)

        // Iterate over 'r--' (read-only) and 'rw-' (read-write) memory regions
        Process.enumerateRanges('r--').concat(Process.enumerateRanges('rw-')).forEach(function(range) {
            // Filter out system libraries, too small/large regions, and file-backed memory
            // We want to focus on dynamically allocated heap/stack data.
            if (range.size > MIN_DUMP_SIZE && range.size < MAX_DUMP_SIZE && !range.file && !range.base.isNull()) {
                try {
                    var data = range.readByteArray(range.size);
                    if (data) {
                        // Send the region base address and size information, along with the binary data
                        send("Dumping region: " + range.base + "_" + range.size + "_" + range.protection, data);
                    }
                } catch (e) {
                    console.log("[-] Error reading region " + range.base + ": " + e.message);
                }
            }
        });

        console.log("[*] Memory enumeration complete.");
    """

    script = process.create_script(frida_js_code)
    script.on('message', on_message)
    script.load()

    print("[*] Script loaded. Waiting for dumps. Press Ctrl+C to detach.")
    sys.stdin.read() # Keep script running until Ctrl+C
    process.detach()
    print("[*] Detached from process.")

if __name__ == "__main__":
    main()

Before running, ensure you replace PACKAGE_NAME or PID with your target application’s details.

2. Frida JavaScript Payload (Embedded)

The core logic for memory dumping resides within the frida_js_code string in the Python script. This JavaScript executes directly within the target Android application’s process:

  • Process.enumerateRanges('r--').concat(Process.enumerateRanges('rw-')): This crucial Frida API call enumerates all memory regions accessible by the process that have read-only (r--) or read-write (rw-) permissions. These are the most likely candidates for sensitive application data.
  • Filtering Regions: The script includes logic to filter out regions that are too small (e.g., 0x100 bytes) or excessively large (e.g., 0x20000000 bytes), and critically, it ignores file-backed memory (!range.file). This focuses the dump on dynamically allocated memory on the heap and stack, where application secrets are typically found.
  • range.readByteArray(range.size): For each identified region, this function attempts to read its entire content as a byte array.
  • send(

    Android Mobile Specs & Compare Directory

    Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

    Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner