Debuggable Apps in Android Market
on 7 July, 2011
Whilst doing Android application security reviews for our clients MWR repeatedly identify Android applications that are shipped with debugging enabled.
However, even without also performing an in depth assessment of the Dalvik VM debugging implementation this was assumed to be a high risk issue and on we report as such to our clients. This opinion was always based on the assumption that any application could initialise the debugging connection and use this to gain full access to the debuggable Java process.
A brief assessment of the most popular free Apps in the Android Market revealed that about 5% of these Apps are shipped with debugging enabled. In order to demonstrate the impact of this prevalent issue we decided to work on Proof-of-Concept exploit.
Debugging in the Dalvik VM
The android debug bridge (adb) is used to connect the developers system to an Android phone or the emulator. On the device itself the adbd daemon is started as soon as the phone is connected to the computer when USB debugging is enabled. On the computer adb can now be used to spawn a shell, forward ports and do other debugging related tasks. Adb is also used by the development environment to get a list of all running debuggable applications and to connect to one of these applications for debugging.
When developing a Java application for Android the Eclipse IDE will set the debuggable attribute of the application tag in the AndroidManifest.xml file to true (see 2). AndroidManifest.xml is the central configuration file for any Android application. If this attribute is set, the application will try to connect to a local unix socket “@jdwp-control”. This socket is opened on the device by adbd, waiting for debuggable applications to register by connecting to the socket.
The Dalvik VM implements this as follows:
for (;;) {
/*
* If adbd isn't running, because USB debugging was disabled or
* perhaps the system is restarting it for "adb root", the
* connect() will fail. We loop here forever waiting for it
* to come back.
*
* Waking up and polling every couple of seconds is generally a
* bad thing to do, but we only do this if the application is
* debuggable *and* adbd isn't running. Still, for the sake
* of battery life, we should consider timing out and giving
* up after a few minutes in case somebody ships an app with
* the debuggable flag set.
*/
int ret = connect(netState->controlSock,
&netState->controlAddr.controlAddrPlain,
netState->controlAddrLen);
if (!ret) {
/* now try to send our pid to the ADB daemon */
do {
ret = send( netState->controlSock, buff, 4, 0 );
} while (ret < 0 && errno == EINTR);
if (ret >= 0) {
LOGV("PID sent as '%.*s' to ADB\n", 4, buff);
break;
}
LOGE("Weird, can't send JDWP process pid to ADB: %s\n", strerror(errno));
return false;
}
LOGV("Can't connect to ADB control socket:%s\n", strerror(errno));
usleep( sleep_ms*1000 );
sleep_ms += (sleep_ms >> 1);
if (sleep_ms > sleep_max_ms)
sleep_ms = sleep_max_ms;
}
As can be seen the application will still try to connect to “@jdwp-control” even if adbd is not running.
If adbd is running it will accept the connections from debuggable applications and keep a list of the applications. Adb running on the computer can now request a connection to any of these and adbd will forward a connection between the debugger and the debuggee. Any further communication will now take place using the standard Java Debug Wire Protocol 1.
Exploiting this Behaviour
As highlighted in the previous code snippet, debuggable applications will try to connect to the unix socket “@jdwp-control”, even if adbd is not running. This is the case on default configurations of Android (USB debugging disabled) or on phones in development mode unless they are connected to a computer.
It was found that any application is able to open this unix socket and thus trick any debuggable application into connecting to it. Using a bit of “magic trickery” we can now establish a JDWP connection between the debuggable application and our malicious application.
Using JDWP we were able to gain full access to the Java process and execute arbitrary code in the context of the debuggable application. Our specific Proof-of-Concept exploit uses Runtime.getRuntime().exec() to execute a shell script provided by our application.
How to test for this?
On newer versions of Android it is possible to view the /data/system/packages.list file from the phone. The output will look something like this:
...
com.htc.ereader 10049 0 /data/data/com.htc.ereader
com.htc.connectedMedia 10012 0 /data/data/com.htc.connectedMedia
com.htc.android.htcime 10008 0 /data/data/com.htc.android.htcime
com.htc.dlnamiddlelayer 10079 0 /data/data/com.htc.dlnamiddlelayer
com.android.providers.downloads 10017 0 /data/data/com.android.providers.downloads
com.android.providers.htcmessage 10150 0 /data/data/com.android.providers.htcmessage
...
This is a list of all installed packages; the third value in each row indicates whether the Application is debuggable. The application is vulnerable if it set to 1.
When looking at a specific packages it is possible to take the .apk file and run the following command to view the AndroidManifest.xml file.
$ aapt d xmltree com.test.test.apk
If the debuggable attribute is set to anything but 0×0, the application is vulnerable to this issue.
How to fix?
For application developers the fix is very straight forward: Don’t ship debuggable applications.
Whereas Google has the opportunity to implement better defence in-depth protections to prevent this issue from affecting end-users. Several ideas that would improve protection are:
- Prevent uploading of debuggable applications to the Android Market
- Notify developers with existing debuggable applications on the Android Market
- Issue a warning to users who are about to install debuggable applications on their phone
1 http://developer.android.com/guide/topics/manifest/application-element.html#debug
2 http://download.oracle.com/javase/6/docs/technotes/guides/jpda/jdwp-spec.html