作者: Flanker
公眾號:Flanker論安全

Text-To-Speech多國語言翻譯引擎引擎是Android系統的標配,上溯到Android 1.5 HTC時代就已經存在的賣點。有些小伙伴可能會用來在自己手機上學習外語,看起來比陳秘書長學外語的方式安全多了。

但在各式各樣的廠商實現中,總能找到一些奇葩的事情,例如。。。一個看起來無任何權限的普通語言包,就能獲得一個system權限持久性后門?(a.k.a CVE-2019-16253)

漏洞概述

概述:SamsungSMT應用是一個具有system權限的,負責對TTS功能進行綜合管理的系統進程。該進程存在本地權限提升漏洞(或者feature,或者后門?),導致本地無權限應用可以以此提升至system-uid權限。

SMT應用聲明了一個動態的導出receiver com.samsung.SMT.mgr.LangPackMgr$2, 在 SamsungTTSService->onCreate=>LangPackMgr->init 中啟用,其監聽的action是 com.samsung.SMT.ACTION_INSTALL_FINISHED. 這個receiver接受的broadcast有一個很有意思的參數 SMT_ENGINE_PATH, 該參數在經過一些預處理后被 LangPackMgr.updateEngine創建的線程所使用.

com.samsung.SMT.engine.SmtTTS->reloadEngine 最終調用了 System->load, 無論你相信不相信,SMT將傳入的path直接在自己的進程里做了加載,反正我是信了。這造成了一個非常典型的權限提升。

值得注意的是,利用該漏洞并不需要惡意的語言包被打開或者進行其他操作。在精心構造特定的參數后,只要安裝該看似無害的應用,漏洞即會被觸發。這是因為 SamsungTTSService注冊了一個receiver來監聽包安裝事件。只要被安裝的包名滿足特定的條件(最關鍵的條件是以com.samsung.SMT.lang開頭即可,并沒有簽名之類的其他強校驗),結合其他構造參數,該漏洞即會在安裝后被立即觸發。此外,由于SMT會在每次啟動時重新加載所有存儲的庫,也就意味著該漏洞是一個持續性的,攻擊者可以無感知獲得一個持久性的system后門。

想象一下如果有攻擊者在各大應用市場中發布了一個看似正常的語言包或者偽裝成其他形式,不需要任何權限,但事實上含有攻擊代碼的應用。只要用戶下載安裝,即會被植入一個持久性提權后門。

漏洞分析

相關的漏洞代碼如下所示:

package com.samsung.SMT.mgr;

class LangPackMgr$2 extends BroadcastReceiver {
//...
    public void onReceive(Context arg10, Intent arg11) {
        int v7 = -1;
        if(arg11.getAction().equals("com.samsung.SMT.ACTION_INSTALL_FINISHED")) {
           //...
            int v0_1 = arg11.getIntExtra("SMT_ENGINE_VERSION", v7);
            String v2 = arg11.getStringExtra("SMT_ENGINE_PATH");
            if(v0_1 > SmtTTS.get().getEngineVersion() && (CString.isValid(v2))) {
                if(CFile.isExist(v2)) {
                    LangPackMgr.getUpdateEngineQueue(this.a).add(new LangPackMgr$UpdateEngineInfo(v0_1, v2));
                    CLog.i(CLog$eLEVEL.D, "LangPackMgr - Add candidate engine [%d][%s]", new Object[]{Integer.valueOf(v0_1), v2});
                }
                else {
                    CLog.e("LangPackMgr - Invalid engine = " + v2);
                }
            }
//...
            LangPackMgr.decreaseTriggerCount(this.a);
            if(LangPackMgr.getTriggerPackageCount(this.a) != 0) {
                return;
            }

            if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {
                return;
            }

            LangPackMgr.doUpdateEngine(this.a);
        }
    }
}

在經過一些檢查后, doUpdateEngine就將一個新的 LangPackMgr.UpdateEngineInfo加入到 Queue中排隊處理:

private void updateEngine() {
        if(this.mThreadUpdateEngine == null || !this.mThreadUpdateEngine.isAlive()) {
            this.mThreadUpdateEngine = new LangPackMgr$EngineUpdateThread(this, null);
            this.mThreadUpdateEngine.start();
        }
    }      

        LangPackMgr$EngineUpdateThread(LangPackMgrarg1, LangPackMgr$1arg2) {
    this(arg1); 
    } 

    public void run() {
    //...     
        if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {
            return;     
        }

        try {
            v1 = LangPackMgr.getUpdateEngineQueue(this.a).poll();
            while(true) {
                if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {
                    gotolabel_20;             
                }

                v0_1 = LangPackMgr.getUpdateEngineQueue(this.a).poll(); 
//...         
            if(v1 != null && ((LangPackMgr$UpdateEngineInfo)v1).VERSION > SmtTTS.get().getEngineVersion()) {
                CHelper.get().set_INSTALLED_ENGINE_PATH(((LangPackMgr$UpdateEngineInfo)v1).PATH); 
                if(SmtTTS.get().reloadEngine()) {
----- 
    public booleanreloadEngine() {
//...     
        this.stop();     
        try {
            Stringv0_2 = CHelper.get().INSTALLED_ENGINE_PATH();
            if(CString.isValid(v0_2)) {
                System.load(v0_2); //<- triggers load         
            }         
            else {            
                gotolabel_70;         
            }    
}

SmtTTS.reloadEngine最終調用 System.load,參數即由傳入的Intent中決定。

當然,為了能成功到達這個路徑,在構造POC Intent的時候仍需要考慮如下幾點:

  • SMT_ENGINE_VERSION參數需要比內置的版本高(361811291)
  • mTriggerCount需要先被增加才能觸發漏洞。如同上面介紹的一樣, com.samsung.SMT.SamsungTTSService注冊的receiver會去掃描包名以 com.samsung.SMT.lang開頭的安裝事件,并自增 mTriggerCount

但是如何靜默發送出惡意Intent呢?注意SMT中以下的代碼會在檢查通過后,靜默啟動攻擊者所指定的service,攻擊者可以在其中觸發真正的提權漏洞

package com.samsung.SMT.mgr;
//...
class LangPackMgr$1 extends BroadcastReceiver {
    LangPackMgr$1(LangPackMgr arg1) {
        this.a = arg1;
        super();
    }

    public void onReceive(Context arg4, Intent arg5) {
        String v0 = arg5.getAction();
        String v1 = arg5.getData().getSchemeSpecificPart();
        if(((v0.equals("android.intent.action.PACKAGE_ADDED")) || (v0.equals("android.intent.action.PACKAGE_CHANGED")) || (v0.equals("android.intent.action.PACKAGE_REMOVED"))) && (v1 != null && (v1.startsWith("com.samsung.SMT.lang")))) {
            this.a.syncLanguagePack();
        }
    }
}
//...
    private void triggerLanguagePack() {
        if(this.mThreadTriggerLanguagePack == null || !this.mThreadTriggerLanguagePack.isAlive()) {
            this.mThreadTriggerLanguagePack = new LangPackMgr$LanguagePackTriggerThread(this, null);
            this.mThreadTriggerLanguagePack.start();
        }
    }

The checking thread reaches here:

package com.samsung.SMT.mgr;
//...

class LangPackMgr$LanguagePackTriggerThread extends Thread {
  //...

    public void run() {
        Object v0_1;
        HashMap v3 = new HashMap();
        HashMap v4 = new HashMap();
        try {
            Iterator v5 = LangPackMgr.f(this.langpackmgr).getPackageManager().getInstalledPackages(0x2000).iterator();
            while(true) {
        //...
                if(!((PackageInfo)v0_1).packageName.startsWith("com.samsung.SMT.lang")) {
                    continue;
                }

                break;
            }
        }
        catch(Exception v0) {
            goto label_53;
        }

        try {
            Intent v1_1 = new Intent(((PackageInfo)v0_1).packageName);
            v1_1.setPackage(((PackageInfo)v0_1).packageName);
            LangPackMgr.f(this.langpackmgr).startService(v1_1);
            LangPackMgr.increaseTriggerCount(this.langpackmgr);

此外,SMT所加載的so對部分導出函數也有一定要求,這些可以通過逆向來實現解決。

POC總結

雖然最初只是想

但實際上卻被提權了。

本文附帶的POC視頻展現了一個安裝后獲取system反彈shell的效果,如下。

同時在logcat中會出現如下日志:

?  ~ adb logcat | grep -i mercury   
16:29:48.816 24289 24317 E mercury-native: somehow I'm in the library yah, my uid is 1000     
16:29:48.885 24318 24318 E mercury-native: weasel begin connect   

日志中可以看到,我們已經獲得了uid=1000,即system的權限

POC代碼稍后會放在https://github.com/flankerhqd/vendor-android-cves 上。

不過可以放心的是,該漏洞已通過Samsung Mobile Security Rewards Program報告并修復,定級為High Severity,廠商已經發布了補丁。在對應的應用市場更新Samsung Text-To-Speech應用至修復的版本,并檢查之前安裝過的SMT語言包。

對于所有的受影響廠商設備,

  • Android N,O or older的版本,請升級SMT到3.0.00.101或更高
  • Android P的系統版本:請升級SMT至3.0.02.7

該漏洞分配的編號是CVE-2019-16253。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1070/