Android App Penetration Testing & Frida Hooks

Frida Lab: Unpacking & Dumping Android App Secrets from Memory

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Frida for Android Dynamic Analysis

Android application penetration testing often requires more than just static analysis of APK files. While decompilers like Jadx-GUI can reveal a great deal, many critical secrets – such as encryption keys, API tokens, sensitive user data, or even anti-tampering logic – are often generated, manipulated, or loaded into memory only at runtime. This is where Frida, a dynamic instrumentation toolkit, becomes an indispensable tool. Frida allows security researchers to inject custom JavaScript or C-like code into running processes on Android, giving unparalleled control to observe, modify, and extract runtime information. In this lab, we’ll dive deep into using Frida to unpack obscured data and dump sensitive information directly from an Android application’s memory.

Setting Up Your Frida Environment

Prerequisites

Before we begin, ensure you have the following:

  • An Android device or emulator (rooted is preferred, but non-rooted can work with specific methods).
  • Android Debug Bridge (ADB) installed and configured on your host machine.
  • Python 3 installed on your host machine.
  • Frida-tools installed via pip:
pip install frida-tools

Installing Frida Server on Android

The Frida server runs on the target Android device and communicates with the Frida client on your host machine. You need to download the correct server binary for your device’s architecture (e.g., arm64, x86_64).

  1. Visit the Frida releases page and download frida-server-*-android-ARCH (e.g., frida-server-16.1.4-android-arm64).
  2. Push the binary to your device and set execute permissions:
adb push frida-server-16.1.4-android-arm64 /data/local/tmp/frida-serveradb shell "chmod 755 /data/local/tmp/frida-server"
  1. Start the Frida server on the device. For rooted devices, you can run it directly:
adb shell "/data/local/tmp/frida-server &"

For non-rooted devices, you might need to use adb reverse and/or inject into a debuggable app.

Verifying Frida Setup

On your host machine, run frida-ps -U to list running processes on the connected USB device. If successful, you’ll see a list of processes.

frida-ps -U

Basic Java Method Hooking

Let’s start with a fundamental concept: hooking a Java method. This allows us to observe arguments, modify return values, and understand execution flow.

Identifying Target Methods

For this lab, we’ll assume we have an application that uses a Toast message. We’ll use a decompiler like Jadx-GUI to find methods of interest, for instance, android.widget.Toast.makeText.

Writing Your First Frida Script

Create a file named toast_hook.js:

Java.perform(function () {    console.log("Frida script loaded.");    var Toast = Java.use("android.widget.Toast");    Toast.makeText.overload('android.content.Context', 'java.lang.CharSequence', 'int').implementation = function (context, text, duration) {        var message = text.toString();        console.log("Toast Message Detected: " + message);        // You can modify the message here if needed        // var newText = Java.cast(Java.use("java.lang.String").$new("Hooked Message!"), Java.use("java.lang.CharSequence"));        // this.makeText(context, newText, duration);        return this.makeText(context, text, duration);    };    console.log("Toast.makeText hooked.");});

Then, inject this script into your target application (e.g., com.example.myapp):

frida -U -l toast_hook.js -f com.example.myapp --no-pause

When the app tries to display a Toast message, you’ll see it logged in your console.

Unpacking & Dumping Sensitive Data from Memory

Now for the main event: extracting secrets directly from memory. This is crucial when data is dynamically generated, encrypted, or obfuscated at rest.

Dumping Arbitrary Memory Regions

Sometimes, an application loads critical data or libraries into memory that you want to inspect. Frida’s Process.getRangeByName and Memory.readByteArray are perfect for this.

Let’s say a native library, libsecrets.so, is loaded, and we suspect it contains hardcoded keys or uninitialized memory that gets populated with sensitive data. We can dump its entire memory range.

Java.perform(function () {    console.log("Frida script loaded for memory dumping.");    var targetModule = Process.getModuleByName("libsecrets.so");    if (targetModule) {        console.log("Found libsecrets.so at base: " + targetModule.base + ", size: " + targetModule.size);        var moduleBase = targetModule.base;        var moduleSize = targetModule.size;        // Read the entire module memory        var moduleBytes = moduleBase.readByteArray(moduleSize);        // You'd typically save this to a file        // For demonstration, let's just log a small part        console.log("First 16 bytes of libsecrets.so: " + hexdump(moduleBytes.slice(0, 16)));        // In a real scenario, you'd send this to your host machine        // send({ type: 'moduleDump', name: 'libsecrets.so', data: Array.from(new Uint8Array(moduleBytes)) });    } else {        console.log("libsecrets.so not found.");    }});

To run this and capture the output (if you implement the send part):

frida -U -l dump_module.js -f com.example.myapp --no-pause > module_dump.txt

This script logs the base address and size of the library and then reads its entire memory. For larger modules, you’d use send and handle the data on the Python side to write to a file.

Intercepting Crypto Keys and IVs

One of the most valuable uses of Frida is to intercept cryptographic operations and extract keys, IVs, and plaintext/ciphertext. A common target is the javax.crypto.Cipher class.

Java.perform(function () {    console.log("Frida script loaded for crypto key extraction.");    var Cipher = Java.use("javax.crypto.Cipher");    // Hook the init method to get the key and IV    Cipher.init.overload('int', 'java.security.Key').implementation = function (opmode, key) {        console.log("Cipher.init(opmode, key) called!");        console.log("  Operation Mode: " + opmode); // 1 for ENCRYPT_MODE, 2 for DECRYPT_MODE        var secretKey = Java.cast(key, Java.use("javax.crypto.spec.SecretKeySpec"));        if (secretKey) {            var keyBytes = secretKey.getEncoded();            console.log("  Key Algorithm: " + secretKey.getAlgorithm());            console.log("  Key Bytes: " + hexdump(keyBytes));        }        this.init(opmode, key);    };    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (opmode, key, params) {        console.log("Cipher.init(opmode, key, params) called!");        console.log("  Operation Mode: " + opmode);        var secretKey = Java.cast(key, Java.use("javax.crypto.spec.SecretKeySpec"));        if (secretKey) {            var keyBytes = secretKey.getEncoded();            console.log("  Key Algorithm: " + secretKey.getAlgorithm());            console.log("  Key Bytes: " + hexdump(keyBytes));        }        var ivSpec = Java.cast(params, Java.use("javax.crypto.spec.IvParameterSpec"));        if (ivSpec) {            var ivBytes = ivSpec.getIV();            console.log("  IV Bytes: " + hexdump(ivBytes));        }        this.init(opmode, key, params);    };    console.log("javax.crypto.Cipher.init hooked.");});function hexdump(buffer) {    if (!buffer) {        return "";    }    var view = new Uint8Array(buffer);    var hex = '';    for (var i = 0; i < view.length; i++) {        hex += (view[i] < 16 ? '0' : '') + view[i].toString(16);        if ((i + 1) % 16 === 0) {            hex += 'n';        } else if ((i + 1) % 2 === 0) {            hex += ' ';        }    }    return hex.trim(); // Trim any trailing space/newline. Not ideal for large dumps, but good for small parts.    }

Run this script similar to the previous ones. When the application performs encryption or decryption, you will see the keys and IVs used, printed to your console. The hexdump function is a helper to format byte arrays for readability.

Conclusion

Frida empowers penetration testers and security researchers with an unparalleled capability for dynamic analysis of Android applications. From simple method hooking to advanced memory dumping and interception of cryptographic operations, Frida allows us to bypass client-side controls, extract sensitive runtime secrets, and gain a deeper understanding of an application’s internal workings. Mastering these techniques is fundamental for thoroughly assessing the security posture of Android applications and uncovering vulnerabilities that static analysis alone might miss.

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