Hacking Android Apps Through Exposed Components

by Tal Melamed

In almost every Android application, developers expose activities without sufficient protections. Exposing activities can lead to various attacks. For example, an attacker or a malicious app installed on the same device, can call those exposed activities to invoke internal pages of the application. Calling internal pages puts the application at risk of phishing by manipulating users to enter details in the phishing app, as well as exposing a user to secret pages, such as admin panels or pages which should have been visible to paid/pro user only.

In this article I will explain the use of activities and their risky configuration on the one-side, and how you can take advantage of exposed activities into hacking the Android app, using Android Activity Manager (AM) over the Android Debug Bridge (ADB) and using Drozer, a security and attack framework for Android.

But let’s start from the beginning and see how things work behind the scenes. What is an Android activity?
The Activity class is a crucial component of any Android app, and the way activities are launched and put together is a fundamental part of the platform’s application model”, – developer.android.com

While most programming languages using the main() function to start a program, the Android system initiates code in an Activity instance by invoking specific callback methods that correspond to specific stages of its lifecycle. To use an activity, it must be declared in the manifest as follows:

<manifest ... >
 <application ... >
 <activity android:name=".ExampleActivity" />
 ...
 </application ... >
 ...
</manifest >

While the only required attribute is android:name, which specifies the class name of the activity, other configured attributes are those exposing the activity to security risks. Why? Usually, the activities are called using Intents. An Intent is a messaging object that can be used to start an activity, passing an Intent to startActivity(), start a service and request an action from another app component by delivering a broadcast.

As mentioned earlier, activities expose the app to two main risks. The first one has nothing to do with their configuration and is more of a design issue. A user with root permissions (i.e. ADB) on the device can invoke any activity, simply by sending an intent to the desired activity. For example:

$ adb shell am start -n <package_name>/<activity_name>

How do we know what we should call? For that we should look at the manifest, and look for the activities declarations, as described above. So, we stumbled upon an android app that requires authentication immediately when launched (figure 1). What do we do now?

figure 1.

First, we always look at the manifest. The following screenshot is part of the app’s manifest:

figure 2.

As we can see in figure 2, the lower part of it is filled with activities. Mostly, each activity represents a page within the application. One activity that drew our attention is the Admin activity. We shall call it and see what we get:

figure 3.

Calling the Admin activity led us to the admin panel without providing any credentials. This is equivalent to calling the admin.html page on a web application. Whether we can see sensitive data or perform unauthorized actions that up to the developers and the design of the application. Of course, if a session/token is required in order to pull the data from the server, and every action is authorized against that session/token, then exposing internal pages is not a great risk. But as we know, not all apps have the server-side to protect them and some of them are standalone, client-side only, apps. In that case, there is little that can be done to protect them. But there are measures that can be taken to make them harder to attack. Encrypting data, obfuscating the code and implementing root-detection code in the app might make the time required to bypass it all not worthwhile for the potential attacker. But protecting the app against the owner of the device is almost impossible without a server-side and is only a matter of time.

The greater risk, however, is exposing the app to other applications/malware installed on the device.  To allow apps to communicate with each other, the developers can explicitly expose the activity, by setting the android:exported attribute to true, as can be seen in the AcHistory and Transfer activities in the figure 2. This means that any app installed on the device can send an intent to launch the exported activity.

Why is that a risk? That depends on what the app is doing with the received intent. To understand that, we have to first understand how the activity class works. The Activity class defines several callback functions that are being called upon pre-defined events; The first callback event is – onCreate(); this function is called when the activity is first created.

Let’s decompile the app and look at the implementation of the exported activity. As mentioned, the first part that will be called is the onCreate() function:

figure 4.

I marked the interesting code line in figure 4. The getIntent().getExtras(); is a method that is used to get the arguments sent as part of the intent. We can see that the developer checks if the intent is not null and then assigns some string variable with the value of the argument uid. But, did we send the uid argument? A malicious app can send an intent to the app with another argument. Since the activity is exported, it can be called by any app, with or without root permissions.

To see what happens, lets invoke the activity with a random argument (extra, using the “-e”)

$ adb shell am start -n com.appsec.hackmepal/.AcHistory -e string hack 1

figure 5.

The result – the app crashed. The developer tried to assign and read null argument, which caused the app to stop working (see figure 6). If the malicious app will constantly send the malicious intent, the app will never work. What could be the motivation for such an attack? market competition, ransom and phishing are only a few reasons for such behavior.

figure 6.

But most apps are exposed where least expected. Activities are not only exposed when explicitly setting the andoird:exported=”true” attribute. Although the default configuration is that the activity is not exported, when setting an to an activity, it automatically becomes exported.

Intent filter specifies the types of intents that an activity, service, or broadcast receiver can respond to. An intent filter declares the capabilities of its parent component — what an activity or service can do and what types of broadcasts a receiver can handle. It opens the component to receiving intents of the advertised type, while filtering out those that are not meaningful for the component.” – developer.android.com

The intent-filters are configured in the manifest:

figure 7.

As mentioned, the configured for the broadcast receiver in figure 7 sets the receiver as exported, which means that it can be called by any app installed on the device. Broadcast receivers work a little differently to activities, but the attack surface is the same. The first function called when a broadcast is received is onReceive(). Decompiling the app will allow us to take a peek at the implementation:

figure 8.

As simple as that, the receiver is looking for three extras (arguments) in the intent: uid, refuid and amount (see figure 8) and then calling the execution function (TransferWithRest). We can assume from the context that these are the from (uid), to (refuid), and sum (amount) of a transfer. As mentioned earlier, due to the intent-filter configured in the manifests, this receiver can be called by any app installed on the device. So an attack could be stealing money from the logged-in user. In figure 9, we can see that the current user (appsec) has $100. Let’s see if we can steal his money:

figure 9.

Sending the following intent will transfer $50 from uid appsec to refuid test. Since we run in the context of the user (appsec), the app will automatically extract the session/token to authorized the transfer in the server:

$ adb shell am broadcast -n com.appsec.hackmepal/.BrReciever -a android.intent.action.ACTION_SEND -e uid appsec -e refuid test -e amount 100

figure 10.

Looking back at the account, will show the new balance set to $50 (figure 11):

figure 11.

So far we did it all manually, extracting and investigating the app manifest to locate insecure configurations and decompiling the app to review the code and find vulnerable points. The following section demonstrate how we can use Drozer, a semi-automatic framework  developed by MWR InfoSecurity, to locate and attack exposed components, as described in the article.

Drozer uses an agent (APK) which is installed on the device and provides a shell for executing the commands. First, download drozer agent from MWR Labs website and install it on the tested device/emulator. After installing the agent, launch the app and turn-on the agent (see figure 12):

figure 12.

After that, set up a port-forward so that your PC can connect to a TCP socket opened by the Agent inside the emulator, or on the device:

$ adb forward tcp:31415 tcp:31415

Now, we can launch drozer shell:

$ drozer console connect

figure 13.

Drozer gives us the ability to easily retrieve package information, identify attack surfaces, launch activities, interact with service and read content from content providers. To get the list of possible commands (figure 14), run the list command:

figure 14.

Now, let’s see how it works. To gain basic information account a package (figure 15):

run.app.package.info –a <package_name>

figure 15.

To identify possible attack surface drozer enumerates exported components (figure 16):

figure 16.

To get more information about the exported activities, we can use (figure 17):

figure 17.

Executing the following command will launch an activity (figure 18):

figure 18.

In case we want to send an intent with extras (arguments), we can use (figure 19):

figure 19.

So let’s take the last $50 from appsec, only this time using Drozer:

figure 20.

The money was transferred without even launching the app (figure 20). Of course, launching the app and going to the account details will reveal the unfortunate for appsec, only $1 is left in his account (figure 21):

figure 21.

There are different ways to exploit exposed components such as activities, content providers and broadcast receivers. As always, it’s in the hands of the developers to prevent such attacks. What can we do to protect? First, we configure the components in a secured manner:

Unless explicitly required – disable external exposure of any components by specifying android:exported=”false” in the application manifest (figure 22). If a component has an intent-filter (e.g. figure 7), by doing so, the component is no longer exposed to other apps.

Another way to protect components is to require permissions when calling them, by declaring the android:permission in the manifest, under the desired component. The name of a permission that sending party must have in order to send an intent to the receiving app. This will be enforced by Android; if the attribute is not set, the receiver is not protected by a permission. To declare the permission in the manifest (figure 22):

figure 22.

In case InterProcess communication is required between your own separated apps that are signed with the same key, it is preferable to use “signature” level permission in the android:protectionLevel.

After we configured the manifest as descried above, we still need to review our code. To avoid crashes, do not assume to have received the required arguments; verify before you assign them. For example:

Bundle extras = getIntent().getExtras();
if (extras == null) {
   return;
}
String user = extras.getStringExtra("user");
// use either one of the following lines to validate the desired argument
if ( getIntent().getStringExtra(user) == null ) {
   return;
}
// or
if ( getIntent().hasExtra(user) ) {   
   //...
}

All the above example, including Drozer, can be found in AppUse; a Virtual Machine developed by AppSec Labs. It is a unique platform for mobile application security testing in the Android environment, and includes exclusive custom-made tools created by AppSec Labs. AppUse can be downloaded free from: https://appsec-labs.com/appuse/