Android Applications Basics

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Try Hard Security Group


Android Security Model

There are two layers:

  • The OS, which keeps installed applications isolated from one another.

  • The application itself, which allows developers to expose certain functionalities and configures application capabilities.

UID Separation

Each application is assigned a specific User ID. This is done during the installation of the app so the app can only interact with files owned by its User ID or shared files. Therefore, only the app itself, certain components of the OS and the root user can access the apps data.

UID Sharing

Two applications can be configured to use the same UID. This can be useful to share information, but if one of them is compromised the data of both applications will be compromised. This is why this behaviour is discourage. To share the same UID, applications must define the same android:sharedUserId value in their manifests.

Sandboxing

The Android Application Sandbox allows to run each application as a separate process under a separate user ID. Each process has its own virtual machine, so an app’s code runs in isolation from other apps. From Android 5.0(L) SELinux is enforced. Basically, SELinux denied all process interactions and then created policies to allow only the expected interactions between them.

Permissions

When you installs an app and it ask for permissions, the app is asking for the permissions configured in the uses-permission elements in the AndroidManifest.xml file. The uses-permission element indicates the name of the requested permission inside the name attribute. It also has the maxSdkVersion attribute which stops asking for permissions on versions higher than the one specified. Note that android applications don't need to ask for all the permissions at the beginning, they can also ask for permissions dynamically but all the permissions must be declared in the manifest.

When an app exposes functionality it can limit the access to only apps that have a specified permission. A permission element has three attributes:

  • The name of the permission

  • The permission-group attribute, which allows for grouping related permissions.

  • The protection-level which indicates how the permissions are granted. There are four types:

    • Normal: Used when there are no known threats to the app. The user is not required to approve it.

    • Dangerous: Indicates the permission grants the requesting application some elevated access. Users are requested to approve them.

    • Signature: Only apps signed by the same certificate as the one exporting the component can be granted permission. This is the strongest type of protection.

    • SignatureOrSystem: Only apps signed by the same certificate as the one exporting the component or apps running with system-level access can be granted permissions

Pre-Installed Applications

These apps are generally found in the /system/app or /system/priv-app directories and some of them are optimised (you may not even find the classes.dex file). Theses applications are worth checking because some times they are running with too many permissions (as root).

  • The ones shipped with the AOSP (Android OpenSource Project) ROM

  • Added by the device manufacturer

  • Added by the cell phone provider (if purchased from them)

Rooting

In order to obtain root access into a physical android device you generally need to exploit 1 or 2 vulnerabilities which use to be specific for the device and version. Once the exploit has worked, usually the Linux su binary is copied into a location specified in the user's PATH env variable like /system/xbin.

Once the su binary is configured, another Android app is used to interface with the su binary and process requests for root access like Superuser and SuperSU (available in Google Play store).

Note that the rooting process is very dangerous and can damage severely the device

ROMs

It's possible to replace the OS installing a custom firmware. Doing this it's possible to extend the usefulness of an old device, bypass software restrictions or gain access to the latest Android code. OmniROM and LineageOS are two of the most popular firmwares to use.

Note that not always is necessary to root the device to install a custom firmware. Some manufacturers allow the unlocking of their bootloaders in a well-documented and safe manner.

Implications

Once a device is rooted, any app could request access as root. If a malicious application gets it, it can will have access to almost everything and it will be able to damage the phone.

Android Application Fundamentals

  • The format of Android applications is referred to as APK file format. It is essentially a ZIP file (by renaming the file extension to .zip, the contents can be extracted and viewed).

  • APK Contents (Not exhaustive)

    • AndroidManifest.xml

    • resources.arsc/strings.xml

    • resources.arsc: contains precompiled resources, like binary XML.

      • res/xml/files_paths.xml

    • META-INF/

      • This is where the Certificate is located!

    • classes.dex

      • Contains Dalvik bytecode, representing the compiled Java (or Kotlin) code that the application executes by default.

    • lib/

      • Houses native libraries, segregated by CPU architecture in subdirectories.

        • armeabi: code for ARM based processors

        • armeabi-v7a: code for ARMv7 and higher based processors

        • x86: code for X86 processors

        • mips: code for MIPS processors only

    • assets/

      • Stores miscellaneous files needed by the app, potentially including additional native libraries or DEX files, sometimes used by malware authors to conceal additional code.

    • res/

      • Contains resources that are not compiled into resources.arsc

Dalvik & Smali

In Android development, Java or Kotlin is used for creating apps. Instead of using the JVM like in desktop apps, Android compiles this code into Dalvik Executable (DEX) bytecode. Earlier, the Dalvik virtual machine handled this bytecode, but now, the Android Runtime (ART) takes over in newer Android versions.

For reverse engineering, Smali becomes crucial. It's the human-readable version of DEX bytecode, acting like assembly language by translating source code into bytecode instructions. Smali and baksmali refer to the assembly and disassembly tools in this context.

Intents

Intents are the primary means by which Android apps communicate between their components or with other apps. These message objects can also carry data between apps or component, similar to how GET/POST requests are used in HTTP communications.

So an Intent is basically a message that is passed between components. Intents can be directed to specific components or apps, or can be sent without a specific recipient. To be simple Intent can be used:

  • To start an Activity, typically opening a user interface for an app

  • As broadcasts to inform the system and apps of changes

  • To start, stop, and communicate with a background service

  • To access data via ContentProviders

  • As callbacks to handle events

If vulerable, Intents can be used to perform a variety of attacks.

Intent-Filter

Intent Filters define how an activity, service, or Broadcast Receiver can interact with different types of Intents. Essentially, they describe the capabilities of these components, such as what actions they can perform or the kinds of broadcasts they can process. The primary place to declare these filters is within the AndroidManifest.xml file, though for Broadcast Receivers, coding them is also an option.

Intent Filters are composed of categories, actions, and data filters, with the possibility of including additional metadata. This setup allows components to handle specific Intents that match the declared criteria.

A critical aspect of Android components (activities/services/content providers/broadcast receivers) is their visibility or public status. A component is considered public and can interact with other apps if it is exported with a value of true or if an Intent Filter is declared for it in the manifest. However, there's a way for developers to explicitly keep these components private, ensuring they do not interact with other apps unintentionally. This is achieved by setting the exported attribute to false in their manifest definitions.

Moreover, developers have the option to secure access to these components further by requiring specific permissions. The permission attribute can be set to enforce that only apps with the designated permission can access the component, adding an extra layer of security and control over who can interact with it.

<activity android:name=".MyActivity" android:exported="false">
    <!-- Intent filters go here -->
</activity>

Implicit Intents

Intents are programatically created using an Intent constructor:

Intent email = new Intent(Intent.ACTION_SEND, Uri.parse("mailto:"));

The Action of the previously declared intent is ACTION_SEND and the Extra is a mailto Uri (the Extra if the extra information the intent is expecting).

This intent should be declared inside the manifest as in the following example:

<activity android:name="ShareActivity">
	<intent-filter>
       <action android:name="android.intent.action.SEND" />
       <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

An intent-filter needs to match the action, data and category to receive a message.

The "Intent resolution" process determine which app should receive each message. This process considers the priority attribute, which can be set in the intent-filter declaration, and the one with the higher priority will be selected. This priority can be set between -1000 and 1000 and applications can use the SYSTEM_HIGH_PRIORITY value. If a conflict arises, a "choser" Window appears so the user can decide.

Explicit Intents

An explicit intent specifies the class name it's targeting:

Intent downloadIntent = new (this, DownloadService.class):

In other applications in order to access to the previously declared intent you can use:

Intent intent = new Intent();
intent.setClassName("com.other.app", "com.other.app.ServiceName");
context.startService(intent);

Pending Intents

These allow other applications to take actions on behalf of your application, using your app's identity and permissions. Constructing a Pending Intent it should be specified an intent and the action to perform. If the declared intent isn't Explicit (doesn't declare which intent can call it) a malicious application could perform the declared action on behalf of the victim app. Moreover, if an action isn't specified, the malicious app will be able to do any action on behalf the victim.

Broadcast Intents

Unlike the previous intents, which are only received by one app, broadcast intents can be received by multiple apps. However, from API version 14, it's possible to specify the app that should receive the message using Intent.set Package.

Alternatively it's also possible to specify a permission when sending the broadcast. The receiver app will need to have that permission.

There are two types of Broadcasts: Normal (asynchronous) and Ordered (synchronous). The order is base on the configured priority within the receiver element. Each app can process, relay or drop the Broadcast.

It's possible to send a broadcast using the function sendBroadcast(intent, receiverPermission) from the Context class. You could also use the function sendBroadcast from the LocalBroadCastManager ensures the message never leaves the app. Using this you won't even need to export a receiver component.

Sticky Broadcasts

This kind of Broadcasts can be accessed long after they were sent. These were deprecated in API level 21 and it's recommended to not use them. They allow any application to sniff the data, but also to modify it.

If you find functions containing the word "sticky" like sendStickyBroadcast or sendStickyBroadcastAsUser, check the impact and try to remove them.

In Android applications, deep links are used to initiate an action (Intent) directly through a URL. This is done by declaring a specific URL scheme within an activity. When an Android device tries to access a URL with this scheme, the specified activity within the application is launched.

The scheme must be declarated in the AndroidManifest.xml file:

[...]
<activity android:name=".MyActivity">
  <intent-filter>
       <action android:name="android.intent.action.VIEW" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
       <data android:scheme="examplescheme" />
    </intent-filter>
[...]

The scheme from the previos example is exampleapp:// (note also the category BROWSABLE)

Then, in the data field, you can specify the host and path:

<data android:scheme="examplescheme" 
      android:host="example"
/>

To access it from a web it's possible to set a link like:

<a href="examplescheme://example/something">click here</a>
<a href="examplescheme://example/javascript://%250dalert(1)">click here</a>

In order to find the code that will be executed in the App, go to the activity called by the deeplink and search the function onNewIntent.

Learn how to call deep links without using HTML pages.

AIDL - Android Interface Definition Language

The Android Interface Definition Language (AIDL) is designed for facilitating communication between client and service in Android applications through interprocess communication (IPC). Since accessing another process's memory directly is not permitted on Android, AIDL simplifies the process by marshalling objects into a format understood by the operating system, thereby easing communication across different processes.

Key Concepts

  • Bound Services: These services utilize AIDL for IPC, enabling activities or components to bind to a service, make requests, and receive responses. The onBind method in the service's class is critical for initiating interaction, marking it as a vital area for security review in search of vulnerabilities.

  • Messenger: Operating as a bound service, Messenger facilitates IPC with a focus on processing data through the onBind method. It's essential to inspect this method closely for any unsafe data handling or execution of sensitive functions.

  • Binder: Although direct usage of the Binder class is less common due to AIDL's abstraction, it's beneficial to understand that Binder acts as a kernel-level driver facilitating data transfer between the memory spaces of different processes. For further understanding, a resource is available at https://www.youtube.com/watch?v=O-UHvFjxwZ8.

Components

These include: Activities, Services, Broadcast Receivers and Providers.

Launcher Activity and other activities

In Android apps, activities are like screens, showing different parts of the app's user interface. An app can have many activities, each one presenting a unique screen to the user.

The launcher activity is the main gateway to an app, launched when you tap the app's icon. It's defined in the app's manifest file with specific MAIN and LAUNCHER intents:

<activity android:name=".LauncherActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Not all apps need a launcher activity, especially those without a user interface, like background services.

Activities can be made available to other apps or processes by marking them as "exported" in the manifest. This setting allows other apps to start this activity:

<service android:name=".ExampleExportedService" android:exported="true"/>

However, accessing an activity from another app isn't always a security risk. The concern arises if sensitive data is being shared improperly, which could lead to information leaks.

An activity's lifecycle begins with the onCreate method, setting up the UI and preparing the activity for interaction with the user.

Application Subclass

In Android development, an app has the option to create a subclass of the Application class, though it's not mandatory. When such a subclass is defined, it becomes the first class to be instantiated within the app. The attachBaseContext method, if implemented in this subclass, is executed before the onCreate method. This setup allows for early initialization before the rest of the application starts.

public class MyApp extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // Initialization code here
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // More initialization code
    }
}

Services

Services are background operatives capable of executing tasks without a user interface. These tasks can continue running even when users switch to different applications, making services crucial for long-running operations.

Services are versatile; they can be initiated in various ways, with Intents being the primary method for launching them as an application's entry point. Once a service is started using the startService method, its onStart method kicks into action and keeps running until the stopService method is explicitly called. Alternatively, if a service's role is contingent on an active client connection, the bindService method is used for binding the client to the service, engaging the onBind method for data passage.

An interesting application of services includes background music playback or network data fetching without hindering the user's interaction with an app. Moreover, services can be made accessible to other processes on the same device through exporting. This is not the default behavior and requires explicit configuration in the Android Manifest file:

<service android:name=".ExampleExportedService" android:exported="true"/>

Broadcast Receivers

Broadcast receivers act as listeners in a messaging system, allowing multiple applications to respond to the same messages from the system. An app can register a receiver in two primary ways: through the app's Manifest or dynamically within the app's code via the registerReceiver API. In the Manifest, broadcasts are filtered with permissions, while dynamically registered receivers can also specify permissions upon registration.

Intent filters are crucial in both registration methods, determining which broadcasts trigger the receiver. Once a matching broadcast is sent, the receiver's onReceive method is invoked, enabling the app to react accordingly, such as adjusting behavior in response to a low battery alert.

Broadcasts can be either asynchronous, reaching all receivers without order, or synchronous, where receivers get the broadcast based on set priorities. However, it's important to note the potential security risk, as any app can prioritize itself to intercept a broadcast.

To understand a receiver's functionality, look for the onReceive method within its class. This method's code can manipulate the received Intent, highlighting the need for data validation by receivers, especially in Ordered Broadcasts, which can modify or drop the Intent.

Content Provider

Content Providers are essential for sharing structured data between apps, emphasizing the importance of implementing permissions to ensure data security. They allow apps to access data from various sources, including databases, filesystems, or the web. Specific permissions, like readPermission and writePermission, are crucial for controlling access. Additionally, temporary access can be granted through grantUriPermission settings in the app's manifest, leveraging attributes such as path, pathPrefix, and pathPattern for detailed access control.

Input validation is paramount to prevent vulnerabilities, such as SQL injection. Content Providers support basic operations: insert(), update(), delete(), and query(), facilitating data manipulation and sharing among applications.

FileProvider, a specialized Content Provider, focuses on sharing files securely. It is defined in the app's manifest with specific attributes to control access to folders, denoted by android:exported and android:resource pointing to folder configurations. Caution is advised when sharing directories to avoid exposing sensitive data inadvertently.

Example manifest declaration for FileProvider:

<provider android:name="androidx.core.content.FileProvider"
          android:authorities="com.example.myapp.fileprovider"
          android:grantUriPermissions="true" 
          android:exported="false">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/filepaths" />
</provider>

And an example of specifying shared folders in filepaths.xml:

<paths>
    <files-path path="images/" name="myimages" />
</paths>

For further information check:

WebViews

WebViews are like mini web browsers inside Android apps, pulling content either from the web or from local files. They face similar risks as regular browsers, yet there are ways to reduce these risks through specific settings.

Android offers two main WebView types:

  • WebViewClient is great for basic HTML but doesn't support the JavaScript alert function, affecting how XSS attacks can be tested.

  • WebChromeClient acts more like the full Chrome browser experience.

A key point is that WebView browsers do not share cookies with the device's main browser.

For loading content, methods such as loadUrl, loadData, and loadDataWithBaseURL are available. It's crucial to ensure these URLs or files are safe to use. Security settings can be managed via the WebSettings class. For instance, disabling JavaScript with setJavaScriptEnabled(false) can prevent XSS attacks.

The JavaScript "Bridge" lets Java objects interact with JavaScript, requiring methods to be marked with @JavascriptInterface for security from Android 4.2 onwards.

Allowing content access (setAllowContentAccess(true)) lets WebViews reach Content Providers, which could be a risk unless the content URLs are verified as secure.

To control file access:

  • Disabling file access (setAllowFileAccess(false)) limits access to the filesystem, with exceptions for certain assets, ensuring they're only used for non-sensitive content.

Other App Components and Mobile Device Management

Digital Signing of Applications

  • Digital signing is a must for Android apps, ensuring they're authentically authored before installation. This process uses a certificate for app identification and must be verified by the device's package manager upon installation. Apps can be self-signed or certified by an external CA, safeguarding against unauthorized access and ensuring the app remains untampered during its delivery to the device.

App Verification for Enhanced Security

  • Starting from Android 4.2, a feature called Verify Apps allows users to have apps checked for safety before installation. This verification process can warn users against potentially harmful apps, or even prevent the installation of particularly malicious ones, enhancing user security.

Mobile Device Management (MDM)

  • MDM solutions provide oversight and security for mobile devices through Device Administration API. They necessitate the installation of an Android app to manage and secure mobile devices effectively. Key functions include enforcing password policies, mandating storage encryption, and permitting remote data wipe, ensuring comprehensive control and security over mobile devices.

// Example of enforcing a password policy with MDM
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminComponent = new ComponentName(context, AdminReceiver.class);

if (dpm.isAdminActive(adminComponent)) {
    // Set minimum password length
    dpm.setPasswordMinimumLength(adminComponent, 8);
}

Try Hard Security Group

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Last updated