Android Deep Dive - Implicit Intents
# Introduction
From the official
Android documentation, the Intent is described as “an abstract description of an operation to be performed”. Conceptually, it can be simplified as an “intention to do something with another application” across Inter-Process Communication (IPC). One of the most interesting facility that intents offer is the implicit resolution. An application can explicitly declare to handle specific intents (through the <intent-filter> declaration) and these intents are magically delivered to it from other applications, without the knowledge of the final destination package. Since magic can be hypothetically just defined as a form of ignorance (at least in computer science?), let’s see where the “magic” happens in the Android source code!
# Intent registration
# Starting from the beginning
Let’s start from an application point of view that needs to handle specific actions: an <intent-filter> is declared inside the AndroidManifest.xml:
| |
In this example, the component can be of any type: an activity, receiver, service or provider. Some filters are also specified in order to discriminate matching events that the component is interested into: action, category and data (with the android:scheme attribute) are specifically used in this case (check out the
<intent-filter> documentation for more filters and options).
At install time, the
PackageInstaller service is responsible to install the application and all its components, including intent filters. More specifically, diving into the AOSP (Android Open Source Project) codebase, it is possible to identify some key functions that parse all declared components. More specifically, the
ComponentResolver::addAllComponents method calls four methods that parse all components’ details.
| |
Following the Add[Component]Locked logic, components are registered based on their type on specific variables (e.g. mActivities, mProviders, mReceivers and mServices) and then intent filters are parsed. Let’s take the activity parsing as an example to reference some code, but the concept is the same across all different components. addActivitiesLocked calls
mActivities.addActivity (part of the ComponentResolver class) that calls addFilter for each declared intent filter.
| |
Intents are cycled within a for loop and each declared intent filter is passed as an argument to
ComponentResolver::MimeGroupsAwareIntentResolver::addFilter that finally calls
IntentResolver::addFilter where most of the registering process happens. Before diving into the logic of this specific method, it is important to discriminate intent filters as they are internally classified: Full MIME Types, Base MIME Types, Wild MIME Types, Schemes, Non-data Actions, MIME Typed Actions.
# The “obscure”, less-known, internal classification
The “obscure” adjective is a clearly amplification of the concept, but there is an interesting internal intent classification (that influences the consecutive resolution process) that is not explicitly documented in the Android Documentation (that is, for most parts, really complete) and it was possible to identify it by wandering in the codebase, more specifically into the
IntentResolver::dump method reachable from the dumpsys utility (more on that later). These categories are not really difficult to understand and they are particularly influenced by the specified MIME type that can be explicitly defined in the <intent-filter> declaration using the
mimeType attribute inside the
<data> tag . The MIME type standard is widely used across technologies in order to identify resource types (e.g. image/png, text/html, ..) and consists of two main parts that we are interested into:
- Type: the generic type of the the format, for example
image,application,audio,videoand so on. - Subtype: the subtype is more specific and contains the media format. For example
png,htmlandmp4are an example of possible subtypes.
With this knowledge, we can go through all categories:
Full MIME Types: inside this category we have all possible MIME Types independently of its two parts (e.g.image/pngandimage/*).Base MIME Types: the base classification is related to data types that fully contains the two parts (e.g.image/pngorvideo/mp4).Wild MIME Types: MIME Types without the “subtype” (e.g.imageorvideo) or with a mask (e.g.image/*orvideo/*).Schemes: intent filters that handles data schemes (e.g.<data android:scheme="scheme"/>).Non-data Actions: Intent filters that do not contain any MIME type and data scheme.MIME Typed Actions: Intent filters that contains at least one MIME type.
As can be seen, an intent filter can also fall inside different categories. For example, an intent filter declared with a mimeType of value image is classified inside the Full MIME Type (as it contains a MIME type), Wild Mime Type (as it contains only the first part of the MIME type) and MIME Typed Action since it contains at least one MIME type.
# Registering methods
After this needed digression on the internal classification, let’s jump back to the
IntentResolver::addFilter:
| |
This method is responsible to register three main categories through its code using the
IntentResolver::register_intent_filter method: Schemes, Non-Data actions and Typed, while other MIME-related categories are registered through
IntentResolver::register_mime_types. As can be observed from the code, filters are registered following the previously described classification and results are stored inside the following class members (defined inside
IntentResolver.java): mSchemeToFilter, mActionToFilter, mTypedActionToFilter, mTypedActionToFilter, mBaseTypeToFilter and mWildTypeToFilter. These members are later used for the resolution process.
# Intent resolution
We have seen the logic behind the registration process of intent filters and now we are in the hearth of the topic: the resolution process.
The resolution process, and related system services and APIs, particularly depends on the targeted components (activities, receivers, services or providers) but in order to circumscribe the logic, let’s take into account two common APIs: startActivity and sendBroadcast. They can both send intents and, more importantly, implicit intents.
# startActivity
Let’s start our journey with the startActivity API, using a simple code as a reference:
| |
From the imported library code (e.g. inside the sender application process) after some preliminary error checking, the startActivity method from the ActivityTaskManager system service is called. This service method is responsible to find and start the destination activity if matched and it is part of the system_server services. In order to find target destinations that match a specific intent action (if not explicitly set from the sender), the previously described attributes (mSchemeToFilter, mActionToFilter, ..) are consulted from an internal method:
IntentResolver::queryIntent.
# IntentResolver::queryIntent
This method is reached after multiple calls (see the backtrace for all involved methods) and is responsible to loop over mentioned attributes in order to find most suitable destinations. The returned result is a list of candidates (List<R>).
The objective is not as easy to implement: a requested intent can have multiple candidates of any type (matching the MIME type, scheme and data) but need to return results that include everything!
| |
The queryIntent function satisfy this logic by using multiple “cuts”. It starts from the first cut that is related to MIME types[1]: if the intent matches some MIME type, the matching candidates are extracted from Full MIME Types [2], Base MIME Types [3] and Wild Mime Types [4] relative members. An interesting behavior is for the Typed Action filters[5]: If the primary part of the MIME type is * (e.g. */*) then, since the target can be anything and is too much generic, the action is used as a discrimination.
Then, if the scheme is specified, schemes candidate filters are retrieved [6] and the same (if the scheme is null) for the Non-data actions[7]. Every cut candidates are then confirmed from the buildResolveList to match all requested intent characteristics with the
intentFilter.match(..) call
and the final list is returned in the finalList variable.
# sendBroadcast
The senBroadcast resolution logic is really similar to the startActivity function with a major difference: the requested method and service.
| |
The involved service is the ActivityManager with the broadcastIntentWithFeature service method. As can be seen from the stack trace at the bottom () the IntentResolver::queryIntent method is called from the ComponentResolver class and the logic is the same one describer earlier.
# System Services and other methods
We have treated two common methods but there are multiple entry points to resolve intents for different types of components, however the logic is always the same: registered intents are cycled through the IntentResolver::queryIntent method. For example, the queryIntentActivities method is another commonly used method, exposed from the PackageManager system service, to resolve intents. AIDLs (Android Interface Definition Language) for the described services can be consulted there for more exposed functionalities:
IActivityTaskManager.aidl,
IActivityManager.aidl and
PackageManager.aidl
# dumpsys
The dumpsys utility is extremely helpful to list all registered intent filters in the system through the package argument. It offers the internal classification structure as output and the dump logic can be found from the previously mentioned
IntentResolver::dump method. The output contains the “Resolver Table” for each component type (activity, receiver, service and provider) with the described internal classification (Full MIME Types, Non-data actions, ..). For example, the adb shell dumpsys package returns a similar output:
| |
It is possible to add the -f option to print details for all specific filters such as declared actions, categories and data. In order to limit the output to a specific app, the application name can be specified: adb shell dumpsys package com.target.app.
| |
# Backtraces
# Backtrace: startActivity
| |
# Backtrace: sendBroadcast
| |
# Conclusion
We have covered the internal intent resolution process that deals with the <intent-filter> package declaration, going through involved system services and the internal AOSP codebase. In the next blog post we will cover Deep and App linking in more details due to its strict relation with the the intent declaration and its interesting attack surface.