Android App Penetration Testing & Frida Hooks

Frida Memory Dumping 101: Extracting Sensitive Android App Data Step-by-Step

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android Memory Dumping with Frida

In the realm of mobile application penetration testing, understanding and manipulating an app’s memory space is a critical skill. Sensitive data, such as API keys, session tokens, user credentials, encryption keys, and other proprietary information, often resides unencrypted in memory at various points during an application’s lifecycle. Memory dumping allows security researchers and pentesters to extract these in-memory artifacts directly. While traditional methods involve using `procdump` or `gdb`, Frida, the dynamic instrumentation toolkit, offers a powerful and flexible approach to interact with and dump an application’s memory on Android devices.

This expert-level guide will walk you through the process of setting up Frida, identifying interesting memory regions, and programmatically dumping their contents. By the end, you’ll have a robust methodology for extracting sensitive data from Android applications.

Prerequisites and Setup

Before diving into memory dumping, ensure you have the following setup:

  • Rooted Android Device or Emulator: Frida requires elevated privileges to attach to processes and perform memory operations.
  • ADB (Android Debug Bridge): Installed and configured on your host machine to communicate with the Android device.
  • Frida-Server: Download the appropriate `frida-server` binary for your device’s architecture (e.g., `arm64`, `x86_64`) from the Frida releases page. Push it to your device, make it executable, and run it.
  • Frida-Tools: Installed on your host machine (`pip install frida-tools`).
  • Basic JavaScript Knowledge: Frida scripts are written in JavaScript.

Frida Server Setup:

1. Download `frida-server-*-android-arm64` (or your device’s architecture) from Frida’s GitHub releases.

2. Push it to your device:

adb push frida-server-*-android-arm64 /data/local/tmp/frida-server

3. Make it executable and run it:

adb shell "chmod +x /data/local/tmp/frida-server && /data/local/tmp/frida-server &"

Identifying Target Memory Regions

The first step in effective memory dumping is to understand which parts of an application’s memory might contain valuable data. On Linux-based systems like Android, the `/proc/[PID]/maps` file provides a map of the process’s virtual memory regions.

Inspecting `/proc/[PID]/maps`

The `maps` file details each memory region, its permissions (read, write, execute), offset, and the file or device it’s mapped from. We’re primarily interested in regions with write permissions (`rw-`) as these often correspond to heap allocations, stack frames, and other dynamic data structures where application-specific data is stored.

1. Find the Package ID (PID) of your target application:

adb shell pidof com.example.targetapp
# Example output: 12345

2. Inspect its memory map:

adb shell cat /proc/12345/maps

You’ll see output similar to this, detailing address ranges, permissions, and what’s mapped:

00a00000-00a21000 rw-p 00000000 00:00 0          [heap]
00a21000-00e21000 rw-p 00000000 00:00 0          [anon:libc_malloc]
7000000000-7000100000 rw-p 00000000 00:00 0          [anon:dalvik-main space]
... (many more lines)

Frida’s `Process.enumerateRanges`

Manually parsing `/proc/[PID]/maps` can be tedious. Frida provides a much more programmatic way to enumerate memory regions using `Process.enumerateRanges()`. This function allows you to filter regions based on their protection (read, write, execute).

Let’s create a basic Frida script to list writable memory regions:

/* enumerate_writable_ranges.js */
console.log("Enumerating writable memory ranges...");

Process.enumerateRanges('rw-').forEach(function(range) {
    console.log(`Base: ${range.base}, Size: ${range.size}, Protection: ${range.protection}, File: ${range.file ? range.file.path : '[anonymous]'}`);
});

console.log("Done.");

To run this script against an application (replace `com.example.targetapp` with your actual target):

frida -U -l enumerate_writable_ranges.js com.example.targetapp

This will output a list of all readable and writable memory regions, giving you a better idea of where to focus your dumping efforts. Pay close attention to regions marked as `[heap]`, `[anon:libc_malloc]`, or `[anon:dalvik-main space]`, as these are common locations for dynamic data.

The Core: Dumping Memory with Frida

Once you’ve identified a promising memory region (or simply want to dump all writable regions), Frida’s `Memory.readByteArray()` function comes into play. This function reads a specified number of bytes from a given memory address.

Building the Dumping Script

We’ll combine memory enumeration with `readByteArray()` and Frida’s `File` API to dump the contents of these regions to files on the Android device.

/* frida_memory_dump.js */

function dumpMemoryRegion(address, size, filename) {
    try {
        console.log(`Attempting to dump ${size} bytes from ${address} to /data/local/tmp/${filename}`);
        let data = Memory.readByteArray(address, size);
        if (data) {
            let file = new File(`/data/local/tmp/${filename}`, "wb");
            file.write(data);
            file.close();
            console.log(`Successfully dumped ${size} bytes from ${address} to ${filename}`);
        } else {
            console.warn(`Failed to read data from ${address}.`);
        }
    } catch (e) {
        console.error(`Error dumping ${address}: ${e.message}`);
    }
}

function startMemoryDump() {
    const DUMP_THRESHOLD_MB = 10; // Only dump regions smaller than this to avoid massive files
    const DUMP_PATH = '/data/local/tmp/';

    console.log("Starting memory dump for writable regions...");

    Process.enumerateRanges('rw-').forEach(function(range) {
        // Filter out excessively large regions or system-level regions if needed
        if (range.size > 0 && range.size < DUMP_THRESHOLD_MB * 1024 * 1024) {
            let filename = `dump_${range.base.toString().substr(2)}_${range.size}.bin`;
            dumpMemoryRegion(range.base, range.size, filename);
        } else {
            console.log(`Skipping large or empty region: Base: ${range.base}, Size: ${range.size}`);
        }
    });

    console.log("Memory dump finished. Files are in " + DUMP_PATH);
    console.log("Use 'adb pull /data/local/tmp/dump_*.bin .' to retrieve them.");
}

// We can call startMemoryDump directly, or hook into a specific function
// if you want to dump memory at a particular point in the app's execution.
// For a general dump on app startup, run it directly.
setImmediate(startMemoryDump);

// Alternatively, if you want to trigger it from the console after attaching:
// rpc.exports = {
//     dump: startMemoryDump
// };

Executing the Dump and Retrieval

1. Save the above script as `frida_memory_dump.js`.

2. Run Frida, injecting your script into the target application. Ensure the app is running or Frida will launch it for you (`-f` flag):

frida -U -f com.example.targetapp -l frida_memory_dump.js --no-pause

3. Monitor the console output for success or error messages. Frida will create `.bin` files in `/data/local/tmp/` on your Android device.

4. After the script completes, pull the dumped files to your host machine:

adb pull /data/local/tmp/ .

This command will pull all files from `/data/local/tmp/` to your current directory. You can be more specific with `adb pull /data/local/tmp/dump_*.bin .`.

Analyzing the Dumped Data

Once you have the `.bin` files, you can use various tools for analysis:

  • `strings` command: Often the quickest way to find plaintext strings within binary data. `strings dump_*.bin | grep “sensitive_keyword”`
  • Hex Editor: Tools like HxD, 010 Editor, or even `xxd` (Linux command-line) can help you visually inspect the raw bytes and identify patterns.
  • Grep: For searching specific byte sequences or patterns.

Look for common indicators of sensitive data: API keys (e.g., “AIza”), URLs, JSON structures, session IDs, user credentials, hardcoded secrets, or anything that looks like plain text in an unexpected place.

Advanced Techniques and Considerations

Hooking Memory Allocation Functions

Instead of just dumping static regions, you can hook functions like `malloc`, `calloc`, `new`, or `mmap` to inspect buffers as they are allocated. This is particularly useful for dynamically generated or transient data. You can then dump specific buffers of interest when they are created or modified.

// Example: Hooking malloc to log allocations (simplified)
Interceptor.attach(Module.findExportByName(null, 'malloc'), {
    onEnter: function(args) {
        this.size = args[0].toInt32();
    },
    onLeave: function(retval) {
        if (this.size > 0 && this.size < 1024) { // Small allocations
            // console.log(`malloc(${this.size}) returned ${retval}`);
            // You could dump this small buffer here if it's interesting
            // dumpMemoryRegion(retval, this.size, `malloc_dump_${retval}.bin`);
        }
    }
});

Context-Aware Dumping

For more targeted analysis, you can integrate memory dumping with function hooking. For instance, if you suspect a particular function handles sensitive data (e.g., `decryptPassword`, `sendApiKey`), you can hook that function and dump memory regions (parameters, return values, nearby heap allocations) only when that function is executed.

Dealing with Large Dumps and Performance

Dumping entire memory spaces can generate very large files and be slow. Refine your `Process.enumerateRanges` filters to exclude irrelevant regions (e.g., read-only code sections, very large anonymous regions that are likely just large internal buffers). You can also introduce logic to sample dumps or dump only regions matching specific criteria (e.g., containing certain string patterns after a quick read).

Obfuscation and Encryption

Remember that data found in memory might still be obfuscated or encrypted. In such cases, memory dumping serves as a first step. You may then need to identify decryption routines and their keys by hooking relevant cryptographic APIs (e.g., `Cipher.init`, `KeyFactory.generateSecret`) and extracting the keys at runtime.

Ethical Considerations

Memory dumping is a powerful technique. Always ensure you have explicit authorization before performing such actions on any system or application. Unauthorized access and data extraction can have severe legal consequences. This guide is for educational and authorized security testing purposes only.

Conclusion

Frida provides an unparalleled level of control and insight into Android application runtime. By mastering memory enumeration and dumping techniques, you equip yourself with a crucial capability for identifying and extracting sensitive data that might otherwise remain hidden. This step-by-step guide offers a robust foundation, and by combining these techniques with targeted function hooking, you can unlock even deeper levels of application security analysis. Happy hunting!

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