The Android Operating System (OS) is used by many vendors on phones and tablets ranging from low-cost budget devices to flagships. Due to its open-source nature it can be found on many other devices including entertainment systems, TVs, e-readers, netbooks, smartwatches, car computers, and gaming consoles.
Android is the mobile platform that has the biggest market share out of all the mobile operating systems available. With this esteemed achievement comes the attention of many hackers around the world wanting to expose security flaws in the OS and popular applications on the platform. Although many app stores are available for Android users, observing only the official Google Play Store statistics from AppBrain (http://www.appbrain.com/stats/number-of-android-apps
) reveals that Google Play Store holds more than 1.1 million applications for download. Vulnerabilities are constantly being discovered in popular applications with varying degrees of severity, and due to the maturity of tools and information about finding these vulnerabilities, this trend looks to be ever increasing.
This chapter presents some fundamental concepts of Android including its application structure, security model, and infrastructure central to its operation. It also delves deeper into the intricacies of the Android platform and ways that you can explore these by setting up a testing environment and making use of popular tools. The goal of this chapter is to provide you with the background knowledge required to find and exploit security flaws in applications.
The first step in building your ideal testing environment is downloading the Android Software Development Kit (SDK). Whether you plan to use an emulator or physical device, the Android SDK provides many tools that are essential to getting started with Android hacking. You can download the SDK tools from http://developer.android.com/sdk/
for your OS. The two options are to download the entire Android Developer Tools package, which includes an integrated development environment (IDE) and all the tools, or download an archive containing only the tools. For the large majority of testing, having only the tools and not a full development environment setup should suffice. However, occasionally you may still have to write a custom application to test a certain condition or create a proof of concept. We highly recommended using Linux as your base OS when testing Android because many of the tools that you will be experimenting with in subsequent chapters were originally written for Linux, and have shown to be less error-prone on Linux. However, you can ignore our bias and use other operating systems successfully. If you are new to Linux, it is recommended that you use the Ubuntu distribution (see http://www.ubuntu.com/
). This is because of the wealth of information and tutorials available for newcomers.
After extracting the SDK tools, place the entire tools/
directory on your path. In Linux, you do so by adding the following line to your .bashrc
in your home folder and then opening a new terminal:
export PATH=$PATH:/path/to/sdk/tools/:/path/to/sdk/platform-tools/
This command appends the provided folders to your path. Some hackers prefer to create symbolic links to specific binaries in a directory that is already in their path (like /usr/local/bin
), which you can do as follows:
# cd /usr/local/bin
# ln –s /path/to/binary
The following is a shortened listing of Android SDK tools to get you started:
A 64-bit system requires an additional installation of 32-bit packages needed by the SDK tools. You can install these on Ubuntu 13.04 upward by using
$ sudo dpkg –add-architecture i386
$ sudo apt-get update
$ sudo apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386
Prior to that version of Ubuntu, you use the following command:
$ sudo apt-get install ia32-libs
Android provides an excellent set of emulators for all versions from the most current all the way back to Android 1.5. To create your very first Android emulator that runs Android 4.4.2 KitKat, run the following to display the Android SDK Manager interface:
$ android sdk
You can use this to install SDK platforms, system images, and tools. Figure 6.1 shows the user interface.
Select Android 4.4.2 (API 19), click Install, and agree to the user license. It will now download and install all required packages. You are now able to create a KitKat emulator by running the Android Virtual Device (AVD) Manager:
$ android avd
On the AVD Manager’s user interface, click the New button. The configuration in Figure 6.2 is fit for most purposes but you can customize it to suit a particular testing requirement.
Your emulator should now be created. You can start it by clicking the Start button on the AVD manager or running the following from a terminal if you know the name of your created AVD:
$ emulator -avd kitkat
After the emulator launches, list all connected Android devices on your computer by using one of the included SDK tools named ADB (Android Debug Bridge):
$ adb devices
To get an interactive shell on the listed device issue the following command:
$ adb -s device_id shell
If only a single device is connected, you can omit the -s
parameter. If you have only a single emulator open and a connected physical device, you can also omit the -s
parameter and use -e
(emulator) and -d
(device) to interact with each, respectively. ADB will be used for a number of tasks on Android, and we advise you to take the time to learn all of its functionality and syntax.
You might immediately notice some minor differences between an actual device and an emulator, such as
ro.secure
which will be explored in Chapter 8.Emulator restrictions are documented at http://developer.android.com/tools/devices/emulator.html#limitations
. When performing testing on an Android application, you should have multiple devices at hand in addition to the emulators to accommodate for the differences between them.
The Android emulator provides a way for users to emulate a number of events, such as receiving an SMS or phone call through a console interface. Locate the console by observing the output of adb devices
in the previous command. For example, an emulator named emulator-5554 indicates that it has a listening port on TCP 5554 on the local host. Use a telnet
or netcat (nc
) client to access the console interface. Most Linux distributions come with nc
, which you use to access the console interface as follows:
$ nc localhost 5554
Android Console: type 'help' for a list of commands
OK
help
Android console command help:
help|h|? print a list of commands
event simulate hardware events
geo Geo-location commands
gsm GSM related commands
cdma CDMA related commands
kill kill the emulator instance
network manage network settings
power power related commands
quit|exit quit control session
redir manage port redirections
sms SMS related commands
avd control virtual device execution
window manage emulator window
qemu QEMU-specific commands
sensor manage emulator sensors
Some other more technical differences between the Android emulator and physical devices are not so apparent on first observation. Writing an exploit for a memory corruption vulnerability will quickly reveal these differences. Exploitation at this level is an advanced topic that would require a separate publication on its own. However, all that is important is that you realize that at the lowest levels of operation, an emulator is not an exact replica of how Android runs on a real device, even though it may feel that way. Often, exploits that work on an emulator may require significant changes to work on an actual device.
Alternatives other than using the emulator that comes with the Android SDK are available. Popular ones include
http://www.genymotion.com/
)http://www.android-x86.org/
)https://youwave.com
)http://windowsandroid.en.softonic.com/
)These emulators run x86 versions of Android and some applications that contain native code may not support this architecture. However, for exploring Android to understand how it works, they are useful and some may run quicker than the Google emulators. However, it is still the author’s preference to use the official Android emulator as it is always guaranteed to be unmodified.
For testing purposes, using a physical Android device may be better than using an emulator because of emulator speed issues or hardware requirements such as Wi-Fi or Bluetooth. As opposed to other mobile platforms where jailbreaking your testing device is essential, you can do a surprising amount of testing or hacking without root access on an Android device. However, some actions cannot be performed or take longer to perform without having root access on the device and so having root access is always advised. More concrete examples of some of the constraints of assessing an application without having root access will be explored in later chapters. The Internet offers many guides on ways to root your specific device. An overview of typical ways to root an Android device appears later in this chapter in the “Rooting Explained” section.
The majority of users experience Android applications through downloading them from the Play Store, reviewing the permission requirements presented to them (or not), and then installing. After the application has been installed, a new home screen icon appears that allows them to open the application, just as the developer intended. As a technical person, you should not feel satisfied with not knowing exactly how and why installation worked. What happened behind the scenes when you clicked the button to install that application? How did this application reach your device? How did it go from a packaged download to an installed application that you can use securely? These are all questions that you need to answer before you can be satisfied with moving onto assessing Android applications.
Before exploring the weird and wonderful world of Android applications, take a step back and understand how the operating system functions as a whole. You can view the Android OS as having two distinct sides to it: a stripped-down and modified Linux kernel and an application virtual machine that runs Java-like applications. The differences between the mainline Linux kernel and the Android kernel have varied over the years and have started to lessen, but fundamental differences between how conventional Linux and Android operate remain. On conventional Linux, applications that are started by a user are run under that user’s context. This model relies on a user’s not installing malicious software on her computer because there are no protection mechanisms against accessing files that are owned by the same user that you are running as. In contrast to conventional Linux computing, each application that is installed on an Android device is assigned its own unique user identifier (UID) and group identifier (GID). In certain instances this statement does not hold true and applications can run under the same user, but these are covered later in this chapter under the “Application Sandbox” section. A snipped output of running the ps
command to display information about running processes on an Android device is shown here:
shell@android:/ $ ps
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 640 496 c00bd520 00019fb8 S /init
...
root 46 1 4660 1200 ffffffff b6f61d14 S /system/bin/vold
root 48 1 9772 1268 ffffffff b6f1fd14 S /system/bin/netd
...
root 52 1 225052 39920 ffffffff b6ecb568 S zygote
...
system 371 52 307064 46084 ffffffff b6ecc5cc S system_server
u0_a7 424 52 255172 45060 ffffffff b6ecc5cc S com.android.systemui
...
radio 520 52 259604 25716 ffffffff b6ecc5cc S com.android.phone
u0_a8 534 52 248952 56996 ffffffff b6ecc5cc S com.android.launcher
u0_a9 789 52 244992 20612 ffffffff b6ecc5cc S com.android.mms
u0_a16 819 52 246240 20104 ffffffff b6ecc5cc S com.android.calendar
...
u0_a37 1419 52 233948 17132 ffffffff b6ecc5cc S com.svox.pico
root 1558 61 928 496 c0010008 b6f57fa0 S /system/bin/sh
u0_a52 1581 52 238060 25708 ffffffff b6ecc5cc S com.mwr.dz
u0_a52 1599 52 240328 27076 ffffffff b6ecc5cc S com.mwr.dz:remote
...
root 14657 1558 1236 464 00000000 b6f0b158 R ps
In this output, note that applications are running as different users. Newly installed applications are assigned UIDs sequentially from 10000 onward (until a maximum of 99999). You can observe this configuration in the Android source at https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
. The user named u0_a0
has UID 10000, and similarly, a user named u0_a12
has UID 10012. Every Android application has to be given a unique package name by its developer. The naming convention for these packages should be all lowercase and the reverse Internet domain name of the organization that developed it. For instance, if an application is named “battery saver” and it was developed by the fictitious “Amazing Utils” company then perhaps they could name the package com .amazingutils.batterysaver
. This would almost guarantee a unique package name and any other application created by this organization could also have the prefix com.amazingutils
that would allow logical grouping of their applications.
If you were to install this application on your device, you would see that it assigns a private data directory at the following location on your device’s filesystem. On disk this may look something like the following:
shell@android:/ # ls -l /data/data/
...
drwxr-x--x u0_a46 u0_a46 2014-04-10 10:41
com.amazingutils.batterysaver
...
Notice that the owner of the folder is the newly created user for that application (u0_a46
, which translates to UID 10046).
The Dalvik Virtual Machine (DVM) was specifically designed for the Android platform and is unique to it. The main reason for its existence is that it was designed to run on hardware with processing and memory constraints and is much lighter than the normal Java Virtual Machine. It was designed in a way that allows many Dalvik VMs to be run at the same time in a memory-efficient manner. The code that runs on it is written and compiled to Java classes and then converted into a single DEX file using the dx
SDK utility. The following is an example of compiling a simple Java JAR for Android without using an IDE. First, create a file named Test.java
with the following content:
class Test
{
public static void main(String[] args)
{
System.out.println("It works! :D");
}
}
Issue the following commands that will compile the class to normal Java bytecode, and then use the dx
utility to convert it to a JAR that contains Dalvik-compatible bytecode.
$ javac Test.java
$ dx –dex –output=test.jar Test.class
The JAR is now compiled and can be pushed to the device and executed using the dalvikvm
or app_process
binaries on the device. The arguments provided to these binaries tell the Dalvik VM to look for the class named Test
in /data/local/tmp/test.jar
and execute the main
function.
$ adb push test.jar /data/local/tmp
$ adb shell dalvikvm -cp /data/local/tmp/test.jar Test
It works :D
The previous code does not produce a full-fledged, installable application on Android. You must follow Android package conventions and have the SDK automatically package your code into an installable Android package that can be deployed onto a device. This example does, however, demonstrate the close link between Java and Dalvik that exists. This could help Java developers transition into the world of Android and its internals. Intricate runtime internals are explored later in this chapter in “Looking Under the Hood.” In addition to this, Android 4.4 introduced a runtime replacement for Dalvik, named ART (Android Runtime), which promised to improve the speed of applications drastically.
An Android package is a bundle that gets installed on an Android device to provide a new application. This section will explore the structure of packages and different ways that exist to install them on a device.
Android applications are distributed in the form of a zipped archive with the file extension of .apk
, which stands for Android Package. The official mime-type of an Android Package is application/vnd.android.package-archive
. These packages are nothing more than zip files containing the relevant compiled application code, resources, and application metadata required to define a complete application. According to Google’s documentation at http://developer.android.com/tools/building/index.html
, an APK is packaged by performing the following tasks:
aapt
(Android Asset Packaging Tool) converts all the XML resource files included in the application to a binary form. R.java
is also produced by aapt
to allow referencing of resources from code.aidl
is used to convert any .aidl
files (explored in Chapter 7 in “Attacking Insecure Services”) to .java
files containing a converted representation of it using a standard Java interface.aapt
and aidl
are compiled into .class
files by the Java 1.6 compiler. This requires the android.jar
file for your desired API version to be in the CLASSPATH
environment variable.dx
utility is used to convert the produced .class
files and any third-party libraries into a single classes.dex
file.apkbuilder
tool to package an APK file. More recent versions of the SDK have deprecated the standalone apkbuilder
tool and included it as a class inside sdklib.jar
. The APK file is signed with a key using the jarsigner
utility. It can either be signed by a default debug key or if it is going to production, it can be signed with your generated release key.zipalign
tool, which ensures that the application resources are aligned optimally for the way that they will be loaded into memory. The benefit of this is that the amount of RAM consumed when running the application is reduced.This compilation process is invisible to you as the developer as these tasks are automatically performed by your IDE but are essential to understanding how code becomes a complete package. When you unzip an APK you see the final product of all steps listed above. Note also that a very strictly defined folder structure is used by every APK. The following is a high-level look at this folder structure:
/assets
/res
/lib
/META-INF
AndroidManifest.xml
classes.dex
resources.asrc
raw/
subdirectory.classes.dex
—this is essentially the executable file containing the Dalvik bytecode of the application. It is the actual code that will run on the Dalvik Virtual Machine.AndroidManifest.xml
—the manifest file containing all configuration information about the application and defined security parameters. This will be explored in detail later in this chapter.Behind the scenes, the process of downloading an application from the Play Store and installing it is actually quite a bit more complicated than one would imagine. The simplest way that Google could have implemented this process is to have the Play Store application visit a website and allow the user to browse through the application categories. When the user chooses to install an application Google would provide an “install” link and all that this does is download the APK file over HTTPS from the browser. What is wrong with this approach? Well, considering this method from a security point of view, how does the OS know that the downloaded package came from the Play Store and is safe to install? The APK would be treated like every other download using the browser and therefore no degree of trust can be afforded using this method.
Instead, Google implemented a very modular and robust way to perform installations. When you click the Install button on the Google Play application or website, functionality to deliver and install the application is invoked on the device via the GTalkService
. This functionality works from a system application on every Android device and maintains a connection to Google infrastructure via a pinned SSL connection. Various other services such as the Android Device Manager or Google Cloud Messaging (GCM) make use of the GTalkService
. The installation process via the GTalkService
was explored in an excellent blog post by Jon Oberheide at https://jon.oberheide.org/blog/2010/06/28/a-peek-inside-the-gtalkservice-connection/
. The GTalkService
gracefully handles cases where the device on which you are installing an application is offline or in a low-signal area. It simply queues the message and delivers it when the device comes online. One of the reasons Android is considered so “open and free” is that so many different ways exist to find and install Android applications. Google does not force users to make use of its Play Store and users can make use of many other application stores instead. Some device vendors and phone carriers like to include their own app stores on devices they sell. A good example of this is the Samsung Apps application that is included on all Samsung devices. Other such examples of popular alternative app stores include Amazon Appstore, GetJar, SlideMe, F-Droid, and a number of big players in the Eastern markets.
In addition to these application stores, multiple ways exist to install new applications onto your device by simply having access to the APK that you would like to install. Making use of an Android SDK tool named ADB (Android Debug Bridge) is one of the simplest ways to do this. Assuming a correct SDK installation, ADB will be on your PATH
. Issuing the following command will install an APK onto a connected device or emulator:
$ adb install /path/to/yourapplication.apk
On Android 4.2.2 and later, making an ADB connection may require you to accept a prompt allowing your computer to connect. The install
command of ADB works behind the scenes invoking the package manager on the device (/system/bin/pm
). Package Manager can perform a number of actions, including listing all installed packages, disabling an application that came with the device that you consider unnecessary “bloatware,” or obtaining the installed path to a particular application. For all the available options, type the following command and observe the output:
$ adb shell pm
Another way to install an application could be to host it on a web server. Some application developers choose not to put their application on any app stores and rather serve it from their website. These sites often check for Android browser user agent strings and automatically start the download of their APK. A simple method of hosting the contents of your current folder using Python can be done as follows:
$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
10.0.0.100 - - [04/May/2014 22:27:14] "GET /agent.apk HTTP/1.1" 200 -
Browse to http://your_computer_ip:8000
on your device and click on the APK you want to install. You will be prompted with an installation activity.
Other techniques may exist to install applications; however, the ones mentioned here are reliable and work on any device regardless of whether you have root access on it. Other ways may include SSH access to the device or even other installer desktop applications, but these are non-standard ways to perform installations and require additional tools.
The best way to learn the internals of Android and become familiar with the way it works is to explore an emulator or device armed with some basic knowledge about it. By exploring Android and becoming comfortable with its internals, you will have the ability to investigate features for which no public information exists.
A simple example of this type of exploration is observing—through inspection of the tool or reading the source code—how some of the standard SDK tools work.
For instance, when installing an application on the device you may see the following output:
$ adb install application.apk
541 KB/s (156124 bytes in 0.236s)
pkg: /data/local/tmp/application.apk
Success
This output shows that the user who runs adbd
(which is typically “shell” on a normal non-rooted device) has the ability to read, write, and execute files in the /data/local/tmp
directory. When exploring a device that is not rooted, you can use this directory but have insufficient privileges to access the /data
parent directory.
ADB is the single most useful SDK tool for exploring Android. The following is a list of common tasks that you can perform using ADB:
$ adb devices
$ adb shell
$ adb shell <command>
$ adb push /path/to/local/file /path/on/android/device
$ adb pull /path/on/android/device /path/to/local/file
$ adb forward tcp:<local_port> tcp:<device_port>
$ adb logcat
If more than one device is connected, prepend the ADB command with -s <device_id>
. If you have one connected device and one emulator, instead of providing their device IDs with the -s
argument, you can use -d
(for device) and -e
(for emulator).
Some Android devices may come with a very limited set of utilities installed by default, and having additional tools installed that ease the process of exploring the device is useful.
BusyBox incorporates a large variety of standard Linux utilities into a single binary. A common misconception about running BusyBox on Android is that it requires root. This is incorrect, and users should be aware that executing a BusyBox binary runs it under the same user account and privilege context of the calling process. You can compile BusyBox with the utilities you require or download a pre-compiled binary that includes many utilities. At the time of this writing, the BusyBox website provided pre-compiled binaries for many architectures at http://www.busybox.net/downloads/binaries/
. This includes ARM, which is the CPU architecture used by the majority of Android devices. You can download a BusyBox binary for the correct architecture (ARMv7 in this case) from the site and then upload it to the /data/local/tmp
directory on your Android device without the need for root access using the following command:
$ adb push busybox-armv7l /data/local/tmp
77 KB/s (1109128 bytes in 14.041s)
Get a shell on the device, browse to /data/local/tmp
, and mark it executable using the following command:
shell@android:/ $ cd /data/local/tmp
shell@android:/data/local/tmp $ chmod 755 busybox-armv7l
Here is an output of the available tools provided by BusyBox:
shell@android:/data/local/tmp $ ./busybox-armv7l
./busybox-armv7l
BusyBox v1.21.1 (2013-07-08 10:26:30 CDT) multi-call binary.
...
acpid, add-shell, addgroup, adduser, adjtimex, arp, arping, ash,
awk, base64, basename, beep, blkid, blockdev, bootchartd, brctl,
bunzip2, bzcat, bzip2, cal, cat, catv, chat, chattr, chgrp, chmod,
chown, chpasswd, chpst, chroot, chrt, chvt, cksum, clear, cmp, comm,
conspy, cp, cpio, crond, crontab, cryptpw, cttyhack, cut, date, dc, dd,
deallocvt, delgroup, deluser, depmod, devmem, df, dhcprelay, diff,
dirname, dmesg, dnsd, dnsdomainname, dos2unix, du, dumpkmap,
dumpleases, echo, ed, egrep, eject, env, envdir, envuidgid, ether-wake,
expand, expr, fakeidentd, false, fbset, fbsplash, fdflush, fdformat,
fdisk, fgconsole, fgrep, find, findfs, flock, fold, free, freeramdisk,
fsck, fsck.minix, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty,
grep, groups, gunzip, gzip, halt, hd, hdparm, head, hexdump, hostid,
hostname, httpd, hush, hwclock, id, ifconfig, ifdown, ifenslave,
ifplugd, ifup, inetd, init, insmod, install, ionice, iostat, ip,
ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule, iptunnel,
kbd_mode, kill, killall, killall5, klogd, last, less, linux32, linux64,
linuxrc, ln, loadfont, loadkmap, logger, login, logname, logread,
losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lsof, lspci, lsusb, lzcat,
lzma, lzop, lzopcat, makedevs, makemime, man, md5sum, mdev, mesg,
microcom, mkdir, mkdosfs, mke2fs, mkfifo, mkfs.ext2, mkfs.minix,
mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modinfo, modprobe, more,
mount, mountpoint, mpstat, mt, mv, nameif, nanddump, nandwrite,
nbd-client, nc, netstat, nice, nmeter, nohup, nslookup, ntpd, od,
openvt, passwd, patch, pgrep, pidof, ping, ping6, pipe_progress,
pivot_root, pkill, pmap, popmaildir, poweroff, powertop, printenv,
printf, ps, pscan, pstree, pwd, pwdx, raidautorun, rdate, rdev,
readahead, readlink, readprofile, realpath, reboot, reformime,
remove-shell, renice, reset, resize, rev, rm, rmdir, rmmod, route, rpm,
rpm2cpio, rtcwake, run-parts, runlevel, runsv, runsvdir, rx, script,
scriptreplay, sed, sendmail, seq, setarch, setconsole, setfont,
setkeycodes, setlogcons, setserial, setsid, setuidgid, sh, sha1sum,
sha256sum, sha3sum, sha512sum, showkey, slattach, sleep, smemcap,
softlimit, sort, split, start-stop-daemon, stat, strings, stty, su,
sulogin, sum, sv, svlogd, swapoff, swapon, switch_root, sync, sysctl,
syslogd, tac, tail, tar, tcpsvd, tee, telnet, telnetd, test, tftp,
tftpd, time, timeout, top, touch, tr, traceroute, traceroute6, true,
tty, ttysize, tunctl, udhcpc, udhcpd, udpsvd, umount, uname, unexpand,
uniq, unix2dos, unlzma, unlzop, unxz, unzip, uptime, users, usleep,
uudecode, uuencode, vconfig, vi, vlock, volname, wall, watch, watchdog,
wc, wget, which, who, whoami, whois, xargs, xz, xzcat, yes, zcat, zcip
This is a huge set of tools, many of which do not come as part of the Android image. Some of these tools are common utilities used on a desktop or server version of Linux, such as cp
and grep
, which the Android image inconveniently left out. Do not expect all the included tools to work fully, because some aspects of Android simply do not work the same as on conventional Linux systems. You can add BusyBox to the shell’s PATH
environment temporarily without root by entering the following command:
shell@android:/ $ export PATH=$PATH:/data/local/tmp
Some useful tools that are present on Android systems in the /system/bin
directory include the following:
pm
—This stands for “package manager” and is the command-line package management utility on Android. It performs all tasks relating to installation, uninstallation, disabling, and information retrieval of installed packages. Some useful commands are:
shell@android:/ $ pm list packages
shell@android:/ $ pm path <package_name>
shell@android:/ $ pm install /path/to/apk
shell@android:/ $ pm uninstall <package_name>
shell@android:/ $ pm disable <package_name>
logcat
—This tool allows you to view system and application logs with flexible filters. This tool can only be invoked by applications or users on the device that have the associated privilege level to do so.
shell@android:/ $ logcat
shell@android:/ $ logcat -s tag
getprop
—This tool allows you to retrieve all system properties including verbose hardware and software information.dumpsys
—This tool displays information about the status of system services. If run without any arguments it iterates through all system services. You can also find these services by running service list
.drozer is an Android assessment tool that was released in March 2012 at Blackhat EU under the name Mercury. Its original intention was to eliminate the need for writing one-use applications that test for a certain issue, and it has evolved into a full testing suite. It was created because of the need to test each aspect of an Android application in a dynamic way. Put simply, drozer has two distinct use cases:
Chapter 7 focuses heavily on using drozer to find vulnerabilities, and Chapter 8 delves into the darker side of drozer and ways of using provided exploits to gain access to Android devices as an attacker.
drozer has two different versions: the community and pro editions. The community edition provides the raw power of drozer and gives the user access to a command-line interface only. It is also a fully open-source project that was released under a 3-clause BSD license. The professional version focuses on features that make doing Android security testing easy for people who do it as a part of their job. It provides a graphical user interface that makes visualizing the large amount of information that can be collected during the course of a typical security assessment of an Android device easier. Throughout the following chapters, the community edition of drozer is used for two reasons: It is free, and it facilitates the learning of Android security better than the pro version, mainly because it does not shield you from what it is doing under the hood. For more information about the differences, see the tool’s homepage at https://www.mwrinfosecurity.com/products/drozer/
.
drozer is a distributed system that makes use of some key components:
These components use a custom protocol named drozerp
(drozer protocol) to exchange data. The agent is somewhat of an empty shell that knows only how to run commands it receives from the console and provide the result. A very technically brilliant method of using the Java Reflection API facilitates the execution of code from Python in the console to Java on the agent. This means that from Python code it is possible to instantiate and interact with Java objects on the connected device.
To set up drozer, visit https://www.mwrinfosecurity.com/products/drozer/community-edition/
and download the package that is appropriate for your platform (Linux, Windows, or Mac). For standard application testing purposes, the tool requires only two parts: an agent application that needs to be installed on your Android device and a console that is run from your computer. You will require the following to install drozer successfully on your computer:
PATH
PATH
The drozer agent can be installed on your Android device using ADB. It is included as agent.apk
in all download packages or as a separate package on the download page. To install the agent on your device, perform the following command:
$ adb install agent.apk
For more verbose information about installing drozer, please refer to the user guide presented on the download page.
You must first set up suitable port forwarding from your device or emulator to your computer because the embedded server in the drozer agent listens on TCP port (31415 by default). Perform the following command to forward this port to your computer:
$ adb forward tcp:31415 tcp:31415
You can now open the drozer agent on the device and turn on the Embedded Server option as shown in Figure 6.3.
On your computer you can now perform the following command to connect to your agent:
$ drozer console connect
You should now see a drozer command prompt that confirms your device ID and looks as follows:
Selecting 1f3213a063299199 (unknown sdk 4.4.2)
.. ..:.
..o.. .r..
..a.. . ....... . ..nd
ro..idsnemesisand..pr
.otectorandroidsneme.
.,sisandprotectorandroids+.
..nemesisandprotectorandroidsn:.
.emesisandprotectorandroidsnemes..
..isandp,..,rotectorandro,..,idsnem.
.isisandp..rotectorandroid..snemisis.
,andprotectorandroidsnemisisandprotec.
.torandroidsnemesisandprotectorandroid.
.snemisisandprotectorandroidsnemesisan:
.dprotectorandroidsnemesisandprotector.
drozer Console (v2.3.4)
dz>
The drozer console is essentially a command-line interface that allows you to run modules currently installed in the framework. To find the available modules, use the list
command. Running this command without any arguments will give a list of all available modules, and providing it with an argument filters the module list by that keyword. The following shows an example:
dz> list package
app.package.attacksurface Get attack surface of package
app.package.backup Lists packages that use backup API (returns
true on FLAG_ALLOW_BACKUP)
app.package.debuggable Find debuggable packages
app.package.info Get information about installed packages
app.package.launchintent Get launch intent of package
app.package.list List Packages
app.package.manifest Get AndroidManifest.xml of package
...
Some modules do not come as part of the standard drozer installation. This is because they are seen as additional modules that may not be used regularly or are specialized for a certain task such as installing an additional tool or a root exploit for a certain device. You search for modules from the online central module repository using the module search
command. Here -d
is used to show module descriptions:
dz> module search -d
...
metall0id.root.cmdclient
Exploit the setuid-root binary at /system/bin/cmdclient on certain
devices to gain a root shell. Command injection vulnerabilities exist
in the parsing mechanisms of the various input arguments.
This exploit has been reported to work on the Acer Iconia, Motorola
XYBoard and Motorola Xoom FE.
...
metall0id.tools.setup.nmap
Installs Nmap on the Agent.
Nmap ("Network Mapper") is a free and open source (license) utility
for network discovery and security auditing.
mwrlabs.develop
Start a Python shell, in the context of a drozer module.
You can also search available modules for specific keywords contained within their descriptions or names by providing a keyword to module search
. This functionality can also be invoked from outside of a drozer console
by using the drozer module
command from your terminal. The searched module repository is at https://github.com/mwrlabs/drozer-modules/
.
Modules are organized into namespaces that group specific functions. Table 6.1 details the default namespaces; however, drozer module developers may choose to create additional namespaces.
Table 6.1 A List of drozer Namespaces and the Purpose of the Modules in Each
NAMESPACE | DESCRIPTION |
app.activity |
Find and interact with activities exported by applications. |
app.broadcast |
Find and interact with broadcast receivers exported by applications. |
app.package |
Find packages installed on a device, and display information about them. |
app.provider |
Find and interact with content providers exported by applications. |
app.service |
Find and interact with services exported by applications. |
auxiliary |
Useful tools that have been ported to drozer. |
exploit.pilfer |
Public exploits that extract sensitive information from vulnerable applications through various means. |
exploit.root |
Publicly available root exploits for Android devices. |
information |
Extract additional information about a device and its configuration. |
scanner |
Find common vulnerabilities in applications or devices with automatic scanners. |
shell |
Interact with the underlying Linux OS through a shell. |
tools.file |
Perform operations on files; e.g., copy files to and from the device. |
tools.setup |
Upload additional utilities on the device for use inside drozer; e.g., busybox. |
A good way to understand what an unprivileged application has access to on a device is by using the drozer shell. Launch it and issue an id
command as shown here:
dz> shell
u0_a59@android:/data/data/com.mwr.dz $ id
uid=10059(u0_a59) gid=10059(u0_a59) groups=3003(inet),50059(all_a59)
context=u:r:untrusted_app:s0
u0_a59@android:/data/data/com.mwr.dz $
Remember that UIDs are assigned sequentially from 10000 upwards, and more about how the groups are assigned to an application is explained later in this section in “Inspecting the Android Permission Model”.
You can find more information about what a module does and its command-line parameters by using the help command within the console. Alternatively, use -h
inline when executing a command as shown here:
dz> run app.package.info -a com.mwr.dz -h
Another useful feature of the console is the ability to redirect any output from a module to a file. You can do this in the same manner as you do it on the terminal using the >
character like so:
dz> run app.package.info -a com.mwr.dz > /path/to/output.txt
For other useful semantics and shortcuts, refer to the drozer user guide on the project’s download page.
For you to get used to drozer’s complex way of executing Java from Python and help with module development in general, installing the following module is crucial:
dz> module install mwrlabs.develop
Processing mwrlabs.develop... Done.
Successfully installed 1 modules, 0 already installed.
This module provides an interactive shell to test the instantiation of objects, retrieval of constant values, and execution of methods. For example, suppose you want to create a module that returns the package’s name when provided with an application’s UID. You could test it first using the auxiliary.develop .interactive
module that was installed previously.
dz> run auxiliary.develop.interactive
Entering an interactive Python shell. Type 'c' to end.
> /home/tyrone/dz-repo/mwrlabs/develop.py(24)execute()
-> self.pop_completer()
(Pdb) context = self.getContext()
(Pdb) pm = context.getPackageManager()
(Pdb) name = pm.getNameForUid(10059)
(Pdb) print name
com.mwr.dz
drozer provides some “common library” commands to help alleviate reimplementation of common tasks. You can find them defined in the /src/drozer/modules/common/
folder of the drozer console source code. The self.getContext()
function used previously is a helper function that provides a handle on Android Context
, which can be elusive at times. An equivalent Java implementation of the preceding code could be the following:
Context context = getApplicationContext();
PackageManager pm = context.getPackageManager();
String name = pm.getNameForUid(10059);
Turning this simple concept into a fully functioning drozer module may look as follows:
from drozer.modules import Module
class GetPackageFromUID(Module):
name = "Get a package's name from the given UID"
description = "Get a package's name from the given UID"
examples = """
dz> run app.package.getpackagefromuid 10059
UID 10059 is com.mwr.dz
"""
author = "Tyrone"
date = "2014-05-30"
license = "BSD (3 clause)"
path = ["app", "package"]
permissions = ["com.mwr.dz.permissions.GET_CONTEXT"]
def add_arguments(self, parser):
parser.add_argument("uid", help="uid of package")
def execute(self, arguments):
context = self.getContext()
pm = context.getPackageManager()
name = pm.getNameForUid(int(arguments.uid))
self.stdout.write("UID %s is %s\n\n" % (arguments.uid, name))
Saving the newly created module in a file with extension .py
in a local repository allows access to it from drozer. Creating a local repository can be done using the following command from the console (or similarly using the drozer
command from the terminal).
dz> module repository create /path/to/repository
Running your newly created module produces the following output:
dz> run app.package.getpackagefromuid 10059
UID 10059 is com.mwr.dz
During development of a module, turning on debugging mode on the console by invoking it with --debug
may be useful. This command prints any errors produced by the loading or running of the module to the screen. For more advanced examples of developing modules, refer to the drozer documentation or read the source code of other similar modules for a deeper insight.
Android applications and their underlying frameworks were designed in a way that keeps them modular and able to communicate with each other. The communication between applications is performed in a well-defined manner that is strictly facilitated by a kernel module named binder, which is an Inter-Process Communication (IPC) system that started as the OpenBinder project and was completely rewritten in 2008 for use on Android. It is implemented as a character device located at /dev/binder
, which applications interact with through multiple layers of abstraction.
Android applications can make use of four standard components that can be invoked via calls to binder.
Services—Services are components that do not provide a graphical interface. They provide the facility to perform tasks that are long running in the background and continue to work even when the user has opened another application or has closed all activities of the application that contains the service. To view running services on your device go to the Running tab in the Application Manager, as shown in Figure 6.5.
Two different modes of operation exist for services. They can be started or bound to. A service that is started is typically one that does not require the ability to communicate back to the application that started it. A bound service provides an interface to communicate back results to the calling application. A started service continues to function even if the calling application has been terminated. A bound service only stays alive for the time that an application is bound to it.
http://www.sqlite.org/
), because Android makes the implementation of SQLite so easy due to their similar structures. Defining a content provider that can retrieve files and serve them is also possible. This may provide a preferable approach for applications that implement access control on the retrieval of their files from other applications.Each Android package contains a file named AndroidManifest.xml
in the root of the archive. This file defines the package configuration, application components, and security attributes. Figure 6.6 shows an example manifest.
Only components that are defined in the manifest file are usable inside the application, with the exception of broadcast receivers. One of the most important aspects of securing defined components in the manifest is using strongly configured permissions, which is explored in detail later in this chapter in “Understanding Permissions”.
An intent is a defined object used for messaging that is created and communicated to an intended application component. This communication is done through calls to binder. It includes all relevant information passed from the calling application to the desired application component and contains an action and data that is relevant to the request being made. A simple example of an application sending a request to open a particular URL in a browser would look as follows in code:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
startActivity(intent);
The preceding code creates a simple implicit intent to view a URL, and the startActivity()
function is called with the intent as a parameter. Any application’s activity that is able to respond to a VIEW
action on data that is formatted like a URL will be eligible to receive this intent. If only a single application can handle this intent, the intent is routed to that application by default. Otherwise, an application picker is shown. An application defines “intent filters” in its manifest, which catches the intents that are appropriate for its components. For example, if an activity in your application can handle HTTP links to websites, then an appropriate intent filter looks as follows:
<activity android:name="MyBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" />
</intent-filter>
</activity>
This snippet states that the activity named MyBrowserActivity
in this application can handle any intent with an action of android.intent.action.VIEW
and has the data scheme of http://
.
If you want to make sure that an intent that you send always reaches an application you intend and would not like the system to decide, then you can make use of explicit intents. Explicit intents specify the application and component that the intent should be delivered to. For example, if an application you created needs to explicitly open a URL in the Android browser application, you use the following code:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
String pack = "com.android.browser";
ComponentName comp = new ComponentName(pack, pack + ".BrowserActivity");
intent.setComponent(comp);
startActivity(intent);
You can try this from drozer without having to create a test application as follows:
dz> run app.activity.start --action android.intent.action.VIEW --data-uri
http://www.google.com --component com.android.browser
com.android.browser.BrowserActivity
drozer can be used to interact with all application components in the same easy manner. The following is an example of querying the system settings content provider from drozer that can be queried from any application:
dz> run app.provider.query content://settings/system
| _id | name | value |
| 1 | volume_music | 11 |
| 2 | volume_ring | 5 |
| 3 | volume_system | 7 |
| 4 | volume_voice | 4 |
| 5 | volume_alarm | 6 |
| 6 | volume_notification | 5 |
| 7 | volume_bluetooth_sco | 7 |
| 9 | mute_streams_affected | 46 |
| 10 | vibrate_when_ringing | 0 |
| 11 | dim_screen | 1 |
| 12 | screen_off_timeout | 60000 |
| 13 | dtmf_tone_type | 0 |
| 14 | hearing_aid | 0 |
| 15 | tty_mode | 0 |
| 16 | screen_brightness | 102 |
| 17 | screen_brightness_mode | 0 |
| 18 | window_animation_scale | 1.0 |
| 19 | transition_animation_scale | 1.0 |
| 20 | accelerometer_rotation | 1 |
| 21 | haptic_feedback_enabled | 1 |
| 22 | notification_light_pulse | 1 |
| 23 | dtmf_tone | 1 |
| 24 | sound_effects_enabled | 1 |
| 26 | lockscreen_sounds_enabled | 1 |
| 27 | pointer_speed | 0 |
| 28 | mode_ringer_streams_affected | 422 |
| 29 | media_button_receiver |
com.android.music/com.android.music.MediaButtonIntentReceiver |
| 30 | next_alarm_formatted | |
Chapter 7 shows many more examples of interacting with components using drozer. The ability to find vulnerabilities in application components requires a thorough understanding of their features and how they can be invoked.
This section explores the finer details of what happens under the hood when installing and running an application.
When an application is installed on an Android device, various tasks must be performed by the Package Manager Service and installd to ensure that the OS fully recognizes and knows how to work with it. The following is a high-level view of the steps:
libs
directory of application data directory and set appropriate file and folder permissionspackages.list
and packages.xml
This installation process was documented in depth by Ketan Parmar in a blog post at http://www.kpbird.com/2012/10/in-depth-android-package-manager-and.html#more
. For the purposes of the next discussion, one of the most important points to take away from the previous list is that when an Android package is installed, it is also stored on the device. User-level applications are stored in /data/app/
, and applications that came with the system image are under /system/app/
.
Here is an example listing of all the APK files present in the /data/app/
folder on an Android 4.4 emulator:
root@android:/data/app # ls -l *.apk
-rw-r--r-- system system ... ApiDemos.apk
-rw-r--r-- system system ... CubeLiveWallpapers.apk
-rw-r--r-- system system ... GestureBuilder.apk
-rw-r--r-- system system ... SmokeTest.apk
-rw-r--r-- system system ... SmokeTestApp.apk
-rw-r--r-- system system ... SoftKeyboard.apk
-rw-r--r-- system system ... WidgetPreview.apk
An important point to note is that each of the APK files listed is world readable according to their file permissions. This is the reason downloading them off a device or accessing them without having any particular level of privileges is possible. These same permissions are set on packages stored in the /system/app
and /system/priv-app
folders.
The Play Store used to have a Copy Protection function that you could enable when publishing an application. Applications that have been installed with this deprecated option reside in /data/app-private/
and are marked with the following file permissions, which do not allow world read access like the other third-party and system applications:
shell@android:/data/app-private # ls -l -a
-rw-r----- system app_132 629950 2014-04-18 23:40 com.mwr.dz-1.apk
These applications have essentially been installed using the FORWARD_LOCK
option provided by the Package Manager. You can replicate this installation option by using the following command from an ADB shell on your device:
shell@android:/data/local/tmp $ pm install -l agent.apk
This installs the package with FORWARD_LOCK
enabled, which places its APK in the /data/app-private
folder. It should be noted here that this form of “copy protection” is fundamentally broken and relies on users not having privileged access on their device. If users have privileged access they can retrieve the application and redistribute it by other means and install it on other devices without this mechanism having any bearing.
Upon installing an application, in addition to storing the APK on disk, the application attributes are cataloged in files located at /data/system/packages.xml
and /data/system/packages.list
. These files contain a list of all installed applications as well as other information important to the package. The packages.xml
file stores information about each installed application, including the permissions that were requested. This means that any changes made inside this file will directly affect the way that the OS treats the application. For instance, editing this file and adding or removing a permission from an application literally changes the application’s permissions. This fact may be used by application testers on Android to manipulate packages into a desirable state for testing or modification. It has also been used by Android “tinkerers” to build toolkits that allow for the “revocation” of permissions on chosen applications. This, of course, requires privileged access on the device because of the allocated file permissions on packages.xml
, which is shown here:
root@android:/ # ls -l /data/system/packages.xml
-rw-rw----- system system 57005 2014-04-18 21:38 packages.xml
Another procedure that takes place at installation time is the optimization and caching of the package’s DEX file. The classes.dex
file is extracted from the APK, optimized using the dexopt
utility, and then stored in the Dalvik cache folder. This folder exists at $ANDROID_DATA/dalvik-cache
on every device (which is normally /data/dalvik-cache
). It is optimized so that minimal instruction checking needs to be performed at runtime, and other such pre-execution checks can be performed on the bytecode. For more information about the specific tasks run by dexopt
go to https://cells-source.cs.columbia.edu/plugins/gitiles/platform/dalvik/+/android-4.3_r0.9/docs/dexopt.html
. The process of creating an ODEX may take time, and this could degrade first-run performance for applications. This is why most system applications on an Android image come pre-”odexed,” or a process of odexing is performed on first startup of the OS. If you explore the filesystem, notice that APKs in the /system/app
directory may have an accompanying file with the same name and an extension of .odex
. These are the application’s “optimized DEX” files that are stored outside of the package archive.
Pre-optimizing the DEX files means that when applications are run they do not need to be processed and stored in the cache first, which improves the loading time of the application. The processing procedure used by the dexopt
utility for converting a DEX to an ODEX is a complex one. It involves parsing each instruction and checking for redundancies that can be replaced and using inline native replacements for methods that are called frequently. This process makes these ODEX files highly dependent on the specific version of the VM in use on the device. As a consequence, it is unlikely that an ODEX file will work on another device, unless the device software type and versions are identical.
Android uses an unusual procedure for starting new applications. It works by having a single application VM started when the OS boots that listens for requests to launch new applications. When it receives a request, it simply fork()
’s itself with new application parameters and code to run. The process that listens for new application requests is aptly named zygote. This technique makes the process of creating new application VMs efficient, as core libraries are shared between VMs. When a user clicks on an application icon, an intent is formulated and sent using startActivity()
. This is handled by the Activity Manager Service, which sends a message to zygote with all the parameters required to start the application. Zygote listens on a UNIX socket located at /dev/socket/zygote
and has the following permissions, which allow only the system UID or root to interact with it:
root@android:/ # ls -l /dev/socket/zygote
srw-rw---- root system 2014-05-04 11:05 zygote
When an application is started, the Dalvik cache is checked to see whether the application’s DEX file has been optimized and stored. If it has not, the system has to perform this optimization, which impacts the application’s loading time.
The foundation of the Android application security model is that no two applications running on the same device should be able to access each other’s data without authorization. They should also not be able to affect the operation of the other application adversely or without the appropriate consent. This concept is the basis of an application sandbox.
In theory, this concept is simple but the practical implementation of what defines an authorized action or not is complex. Keeping an open and extendible environment while maintaining security means that the security model has to stretch further than just the application code itself. An application would need to know whether another application is authorized to perform an action and so the concept of application identity is important.
Android has built-in ways of checking which entity created an application, and using this information could determine what privilege context it can be assigned on the device. After all, if any application author could claim to be Google, enforcing any trust boundaries would not be possible and every application would have to be afforded the same level of trust on the device. An application author’s identity is managed by code signing.
The signing of an Android package is done cryptographically through the use of digital certificates whose private key is only held by the application developers. Code signing is used to prove the identity of an application’s author in order to designate a degree of trust to it in other aspects of the security model. Signing of a package is mandatory, even if the certificate used is the default debug certificate that can only be used during development.
To generate your own X.509 certificate that can be used for signing, use the following command:
$ keytool -genkey -v -keystore mykey.keystore -alias alias_name -keyalg RSA
-keysize 2048 -validity 10000
Signing your unsigned application can be performed using the following command, making use of your newly created certificate:
$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore
mykey.keystore application.apk alias_name
The certificate information of an application is contained within the CERT.RSA
file in the META-INF
folder inside every Android package.
You can view the certificate using any tool capable of parsing the DER format. Here is an example of using openssl
to display the certificate and its attributes:
$ openssl pkcs7 -inform DER -in CERT.RSA -text -print_certs
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 10623618503190643167 (0x936eacbe07f201df)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=California, L=Mountain View, O=Android,
OU=Android, CN=Android/emailAddress=android@android.com
Validity
Not Before: Feb 29 01:33:46 2008 GMT
Not After : Jul 17 01:33:46 2035 GMT
Subject: C=US, ST=California, L=Mountain View, O=Android,
OU=Android, CN=Android/emailAddress=android@android.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:d6:93:19:04:de:c6:0b:24:b1:ed:c7:62:e0:d9:
d8:25:3e:3e:cd:6c:eb:1d:e2:ff:06:8c:a8:e8:bc:
a8:cd:6b:d3:78:6e:a7:0a:a7:6c:e6:0e:bb:0f:99:
35:59:ff:d9:3e:77:a9:43:e7:e8:3d:4b:64:b8:e4:
fe:a2:d3:e6:56:f1:e2:67:a8:1b:bf:b2:30:b5:78:
c2:04:43:be:4c:72:18:b8:46:f5:21:15:86:f0:38:
a1:4e:89:c2:be:38:7f:8e:be:cf:8f:ca:c3:da:1e:
e3:30:c9:ea:93:d0:a7:c3:dc:4a:f3:50:22:0d:50:
08:07:32:e0:80:97:17:ee:6a:05:33:59:e6:a6:94:
ec:2c:b3:f2:84:a0:a4:66:c8:7a:94:d8:3b:31:09:
3a:67:37:2e:2f:64:12:c0:6e:6d:42:f1:58:18:df:
fe:03:81:cc:0c:d4:44:da:6c:dd:c3:b8:24:58:19:
48:01:b3:25:64:13:4f:bf:de:98:c9:28:77:48:db:
f5:67:6a:54:0d:81:54:c8:bb:ca:07:b9:e2:47:55:
33:11:c4:6b:9a:f7:6f:de:ec:cc:8e:69:e7:c8:a2:
d0:8e:78:26:20:94:3f:99:72:7d:3c:04:fe:72:99:
1d:99:df:9b:ae:38:a0:b2:17:7f:a3:1d:5b:6a:fe:
e9:1f
Exponent: 3 (0x3)
X509v3 extensions:
X509v3 Subject Key Identifier:
48:59:00:56:3D:27:2C:46:AE:11:86:05:A4:74:19:AC:09:CA:8C:11
X509v3 Authority Key Identifier:
keyid:48:59:00:56:3D:27:2C:46:AE:11:86:05:A4:74:19:AC:09:CA:8C:11
DirName:/C=US/ST=California/L=Mountain
View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com
serial:93:6E:AC:BE:07:F2:01:DF
X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
7a:af:96:8c:eb:50:c4:41:05:51:18:d0:da:ab:af:01:5b:8a:
76:5a:27:a7:15:a2:c2:b4:4f:22:14:15:ff:da:ce:03:09:5a:
bf:a4:2d:f7:07:08:72:6c:20:69:e5:c3:6e:dd:ae:04:00:be:
29:45:2c:08:4b:c2:7e:b6:a1:7e:ac:9d:be:18:2c:20:4e:b1:
53:11:f4:55:d8:24:b6:56:db:e4:dc:22:40:91:2d:75:86:fe:
88:95:1d:01:a8:fe:b5:ae:5a:42:60:53:5d:f8:34:31:05:24:
22:46:8c:36:e2:2c:2a:5e:f9:94:d6:1d:d7:30:6a:e4:c9:f6:
95:1b:a3:c1:2f:1d:19:14:dd:c6:1f:1a:62:da:2d:f8:27:f6:
03:fe:a5:60:3b:2c:54:0d:bd:7c:01:9c:36:ba:b2:9a:42:71:
c1:17:df:52:3c:db:c5:f3:81:7a:49:e0:ef:a6:0c:bd:7f:74:
17:7e:7a:4f:19:3d:43:f4:22:07:72:66:6e:4c:4d:83:e1:bd:
5a:86:08:7c:f3:4f:2d:ec:21:e2:45:ca:6c:2b:b0:16:e6:83:
63:80:50:d2:c4:30:ee:a7:c2:6a:1c:49:d3:76:0a:58:ab:7f:
1a:82:cc:93:8b:48:31:38:43:24:bd:04:01:fa:12:16:3a:50:
57:0e:68:4d
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
-----END CERTIFICATE-----
You can also use the Java keytool
utility with the following parameters:
$ keytool -printcert -file CERT.RSA
Application certificates are not verified by the Android operating system in any way and do not need to be issued by a certain Certificate Authority (CA) like other platforms. In fact, the majority of applications make use of a self-signed signing certificate and the OS does not check this certificate against any stored or online repository. The signing certificate is checked only when the application gets installed and if the certificate subsequently expires, the application will still run as normal. Google recommends that signing certificates be created with a validity period of 25 years or longer to support seamless updates to your application (see http://developer.android.com/tools/publishing/app-signing.html#considerations
). Google Play enforces that the expiration on the signing certificate used to sign published applications is after October 22, 2033. This again is to support updates to your application.
With all the preceding information at hand, one can observe that the Android OS does not follow a conventional Public Key Infrastructure (PKI) process. It does not query any infrastructure to check the authenticity of an author’s claimed identity. This does not mean that the model is flawed in any way, it is simply different. Certificates are used for doing comparisons against other applications claiming to be written by the same author in order to establish trust relationships as well as for accepting application updates. This security model depends highly on the operating system’s ability to compare these application certificates and deny forged applications the associated privilege of a certain certificate. This chapter provides more concrete examples later when the permission model is introduced and protection levels are discussed. As noted by Nikolay Elenkov in a blog post at http://nelenkov.blogspot.com/2013/05/code-signing-in-androids-security-model.html
, the certificate check is a literal binary comparison of the two certificates being compared. The function that handles this check is in /core/java/android/content/pm/Signature.java
of the Android source tree, and the specific check is highlighted in the code:
@Override
public boolean equals(Object obj) {
try {
if (obj != null) {
Signature other = (Signature)obj;
return this == other‖ Arrays.equals(mSignature,
other.mSignature
}
} catch (ClassCastException e) {
}
return false;
}
This means that issuing an update for your application is only possible if it has been signed with exactly the same certificate. If a developer loses his signing certificate, he can no longer issue updates to his users. Instead, he would have to publish their latest application update as a new application that has been signed with their new certificate. This means that users would have to re-download the newly published application as if it were a new application altogether. This speaks to the importance of keeping your signing certificate safe and backed up appropriately.
For the official Android Developer documentation from which some of this information has been taken, please visit http://developer.android.com/tools/publishing/app-signing.html
.
A number of vulnerabilities have been discovered in the way that the validation of signatures is performed on APK files. The presented vulnerabilities affect devices up to and including Android 4.3.
In February 2013, Bluebox Security discovered the first vulnerability in the way that Android application contents are cryptographically verified. This is commonly known as the “Master Key” vulnerability. The discovered bug allowed for the arbitrary modification of an APK file without invalidating the cryptographic signature.
The vulnerability was that if a duplicate filename occurred in the zip archive, only the first file entry’s hash was checked. The MANIFEST.MF
file included in each APK contains all the hashes of each file present in the rest of the archive. Here is an example:
$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.0 (Android SignApk)
Name: res/layout-land/activity_main.xml
SHA1-Digest: tHBSzedjV31QNPH6RbNFbk5BW0g=
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: itzF8BBhIB+iXXF/RtrTdHKjd0A=
...
Name: AndroidManifest.xml
SHA1-Digest: HoN6bMMe9RH6KHGajGz3Bn/fWWQ=
...
Name: classes.dex
SHA1-Digest: 6R7zbiNfV8Uxty8bvi4VHpB7A8I=
...
However, it is possible in the zip format to include two files with the same name. This bug exploits the fact that the hash of the first file is checked in Java code, but then the second file with the same name ends up being used by the C++ implementation when deployed to the device. This means that the second file can contain completely new contents and the validation of the APK still passes all checks. This vulnerability makes taking a legitimate application and including malicious code without breaking the signature possible. This vulnerability can also be used to gain system
(and sometimes root) access on a device by modifying and reinstalling a system
application. This use case is explained later in this chapter in “Root Explained”.
A basic proof of concept was created by Pau Oliva to demonstrate how simple the process is to repackage an APK with modified code without breaking the signature. You can find it at https://gist.github.com/poliva/36b0795ab79ad6f14fd8
. A more comprehensive tool that exploits this issue and other discovered code signing vulnerabilities was written by Ryan Welton and is at https://github.com/Fuzion24/AndroidZipArbitrage/
.
Just two days after bug #8219321 was revealed, a patch was committed (see https://android.googlesource.com/platform/libcore/+/9edf43dfcc35c761d97eb9156ac4254152ddbc55%5E%21/
) that revealed another way that could be used to manipulate an APK to the same effect as the Master Key bug. This time, the vulnerability existed in the way that the length of the “extra” field in the local file headers of an entry in the zip archive was calculated in code. Figure 6.8 shows a simplified view of a zip archive containing a single file.
The format provides for a 16-bit length “extra” field, but in the Java code the length was read as a signed 16-bit number. This meant that overflowing this value into a negative length was possible. Exploitation techniques presented by the community were quite involved but putting it simply, the discrepancy between how the Java and C++ implementation parsed these values allowed for the injection of altered files that passed the signature validation. Jay Freeman covers various exploitation techniques in detail at http://www.saurik.com/id/18
.
In July 2013, another vulnerability affecting signature verification of packages was patched by Google. To find the exact commit go to https://android.googlesource.com/platform/libcore/+/2da1bf57a6631f1cbd47cdd7692ba8743c993ad9%5E%21/
. The length of the “name” field in the local file headers of an entry in the zip file was found to not be checked by the Java verification code. Rather, this length was calculated from another place in the zip file, known as the “central directory.” This can be exploited by setting a large “name” value in the local file header, which is not checked by the Java implementation, and putting the correct “name” in the “central directory.” The C++ code checks the local file header and executes code that is appended. However, the Java code verifies the signature of the entry according to the length of the “name” in the “central directory.” Building an archive with entries that satisfy both conditions and allow for the execution of arbitrary code while maintaining the signatures of the files in the package is therefore possible. Once again, Jay Freeman provides an excellent in-depth write-up of this issue at http://www.saurik.com/id/19
.
Imagine if every application you have installed on your device could access your contacts, SMS messages, GPS location, or any other information. This would be a scary prospect in a world where the average Android user has 26 or more applications installed (according to http://www.statista.com/topics/1002/mobile-app-usage/chart/1435/top-10-countries-by-app-usage/
). This section will discuss how Android implements its permission model and assigns applications the rights to request access to device resources.
Android employs a fine-grained privilege model for applications. Applications have to request “permission” to access certain information and resources on a device. A user who installs an application from the Play Store is presented with an activity displaying the types of information and hardware that the application can access on your device. However, this information is abstracted away from the technical details in newer versions of the Play Store and does not display the details of the actual permission requested. Figure 6.9 shows an example of clicking the “Permission details” option in the Play Store on the Twitter (https://twitter.com/
) application.
Each defined permission has a unique name that is used when referring to it in code as well as a friendly label and a more verbose description about what it is for. This means that when an application permission activity shows “Read your text messages (SMS or MMS)” that it actually translates back to the permission with the name android.permission.READ_SMS
. If you examine the AndroidManifest .xml
file associated with an application, notice the XML describing the defined and requested permissions respectively as <permission>
and <uses-permission>
tags.
In drozer, to find the permissions that have been requested and defined by a certain application, run the app.package.info
module with the package name as the argument (in this case the Android browser):
dz> run app.package.info -a com.android.browser
Package: com.android.browser
Application Label: Browser
Process Name: com.android.browser
Version: 4.4.2-938007
Data Directory: /data/data/com.android.browser
APK Path: /system/app/Browser.apk
UID: 10014
GID: [3003, 1028, 1015]
Shared Libraries: null
Shared User ID: null
Uses Permissions:
- android.permission.ACCESS_COARSE_LOCATION
- android.permission.ACCESS_DOWNLOAD_MANAGER
- android.permission.ACCESS_FINE_LOCATION
- android.permission.ACCESS_NETWORK_STATE
- android.permission.ACCESS_WIFI_STATE
- android.permission.GET_ACCOUNTS
- android.permission.USE_CREDENTIALS
- android.permission.INTERNET
- android.permission.NFC
- android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS
- android.permission.SET_WALLPAPER
- android.permission.WAKE_LOCK
- android.permission.WRITE_EXTERNAL_STORAGE
- android.permission.WRITE_SETTINGS
- android.permission.READ_SYNC_SETTINGS
- android.permission.WRITE_SYNC_SETTINGS
- android.permission.MANAGE_ACCOUNTS
- android.permission.READ_PROFILE
- android.permission.READ_CONTACTS
- com.android.browser.permission.READ_HISTORY_BOOKMARKS
- com.android.browser.permission.WRITE_HISTORY_BOOKMARKS
- com.android.launcher.permission.INSTALL_SHORTCUT
- android.permission.READ_EXTERNAL_STORAGE
Defines Permissions:
- com.android.browser.permission.PRELOAD
Searching for applications that have requested a particular permission using the permission filter is also possible. For verbose package information, make use of the app.package.info
module or for a short list, use app.package.list
in the following manner, providing the permission of interest as a parameter:
dz> run app.package.list -p android.permission.READ_SMS
com.android.phone (Phone)
com.android.mms (Messaging)
com.android.gallery (Camera)
com.android.camera (Camera)
Requesting certain permissions may cause the application’s user identifier to be added to a Linux group. For instance, requesting the permission android.permission.INTERNET
puts the application in the inet
group. This mapping is shown here:
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
These mappings are defined in /system/etc/permissions/platform.xml
. Other permissions may not equate to any group amendments being made and are simply a form of access control. For instance, the READ_SMS
permission does not allow the application to read the SMS database directly, but rather allows it to query content://sms
and other related content providers. The following drozer command allows a user to query which content providers require the READ_SMS
permission:
dz> run app.provider.info -p android.permission.READ_SMS
Package: com.android.mms
Authority: com.android.mms.SuggestionsProvider
Read Permission: android.permission.READ_SMS
Write Permission: null
Content Provider: com.android.mms.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
Path: /search_suggest_shortcut
Type: PATTERN_PREFIX
Read Permission: android.permission.GLOBAL_SEARCH
Write Permission: null
Package: com.android.providers.telephony
Authority: mms
Read Permission: android.permission.READ_SMS
Write Permission: android.permission.WRITE_SMS
Content Provider: com.android.providers.telephony.MmsProvider
Multiprocess Allowed: False
Grant Uri Permissions: True
Uri Permission Patterns:
Path: /part/
Type: PATTERN_PREFIX
Path: /drm/
Type: PATTERN_PREFIX
Authority: sms
Read Permission: android.permission.READ_SMS
Write Permission: android.permission.WRITE_SMS
Content Provider: com.android.providers.telephony.SmsProvider
Multiprocess Allowed: False
Grant Uri Permissions: False
Authority: mms-sms
Read Permission: android.permission.READ_SMS
Write Permission: android.permission.WRITE_SMS
Content Provider: com.android.providers.telephony.MmsSmsProvider
Multiprocess Allowed: False
Grant Uri Permissions: False
...
When an application attempts to access one of the content providers listed previously, the operating system will check that the calling application holds the required permission. If it does not hold the appropriate permission, a permission denial results. An example of querying content://sms
from drozer, which does not hold the READ_SMS
permission by default, is shown here:
dz> run app.provider.query content://sms
Permission Denial: opening provider
com.android.providers.telephony.SmsProvider from ProcessRecord{b1ff0638
1312:com.mwr.dz:remote/u0a56} (pid=1312, uid=10056) requires
android.permission.READ_SMS or android.permission.WRITE_SMS
Each permission that is defined has an associated attribute known as its protection level. Protection levels control the conditions under which other applications can request the permission. Naturally, some permissions are more dangerous than others and this should reflect in the assigned protection level. For instance, third-party applications should never be granted the ability to install new applications (using the android.permission.INSTALL_PACKAGES
permission) and the system should not allow it. An author of a number of applications may want to share information or invoke functionality between her applications at runtime in a secure manner. Both of these scenarios can be achieved by selecting the correct protection level on defined permissions. Table 6.2 describes all the available protection levels that can be set on a newly defined permission.
Table 6.2 Permission Protection Levels
PROTECTION LEVEL | INTEGER VALUE | DESCRIPTION |
normal | 0x0 | The default value for a permission. Any application may request a permission with this protection level. |
dangerous | 0x1 | Indicates that this permission has the ability to access some potentially sensitive information or perform actions on the device. Any application may request a permission with this protection level. |
signature | 0x2 | Indicates that this permission can only be granted to another application that was signed with the same certificate as the application that defined the permission. |
signatureOrSystem | 0x3 | This is the same as the signature protection level, except that the permission can also be granted to an application that came with the Android system image or any other application that is installed on the /system partition. |
system | 0x10 | This permission can only be granted to an application that came with the Android system image or any other application that is installed in particular folders on the /system partition. |
development | 0x20 | This permission can be granted from a privileged context to an application at runtime. This scarcely documented feature was discussed at https://code.google.com/p/android/issues/detail?id=34785 . |
As a practical example of protection levels in action, take a look at what happens when you compile a new drozer agent with the INSTALL_PACKAGES
permission and attempt to install it.
$ drozer agent build --permission android.permission.INSTALL_PACKAGES
Done: /tmp/tmp2RdLTd/agent.apk
$ adb install /tmp/tmp2RdLTd/agent.apk
2312 KB/s (653054 bytes in 0.275s)
pkg: /data/local/tmp/agent.apk
Success
The package installs successfully but logcat
shows a log entry from the Package Manager saying the following:
W/PackageManager( 373): Not granting permission
android.permission.INSTALL_PACKAGES to package com.mwr.dz
(protectionLevel=18 flags=0x83e46)
It refused to grant the INSTALL_PACKAGES
permission. This can be confirmed in drozer by displaying the permissions held by the agent:
dz> permissions
Has ApplicationContext: YES
Available Permissions:
- android.permission.INTERNET
It is quite obvious that this happened because of the protection level set on the INSTALL_PACKAGES
permission, which is signature|system
(which equates to an integer protection level of 18. This value comes from performing a Boolean OR operation on 0x02 and 0x10). The drozer agent was not signed by the same certificate as the application that defined the INSTALL_PACKAGES
permission (which is usually the package named android
) and it did not come as part of the system image. Hence, the request to attain this permission was rejected by the OS. If one application permission request is rejected, the application will still function correctly as long as it handles this rejection gracefully when attempting to use functionality provided by this permission at runtime. If the application does not handle this scenario gracefully it may result in an application crash.
Third-party applications that do not have any intention of sharing data or functionality with applications from other developers should always define permissions with the signature
protection level. This ensures that another developer cannot write an application that requests your permission and gains access to your exported components. This may not constitute a direct risk to your application or its data depending on what the permission is used for; however, in most cases this is not desirable from a security perspective. Using the signature
protection level does not affect the application’s ability to integrate or communicate with other applications created by the same developer, as these applications would be signed with the same certificate. This is why it is so important that Android packages are signed cryptographically, or else how would Android know which application is fit to hold a particular permission? In fact, Android will not allow you to install an application that is not signed and doing so from ADB will result in an error with the code INSTALL_PARSE_FAILED_NO_CERTIFICATES
. The use of permissions with protection levels provides a strong foundation for application security for developers; however, the foundation’s strength depends on the correct configuration of protection levels.
The Android application sandbox comprises multiple measures that were designed to ensure that one application cannot harm another or read its data without being explicitly allowed to do so.
Start by looking at what measures are in place from a native Linux viewpoint. As discussed earlier in this chapter, each application runs as its own user on Android. This provides a strong model for filesystem security that is inherited from UNIX. Each application’s private data directory is marked with the file permissions that only allow the application’s user to access it. Here is an example of the drozer agent’s data directory permissions:
drwxr-x--x u0_a59 u0_a59 2014-05-11 18:49 com.mwr.dz
Attempting to access this folder as any other non-privileged user results in a permission denial, as shown in this example:
shell@android:/ $ ls -l /data/data/com.mwr.dz
opendir failed, Permission denied
However, note that the folder is marked as world executable. This means that any other files or subfolders inside this directory with lax permissions set on them will result in the exposure of these files to any user (and hence application) on the system. Chapter 7 explores this topic in detail.
An exception to the rule that each application runs as its own user is when an application requests to use a sharedUserId
. This can be done by using the manifest entry android:sharedUserId="requested.userid.name"
. This request is granted to an application only if it is signed by the same certificate as the first application that requested this user identifier. If a set of applications use this option, they will be running under the exact same UID. This means that there will be no separation between them and they can freely read and write to each other’s private data directories. There are even configuration options available to accommodate running these applications in the same process. This means that every one of these applications effectively hold all the permissions of the entire collection of applications running under the same user identifier.
An example of mapping what the collective permissions are of applications making use of the android.media sharedUserId
is shown in drozer:
dz> run app.package.shareduid -u 10005
UID: 10005 (android.media:10005)
Package: com.android.providers.downloads
Package: com.android.providers.downloads.ui
Package: com.android.gallery
Package: com.android.providers.media
Permissions: android.permission.WRITE_EXTERNAL_STORAGE,
android.permission.ACCESS_ALL_DOWNLOADS, android.permission.WAKE_LOCK,
android.permission.WRITE_SETTINGS, android.permission.WAKE_LOCK,
android.permission.CAMERA, android.permission.RECEIVE_BOOT_COMPLETED,
android.permission.ACCESS_DOWNLOAD_MANAGER,
android.permission.ACCESS_NETWORK_STATE,
android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS,
android.permission.WRITE_MEDIA_STORAGE,
android.permission.WRITE_EXTERNAL_STORAGE, android.permission.RECORD_AUDIO,
android.permission.ACCESS_FINE_LOCATION,
android.permission.RECEIVE_BOOT_COMPLETED, android.permission.INTERNET,
android.permission.READ_EXTERNAL_STORAGE, android.permission.SET_WALLPAPER,
android.permission.INTERACT_ACROSS_USERS, android.permission.READ_SMS,
android.permission.ACCESS_MTP, android.permission.READ_EXTERNAL_STORAGE,
android.permission.ACCESS_CACHE_FILESYSTEM,
android.permission.MODIFY_NETWORK_ACCOUNTING,
android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS,
android.permission.MANAGE_USERS, android.permission.READ_EXTERNAL_STORAGE,
android.permission.ACCESS_ALL_DOWNLOADS,
android.permission.CONNECTIVITY_INTERNAL,
android.permission.WRITE_EXTERNAL_STORAGE,
android.permission.UPDATE_DEVICE_STATS
This drozer module can be used to retrieve the collective permissions that all four packages shown effectively hold. You can find more about the sharedUserId
attribute at http://developer.android.com/guide/topics/manifest/manifest-element.html#uid
.
Other application sandbox features are controlled by binder. Every application has access to binder and is able to communicate with it. Specialized IPC parcels are sent to it by applications and passed to the Activity Manager Service, which checks whether the calling application holds the permission required to perform the requested task. For example, if an application had to request that an exported activity from another application be started, the OS would check that the calling application holds the appropriate permission to start the activity. All Android API calls to exposed application components are controlled and the permission model is strictly enforced when accessing them.
Some application permissions are not enforced by binder, but rather by the Linux group assigned to an application. As explained in the “Understanding Permissions” section, requesting some permissions may get your application put in a certain group. For instance, inet
when requesting android.permission .INTERNET
. This means that accessing the network from an application would be governed by the OS’s native security checks and not binder.
In summary, Android does not implement a sandbox as you would expect. People often think of a sandbox as a completely separate virtual machine environment like one would run a sample of malware inside to make sure that it cannot infect the host system. Instead, Android uses only the strength of Linux user and group separation security enforced by the kernel as well as special IPC calls to binder to uphold the application capability security model. It does not provide a completely segregated environment for each application as some have thought.
Full disk encryption (FDE) is when the contents of an entire drive or volume are encrypted and not only selected individual files. This is useful because it requests the password from the user at startup and from then onward transparently encrypts and decrypts all data read and written to the disk. This serves as protection against stolen or lost disks that have been powered down. Part of the benefit is being able to defeat common forensics techniques such as disk imaging and booting the disk attached to another OS in order to browse the contents. Widely accepted FDE software makes use of a user-provided password in order to derive the key used for encryption.
FDE has been available on Android since version 3.0 (Honeycomb). It makes use of the dm-crypt
module in the kernel to transparently encrypt and decrypt data on the block device layer. This is the same implementation used on modern Linux systems and is a tried and trusted form of FDE. The encryption suite used under the hood is aes-cbc-essiv:sha256
, which had no publicly acknowledged weaknesses at the time of writing. Filesystem encryption is not enabled by default on Android versions prior to 5.0 (Lollipop) and has to be enabled by the user in the encryption options in the security section of the settings application. The user’s unlock screen PIN or password is the same one that is used to encrypt the FDE password. This means that Android generates a password, and this is encrypted using a key that is derived from the user’s screen unlock PIN or password. The key used to encrypt the FDE password is derived from the PIN or user’s password using 2000 rounds of PBKDF2 on versions of Android prior to 4.4 (KitKat). KitKat onwards implements scrypt
for key derivation instead of PBKDF2 to make brute-forcing of long PIN numbers and passwords extremely difficult. The use of this intermediary password allows users to change their unlock screen password without having to change the actual FDE password.
This solution encrypts only the /data
partition on an Android device. This means that the private data directory of applications and other sensitive user information is encrypted. Performing disk imaging techniques on the entire filesystem (as one would do in a forensic investigation) would yield access to only this encrypted data and not to any of the files in the /data
folder or any of its subfolders. An interesting downfall is that the Secure Digital (SD) card is not included as part of the standard FDE scheme used by Android. Some handset manufacturers have included the encryption of the SD card as part of their customizations to Android; however, these implementations are proprietary and non-standardized. This means that gaining physical access to an Android device that has not implemented SD card encryption will allow the retrieval of all files stored on the SD card. Some applications have been discovered to use the SD card for storage of sensitive files, so this may prove useful to an attacker.
Disk encryption by nature protects only data at rest. This means that if an attacker had to gain code execution on a device that is making use of FDE on Android, he would not notice a difference in the data he could access. He would find that the data he retrieves is not encrypted in any manner, as it would transparently be decrypted for him by dm-crypt
. Disk encryption does, however, protect users when an encrypted device has been stolen and the attacker does not have code execution or access to the device.
For additional information about the technical aspects of FDE on Android check out http://source.android.com/devices/tech/encryption/
and http://nelenkov.blogspot.com/2014/10/revisiting-android-disk-encryption.html
.
Attackers have exploited native memory corruption issues since the first operating systems, and Android is no exception. Where native code is running in applications, the potential exists to corrupt memory structures to take control of it. To combat the trivial exploitation of native bugs, OS developers began to implement preventative and reactive measures known as exploit mitigations. These measures result from the attitude of “we will not be able to secure all code, so why not make it harder to exploit these issues instead.”
Many of the mitigations that Android makes use of are inherited from the Linux kernel. Applications on Android can make use of native libraries that are built in C/C++ or execute binaries that are included in their assets. Code that contains vulnerabilities and is in a code path that provides an entry point for an attacker could be exploited by the attacker to take control of the application. Note that if an attacker had to successfully exploit a native component, he would gain the privileges of the application itself and nothing more. In other words, native code runs under the exact same context as the calling application.
A simple example of this scenario is the Android browser. All the parsing performed by the Android browser is done inside a native library. If an attacker can provide malformed HTML, JavaScript, CSS, or any other element that requires parsing from this native component, he could potentially cause the corruption of memory structures within the browser application. If this is done in a finely crafted manner, an attacker can cause new code to be executed by the application. This is why including any and all exploit mitigations on the Android OS is important to protect users from compromise.
Exploit mitigations have been included since the very first publicly available version of Android. However, mitigations that are comparable with modern desktop operating systems have only been available in Android since version 4.0 (Ice Cream Sandwich). This point may be argued, but the fact is that writing an exploit for a remotely exploitable memory corruption vulnerability on a Jelly Bean (or newer) device is a time-consuming task that often requires the chaining of multiple vulnerabilities. Exploit mitigations do not make it impossible to write an exploit for a vulnerability but rather make it a lot more expensive to do so. Table 6.3 lists some of the truly noteworthy mitigations introduced to Android.
Table 6.3 Noteworthy Exploit Mitigations Included in Android
EXPLOIT MITIGATION | VERSION INTRODUCED | EXPLANATION |
Stack cookies | 1.5 | Protects against basic stack-based overflows by including a “canary” value after the stack that is checked. |
safe_iop |
1.5 | Provides a library that helps reduce integer overflows. |
dlmalloc extensions |
1.5 | Helps prevent double free() vulnerabilities and other common ways to exploit heap corruptions. |
calloc extensions |
1.5 | Helps prevent integer overflows during memory allocations. |
Format string protections | 2.3 | Helps prevent the exploitation of format string vulnerabilities. |
NX (No eXecute) |
2.3 | Prevents code from running on the stack or heap. |
Partial ASLR (Address Space Layout Randomization) | 4.0 | Randomizes the location of libraries and other memory segments in an attempt to defeat a common exploitation technique called ROP (Return-Oriented Programming). |
PIE (Position Independent Executable) support | 4.1 | Supports ASLR to ensure all memory components are fully randomized. Effectively ensures that app_process and linker are randomized in memory so that these cannot be used as a source of ROP gadgets. |
RELRO (RELocation Read-Only) and BIND_NOW |
4.1 | Hardens data sections inside a process by making them read-only. This prevents common exploitation techniques such as GOT (Global Offset Table) overwrites. |
FORTIFY_SOURCE (Level 1) |
4.2 | Replaces common C functions that are known to cause security problems with “fortified” versions that stop memory corruption from taking place. |
SELinux (Permissive mode) | 4.3 | Allows for fine-grained access control security policies to be specified. When properly configured policies are present, it can provide a significant improvement in the security model. Permissive mode means that security exceptions are not enforced when a policy is breached. This information is only logged. |
SELinux (Enforcing mode) | 4.4 | Enforcing mode means that the specified policies are imposed. |
FORTIFY_SOURCE (Level 2) |
4.4 | Replaces additional functions with their “fortified” versions. |
Note that using the latest NDK (see https://developer.android.com/tools/sdk/ndk/index.html
) and targeting the latest Android API version automatically enables all the exploit mitigations discussed in Table 6.3. These mitigations can also be turned off explicitly, but there is seldom a need to do that.
You can find more information about the exploit mitigations and other security features introduced in each version at https://source.android.com/devices/tech/security/
and in the relevant source code commit logs.
Table 6.4 Noteworthy Exploit Mitigations to Prevent a Non-privileged User From Exploiting a Vulnerability and Gaining Root Access
EXPLOIT MIGITATION | VERSION INTRODUCED | EXPLANATION |
mmap_min_addr |
2.3 | This value specifies the minimum virtual address that a process is allowed to mmap and was set to 4096. This stops processes from mapping the zero page and causing a null pointer dereference in order to execute arbitrary code as root. |
kptr_restrict and dmesg_restrict |
4.1 | Avoids leaking kernel addresses when displaying /proc/kallsyms and /proc/kmsg to users. |
mmap_min_addr update |
4.1.1 | This value was increased to 32768. |
installd hardening |
4.2 | The installd daemon no longer runs as the root user. This means that any compromise of this component will not result in a privilege escalation to root. |
Init script O_NOFOLLOW |
4.2 | This helps prevent against symbolic-link related attacks. |
Init script no longer parses /data/local.prop |
4.2 | Using some vulnerability to add ro.secure=0 or ro.kernel .qemu=1 to /data/local.prop was a common way of escalating from the system user to root as these values cause adbd to be started as root. |
Removed setuid/setguid programs |
4.3 | Removed all setuid/setgid programs and added support for filesystem capabilities instead. |
Restrict setuid from installed apps |
4.3 | The /system partition is mounted as nosuid for all processes that were spawned by zygote. This means that installed applications cannot abuse vulnerabilities in any SUID binaries to gain root access. |
On Android, by default no way exists to run an application or some task within it as the root user. This simple fact has led to entire communities of researchers dedicating their time to finding ways to obtain root on various Android devices. There are also very many misconceptions about what rooting your device entails technically and why it is possible (or not) on certain devices. This section sheds light on some of the common rooting methods and gives a technical breakdown of each.
A typical objective of rooting an Android device is so that you can put a su
binary in a directory on the PATH
(for example, /system/bin
or /system/xbin
). The job of the su
binary is to allow a user to switch security contexts and become another user, including root. The su
binary should, however, first determine whether the user should be allowed to impersonate the requested user. The required criteria is different on conventional Linux systems from the methods used on commonly found su
packages on Android, but one fact that remains the same is that the su
binary needs to be running as root in order to allow the change to another user context. The following shows the file permissions on su
on a modern Linux system:
$ ls -l /bin/su
-rwsr-xr-x 1 root root 36936 Feb 17 04:42 /bin/su
These permissions tell you that any user can execute this binary and when she does she will be running it as the root user. This is a Set User Identifier (SUID) binary, which sets the user ID to the file’s owner upon execution. You can invoke it from within an application by using code similar to this:
Runtime.getRuntime().exec(new String[]{"su", "-c", "id"});
This executes the id
command as the root user and works because the su
binary is on the PATH
, which means that the OS knows where to find it on the system. When using su
on a Linux system, it asks for the target user’s password to authenticate the action. However, on Android a different approach is commonly taken because the root user does not have a password. Different root manager application developers use different technical methods but they both come down to the same concept for the user. When an application executes su
, an activity is displayed to the user requesting the user’s permission to grant the requesting application root context. These applications usually display information about the application requesting root and what it is attempting to execute. Figure 6.10 shows an example of a prompt from the SuperSU application.
This application works by using a custom version of su
that sends a broadcast directly to a broadcast receiver in the SuperSU application. This broadcast contains the requesting application’s information as well as relevant details about which command will be executed as root. After this broadcast is received by the application it displays a prompt to the user with the supplied information. The su
binary then polls a file in the private data directory to find out whether permission was granted by the user. According to the user’s decision, su
decides to setuid(0)
or not.
The information just presented explains how you can allow applications to execute commands as root in a user-controlled manner that in theory is safe. Another objective that an attacker may pursue is gaining persistent root access on a device under his control without the user noticing. For this purpose, a completely unprotected custom version of su
is included with drozer as part of the tools.setup.minimalsu
module. This su
version is meant to be used for post-exploitation on older devices and should not be used for everyday purposes. Here is the code for it:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (setgid(0) || setuid(0))
fprintf(stderr, "su: permission denied\n");
else
{
char *args[argc + 1];
args[0] = "sh";
args[argc] = NULL;
int i;
for (i = 1; i < argc; i++)
args[i] = argv[i];
execv("/system/bin/sh", args);
}
}
This code is simply using setuid(0)
and setgid(0)
to change to the root user’s context, which means that any application that executes su
will receive root context and no checks are performed or prompts shown to the user. An application that has been allowed to run commands as root can control absolutely any aspect of the device and completely breaks the Android security model. This means that it will be able to access any other application’s files or modify their code at rest or at runtime. This is why there are so many warning labels about downloading untrusted applications that require root access. An application that implements poor or malicious code can damage the OS or even ruin it completely.
Many online articles provide tutorials on rooting specific devices; however, technical details of what exactly is going on in the background are often scarce. This section does not delve extensively into different methods of rooting devices, but does give you enough information to know what scenarios an attacker could use with each type to gain access to the data stored by applications.
There are two main ways of gaining root access on an Android device—using an exploit and using an unlocked bootloader. Both are explored in the following subsections.
Android uses the Linux kernel and also contains code added by device manufacturers. Like most code these implementations could contain bugs. These bugs could be anything from a simple mistake in the permissions of a particular file or driver code that does not handle certain user input securely. Entire books have been written about finding these sorts of vulnerabilities, so we explore a small subset of noteworthy exploits from different vulnerability classes.
Some devices come with a user-unlockable bootloader that allows you to flash new firmware onto it. Various methods can be used to obtain root using an unlocked bootloader. The most common ways are flashing a new recovery image or flashing a pre-rooted kernel image that already contains the su
binary. This may void the warranty of your device or if you do not know what you are doing, you may leave your device in an irrecoverable state.
Other device manufacturers may also provide unlocked bootloaders but different tools and protocols to perform flashing operations. A good example of this is Samsung; you can use a tool named ODIN to flash any Samsung device. A vast number of guides are on the Internet on how to use tools from each manufacturer and where to get custom system and recovery images.
Reverse-engineering is the process of gaining a deep understanding of a system or application by only having the finished product at hand. Being able to understand what is happening under the hood of an application that you do not have the source code of is the basis of reverse-engineering. A very different mindset and set of skills is needed when compared to performing source code review of an application. This section covers the multiple techniques and tools required to reverse engineer Android applications. First, having the APK file of your target application is crucial. This may be an application that is already installed on a device you have or one that is available on the Play Store (or some other app store).
If the application you are targeting is on a device that you are able to get ADB access to, you can use this access to retrieve the APK file. Sometimes, finding the package name of a target application can be tricky. For example, look at the twitter application. The following approach lists all installed packages on the device and looks specifically for the word twitter:
$ adb shell pm list packages | grep twitter
package:com.twitter.android
This package was easy to find because it had a predictable word in the package name. However, this may not always be the case. For example, to find the package that is started when you click the Terminal Emulator launcher icon, run your search in drozer using the app.packages.list
command with a filter for this application’s label.
dz> run app.package.list -f "Terminal Emulator"
jackpal.androidterm (Terminal Emulator)
This application would not have been found using the ADB method. To pull this application off the device you first need to find the path where the APK is stored, which you can do using ADB as follows:
$ adb shell pm path jackpal.androidterm
package:/data/app/jackpal.androidterm-2.apk
Or using drozer’s app.package.info
module and observing the APK Path
line in the output:
dz> run app.package.info -a jackpal.androidterm
Package: jackpal.androidterm
Application Label: Terminal Emulator
Process Name: jackpal.androidterm
Version: 1.0.59
Data Directory: /data/data/jackpal.androidterm
APK Path: /data/app/jackpal.androidterm-2.apk
UID: 10215
GID: [3003, 1015, 1023, 1028]
Shared Libraries: null
Shared User ID: null
Uses Permissions:
- android.permission.INTERNET
- android.permission.WRITE_EXTERNAL_STORAGE
- android.permission.ACCESS_SUPERUSER
- android.permission.WAKE_LOCK
- android.permission.READ_EXTERNAL_STORAGE
Defines Permissions:
- jackpal.androidterm.permission.RUN_SCRIPT
- jackpal.androidterm.permission.APPEND_TO_PATH
- jackpal.androidterm.permission.PREPEND_TO_PATH
To reverse engineer applications from the Play Store, you would need to install them onto a device you own and then use the preceding method. However, sometimes the application you are targeting is not available in the Play Store from your country. You can overcome this issue by using sites to which you provide the package name or Play Store link to your target application, and they provide a direct APK download. Two such sites are
A big part of understanding an Android application is obtaining and reviewing the AndroidManifest.xml
file associated with the package. A number of tools are available to do this, and this section discusses three of them.
The Android Asset Packaging Tool (aapt
) that comes with the Android SDK can be used to dump binary resource files included in an APK. To dump the manifest of the drozer agent using aapt
, perform the following command:
$ aapt dump xmltree /path/to/agent.apk AndroidManifest.xml
N: android=http://schemas.android.com/apk/res/android
E: manifest (line=2)
A: android:versionCode(0x0101021b)=(type 0x10)0x5
A: android:versionName(0x0101021c)="2.3.4" (Raw: "2.3.4")
A: package="com.mwr.dz" (Raw: "com.mwr.dz")
E: uses-sdk (line=7)
A: android:minSdkVersion(0x0101020c)=(type 0x10)0x7
A: android:targetSdkVersion(0x01010270)=(type 0x10)0x12
E: uses-permission (line=11)
A: android:name(0x01010003)="android.permission.INTERNET" (Raw:
"android.permission.INTERNET")
E: application (line=13)
A: android:theme(0x01010000)=@0x7f070001
A: android:label(0x01010001)=@0x7f060000
A: android:icon(0x01010002)=@0x7f020009
A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
...
Another shorter way to dump resources in addition to the manifest is:
$ aapt l -a /path/to/agent.apk
You will notice that aapt
does not produce XML output, which makes it hard to use inside XML viewing applications. Instead, it produces text that specifies E: for an XML entity and A: for an attribute. Using aapt
can be useful when you have limited tools available.
This tool parses the Android binary XML format directly. Therefore, APK files need to be unzipped first in order to obtain the AndroidManifest.xml
to pass as an argument to this tool. You can download it from https://code.google.com/p/android4me/downloads/list
. Here is an example of using it to parse and display the drozer agent manifest:
$ unzip agent.apk
Archive: agent.apk
inflating: res/drawable/ic_stat_connecting.xml
inflating: res/layout/activity_about.xml
inflating: res/layout/activity_endpoint.xml
inflating: res/layout/activity_endpoint_settings.xml
inflating: AndroidManifest.xml
...
$ java -jar AXMLPrinter2.jar AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="5"
android:versionName="2.3.4"
package="com.mwr.dz"
>
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="18"
>
</uses-sdk>
<uses-permission
android:name="android.permission.INTERNET"
>
</uses-permission>
<application
android:theme="@7F070001"
android:label="@7F060000"
android:icon="@7F020009"
android:debuggable="true"
...
A module in drozer named app.package.manifest
can parse manifest files and display them to screen. Using drozer to retrieve a manifest differs from other tools in that it can only parse the manifests of installed applications. The argument that is passed to this module is the package’s name whose manifest you would like displayed. An example of this is shown here:
dz> run app.package.manifest com.mwr.dz
<manifest versionCode="5"
versionName="2.3.4"
package="com.mwr.dz">
<uses-sdk minSdkVersion="7"
targetSdkVersion="18">
</uses-sdk>
<uses-permission name="android.permission.INTERNET">
</uses-permission>
<application theme="@2131165185"
label="@2131099648"
icon="@2130837513"
debuggable="true"
...
Like all other compiled and interpreted code, the Dalvik bytecode contained within DEX files can be disassembled into low-level human-readable assembly.
Dexdump is a tool that comes with the Android SDK, and you can find it in any of the subdirectories in the build-tools
folder of the SDK directory. To disassemble DEX files into Dalvik instructions, use the following command:
$ ./dexdump -d /path/to/classes.dex
...
#3 : (in Landroid/support/v4/app/FragmentState$1;)
name : 'newArray'
type : '(I)[Ljava/lang/Object;'
access : 0x1041 (PUBLIC BRIDGE SYNTHETIC)
code -
registers : 3
ins : 2
outs : 2
insns size : 5 16-bit code units
057050: |[057050]
android.support.v4.app.FragmentState.1.newArray:(I)[Ljava/lang/Object;
057060: 6e20 ea03 2100 |0000: invoke-virtual {v1,
v2},
Landroid/support/v4/app/FragmentState$1;.newArray:(I)[Landroid/support/v4/a
pp/FragmentState; // method@03ea
057066: 0c00 |0003: move-result-object v0
057068: 1100 |0004: return-object v0
catches : (none)
positions :
0x0000 line=137
locals :
0x0000 - 0x0005 reg=1 this Landroid/support/v4/app/FragmentState$1;
0x0000 - 0x0005 reg=2 x0 I
source_file_idx : 1152 (Fragment.java)
...
The output produced by this tool is quite hard to read and almost in the most rudimentary state possible.
Baksmali is a disassembler that makes use of Jasmin syntax (see http://jasmin.sourceforge.net/
). It accepts DEX and APK files as arguments and disassembles each class in the DEX file to its own file, which is in a much more readable format. This, in turn, makes analysis of this code much more manageable. To disassemble the DEX file inside an APK, perform the following command:
$ java -jar baksmali-x.x.x.jar /path/to/app.apk
If no output directory is specified via the -o
flag then by default all class files will be put in a directory named out
.
Combined with the tool named smali, this toolkit is very powerful. Smali is an assembler that compiles a directory filled with classes in disassembled format back to a single DEX file. You can use the following command:
$ java -jar smali-x.x.x.jar -o classes.dex out/
Go to https://code.google.com/p/smali/
to download both of these tools.
IDA is a very popular disassembler used by reverse engineers all around the world. The power of IDA is its rich user interface and vast support for many different CPU architectures and interpreters. It is a commercial tool sold by Hex-Rays and is available at https://www.hex-rays.com/
.
IDA is able to understand the DEX format and provides a user interface with a “graph-view” for understanding the flow of application logic in an intuitive way. Figure 6.12 shows an example of the graph view provided when disassembling a DEX file with IDA.
Reading and understanding disassembled code is hard work. The more natural way to review an application would be to obtain the source code. Dalvik bytecode contained within a DEX file is an interpreted language that can be translated back to something that resembles the original source code. This can be performed by tools natively on the DEX file or by first converting the DEX file to standard Java CLASS files.
Dex2jar converts Android DEX files to Java Class files. This is useful because many tools are already available that can decompile Java bytecode back to source code. It is open source and you can download it from https://code.google.com/p/dex2jar/
. It has grown from just a decompiler into a tool suite that performs many different tasks. However, the focus in this section is on converting Android DEX files to Java files. Here is an example of performing this operation with the d2j-dex2jar
utility:
$ ./d2j-dex2jar.sh /path/to/agent.apk -o /output/to/agent.jar
dex2jar /path/to/agent.apk -> /output/to/agent.jar
The produced JAR file can now be decompiled back into Java source code using a number of available tools. The most popular choice for decompilation and viewing is JD-GUI. Figure 6.13 shows the converted JAR file open in JD-GUI.
JD-GUI can be downloaded from http://jd.benow.ca/
for all major platforms.
JEB is a dedicated Android application decompiler that is sold by PNF Software. It comes in two flavors:
Figure 6.14 shows an example of decompiling an application in the JEB interface.
JEB works directly on the Android package’s DEX file and does not use any intermediate steps that convert the DEX to a JAR file like other tools. Subtle differences in the Dalvik and Java bytecode sometimes cause other tools to fail to decompile the code. This is what JEB overcomes by performing this decompilation natively on the DEX file. For the casual Android application hacker, this failure may not be a problem. However, if accuracy and quality decompilation is what you are after, JEB offers it at a price. Go to http://www.android-decompiler.com/
for more information about JEB.
DEX files for system applications aren’t usually stored inside their APK. Rather, the code is pre-optimized and stored as an ODEX file. This file is the result of many optimizations that cause it to become reliant on the exact version of the Dalvik VM in use and other framework dependencies. This means that ODEX files cannot be decompiled in the same way as DEX files. In fact, they first need to be converted back to DEX files that have those optimizations and framework dependencies removed.
To perform this conversion from ODEX to DEX you can use smali
and baksmali
. You download the entire /system/frameworks
directory of the device on which the optimization took place, which you can do using ADB:
$ mkdir framework
$ adb pull /system/framework framework/
pull: building file list...
...
pull: /system/framework/framework2.odex -> framework/framework2.odex
pull: /system/framework/framework2.jar -> framework/framework2.jar
pull: /system/framework/framework.odex -> framework/framework.odex
pull: /system/framework/framework.jar -> framework/framework.jar
pull: /system/framework/framework-res.apk -> framework/framework-res.apk
pull: /system/framework/ext.odex -> framework/ext.odex
pull: /system/framework/ext.jar -> framework/ext.jar
pull: /system/framework/core.odex -> framework/core.odex
pull: /system/framework/core.jar -> framework/core.jar
pull: /system/framework/core-libart.odex -> framework/core-libart.odex
pull: /system/framework/core-libart.jar -> framework/core-libart.jar
pull: /system/framework/core-junit.odex -> framework/core-junit.odex
pull: /system/framework/core-junit.jar -> framework/core-junit.jar
...
123 files pulled. 0 files skipped.
1470 KB/s (56841549 bytes in 37.738s)
The target ODEX file can then be disassembled into an assembly-like format that uses the provided framework dependencies and then compiled back into a normal DEX file. For instance, try this on the Settings.odex
file that belongs to the settings application.
$ adb pull /system/priv-app/Settings.odex
2079 KB/s (1557496 bytes in 0.731s)
You can use the following command to convert the ODEX to smali. By default, it stores the disassembled code in the out/
directory.
$ java -jar baksmali-x.x.x.jar -a 19 -x Settings.odex -d framework/
Now the disassembled code can be assembled again into a DEX file.
$ java -jar smali-x.x.x.jar -a 19 -o Settings.dex out/
The -a
parameter given to smali
and baksmali
is the API version used by the applications. After you have generated a DEX file you can use your favorite decompilation and viewing tools to analyze the source code.
You can find the API version in use programmatically or by observing which Android version is running on your device and then finding the corresponding API version number. Table 6.5 shows this mapping for all versions available at the time of writing.
Table 6.5 Mapping Android Versions to Corresponding API Levels
PLATFORM VERSION | API LEVEL | VERSION CODE |
Android 5.0 | 21 | LOLLIPOP |
Android 4.4W | 20 | KITKAT_WATCH |
Android 4.4 | 19 | KITKAT |
Android 4.3 | 18 | JELLY_BEAN_MR2 |
Android 4.2, 4.2.2 | 17 | JELLY_BEAN_MR1 |
Android 4.1, 4.1.1 | 16 | JELLY_BEAN |
Android 4.0.3, 4.0.4 | 15 | ICE_CREAM_SANDWICH_MR1 |
Android 4.0, 4.0.1, 4.0.2 | 14 | ICE_CREAM_SANDWICH |
Android 3.2 | 13 | HONEYCOMB_MR2 |
Android 3.1.x | 12 | HONEYCOMB_MR1 |
Android 3.0.x | 11 | HONEYCOMB |
Android 2.3.3, 2.3.4 | 10 | GINGERBREAD_MR1 |
Android 2.3, 2.3.1, 2.3.2 | 9 | GINGERBREAD |
Android 2.2.x | 8 | FROYO |
Android 2.1.x | 7 | ECLAIR_MR1 |
Android 2.0.1 | 6 | ECLAIR_0_1 |
Android 2.0 | 5 | ECLAIR |
Android 1.6 | 4 | DONUT |
Android 1.5 | 3 | CUPCAKE |
Android 1.1 | 2 | BASE_1_1 |
Android 1.0 | 1 | BASE |
http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels
This table is going to be useful as a reference for future chapters that will discuss vulnerabilities that were fixed in certain API versions.
The Linux shared object (.so
) files that can be included as part of an Android application may also require reverse engineering. This may be a scenario where source code is not available and the code being executed by the native component needs to be understood. Typically, native components run compiled machine code for the ARM architecture; however, Android now runs on multiple other architectures as well. At the time of writing, the supported architectures also included x86 and MIPS.
Disassembly and the understanding of native code in this way is a topic that is beyond the scope of this book. A number of tools are available to disassemble native code, and IDA is one of the most popular choices for this task.
In addition to just disassembling native code, it is possible to decompile it with the Hex-Rays Decompiler. Hex-Rays provides a full decompiler from ARM machine code to pseudo-C output; it is at https://www.hex-rays.com/products/decompiler/
with a hefty price tag attached to it. Multiple open-source attempts have been made at creating a decompiler for ARM machine code, but to date they have not been as successful as commercial counterparts.
This section lists other tools that may be of interest to an Android reverse engineer.
You can use Apktool to reverse-engineer an entire Android package back to a workable form for modification. This includes converting all resources, including AndroidManifest.xml
, back to (nearly) their original source as well as disassembling the DEX file back to smali code. To do this, perform the following command:
$ java -jar apktool.jar d /path/to/app.apk output
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/tyrone/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...
You can compile a fully working APK file again after making any necessary modifications to the source by using the following command:
$ java -jar apktool.jar b output/ new.apk
I: Checking whether sources has changed...
I: Smaling...
I: Checking whether resources has changed...
I: Building resources...
I: Copying libs...
I: Building apk file...
Apktool is an ideal tool to use if you need to modify any aspect of an application that you do not have the source for. Download it for free from https://code.google.com/p/android-apktool/
.
Jadx is an open source DEX decompiler project that is in a working state and looks evermore promising each version. It contains command-line tools as well as a GUI to browse decompiled code. Source code and downloads are at https://github.com/skylot/jadx
. Figure 6.15 shows the jadx-gui
tool that has decompiled an Android application.
JAD is another popular free tool that allows for the decompilation of Java Class files back to source files. It does not provide a user interface like JD-GUI does. Unfortunately, it is not in active development anymore and the last release was in 2001. In some cases it has been found to be less reliable than using other similar tools. You can download it from a mirror site at http://varaneckas.com/jad/
.
Android devices making use of the new Android Runtime (ART) convert DEX files into OAT files at installation time. OAT files are essentially ELF dynamic objects that are run on the device and one would assume that they would have to be treated like native code when reverse engineering them. A tool named oatdump
performs a similar disassembling function for OAT files as dexdump
does for DEX files. Explore the options provided by this tool if you are interested in disassembling an OAT file. However, similarly to dexdump
the output is provided in quite a raw manner.
One simple fact that can be used is that the APK file of each installed application is still stored on the device. This means that the DEX file of your target application is still accessible in the normal way even when the converted OAT file is being used on the device. Another interesting detail is that every OAT file contains the original DEX file(s) embedded inside it. Pau Oliva created a script called oat2dex
that can extract the DEX file(s) from within a given OAT file. This script relies on radare2 (see http://www.radare.org/
) and can be found at https://github.com/poliva/random-scripts/blob/master/android/oat2dex.sh
. This can be used if the original APK containing the DEX is no longer available. At the time of writing reverse-engineering tools and techniques for OAT files were still in active research by the security community.
Android is a unique operating system with some components that would be familiar to those who understand the inner workings of Linux. However, the way in which applications work on Android is completely unique to the platform. The security model provided for Android applications is complex but rich and requires you to have a thorough understanding before you can analyze applications.
The tools available on Android for reverse engineers and hackers are mature and can be used to thoroughly investigate application behavior and their underlying code. Using these tools it is possible to easily dig in and get ready to start finding vulnerabilities in applications. This chapter presented all of the fundamental knowledge required to move on to hacking Android applications and Chapter 7 will give you a kick start in doing just that!