恶意意图:利用GPU漏洞(CVE-2022-22706 / CVE-2021-39793)
想象一下从第三方应用商店下载一个游戏。你授予了它看似无害的权限,但隐藏在应用中的是一个恶意漏洞利用程序,允许攻击者窃取你的照片、窃听你的对话,甚至完全控制你的设备。这就是像CVE-2022-22706和CVE-2021-39793这样的漏洞所带来的威胁,我们将在本文中深入剖析。这些漏洞影响了许多Android设备中常见的Mali GPU,允许非特权应用获得Root访问权限。
漏洞概述
受影响产品
| 产品 | Mali GPU内核驱动 |
|---|---|
| 厂商 | ARM |
| 严重性 | 高 - 非特权用户可以获取对只读内存页的写入访问权限 |
| 受影响版本 | - Midgard GPU内核驱动:所有版本从r26p0 - r31p0 - Bifrost GPU内核驱动:所有版本从r0p0 - r35p0 - Valhall GPU内核驱动:所有版本从r19p0 - r35p0 |
| 测试版本 | - Pixel 6, MP1.0, 2022 - 降级到Android 12.0.0 (SD1A.210817.015.A4, Oct 2021) - Linux localhost 5.10.43-android12-9-00002-g4fb696975cdd-ab7658202 #1 SMP PREEMPT Thu Aug 19 00:59:56 UTC 2021 aarch64 |
| CWE | CWE-119: 内存缓冲区边界内操作的不当限制 |
CVSS3.1评分
基础评分:7.8(高) 向量字符串:CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Termux
此Termux输出显示了漏洞利用的上下文,从非特权不受信任的应用上下文运行。
|
|
根本原因分析
我们在Mali GPU内核驱动的kbase_jd_user_buf_pin_pages()函数中发现了一个关键漏洞。这个函数至关重要:它管理GPU如何访问内存,准备用户提供的内存缓冲区,并(理论上)确保应用具有正确的权限(读或写)。
查看补丁变更列表,问题变得清晰。漏洞在于kbase_jd_user_buf_pin_pages()检查这些权限的方式。这完全与KBASE_REG_GPU_WR(GPU写)和KBASE_REG_CPU_WR(CPU写)标志有关——它们告诉驱动程序需要哪种类型的访问。
应用应该需要设置两个标志才能获得GPU写访问权限,但代码只检查KBASE_REG_GPU_WR标志,留下了一个巨大的漏洞。
|
|
补丁显示了问题的核心。如果设置了KBASE_REG_GPU_WR但没有设置KBASE_REG_CPU_WR,FOLL_WRITE标志会被设置。这是不正确的。它应该只在两个标志都设置时才被设置。
由于这个疏忽,恶意应用可以请求CPU写访问(通过设置KBASE_REG_CPU_WR),而不需要所需的GPU写访问(KBASE_REG_GPU_WR)。这允许应用绕过预期的安全检查,并获得对不应该被允许修改的内存的写访问权限。
这种写入只读内存的能力是漏洞利用其余部分所依赖的基本原语。它允许攻击者将恶意代码注入特权进程,并最终获得Root访问权限。
触发漏洞
通过利用此漏洞,我们可以强制Mali驱动程序授予对只读内存区域的写权限。以下步骤概述了漏洞利用过程:
- 分配读写内存页:我们首先分配一个具有读写权限的内存页。
- 使用KBASE_REG_CPU_WR导入映射(不使用KBASE_REG_GPU_WR):由于驱动程序中的检查缺失,这无意中授予了GPU写访问权限。
- 将导入的缓冲区映射到GPU VA空间:缓冲区被分配GPU地址空间中的虚拟地址。
- 取消映射原始读写映射:然后移除原始映射。
- 使用只读页重新映射相同地址:这导致CPU将页面视为只读,而GPU保留写访问权限。
- 提交具有GPU VA映射的GPU作业作为BASE_JD_REQ_EXTERNAL_RESOURCES:这会触发易受攻击的函数
kbase_jd_user_buf_pin_pages(),启用对只读内存的写入。
此时,我们可以通过利用来自用户空间的GPU VA映射来修改从CPU角度应该是只读的内存页。
利用原语
能力:能够写入文件的只读内存页。 影响:
- 修改的文件内存页被缓存在内存中供其他进程使用
- 修改不会保存到磁盘
先决条件:
- 能够打开和读取目标文件
- 对gpu_device使用ioctl、读取和写入的权限
通过将钩子和有效负载注入只读共享库,我们可以操纵特权进程(如init)中的执行流。由于所有域都可以读取system_lib_file类型的文件,这种技术具有广泛适用性:
|
|
然而,并非所有域都可以在gpu_device上调用ioctl、读取和写入。幸运的是,低特权域(如shell和untrusted_app)被允许这样做:
|
|
这使得能够通过system_lib_file修改从shell和untrusted_app劫持特权进程。
攻击策略:Root反向Shell
目标:将权限提升以从untrusted_app_27获取Root反向Shell。 挑战:绕过SELinux强制执行。 解决方案:加载任意内核模块。
首先,我们使用位于/sys/fs/selinux/policy的设备SELinux策略识别具有module_load权限的域:
|
|
其中,只有init-insmod-sh具有自动类型转换,它是目标:
|
|
由于init-insmod-sh可以通过运行类型为init-insmod-sh_exec的文件来执行,我们在该设备上定位相关的可执行文件(/vendor/bin/init.insmod.sh)。
通过init劫持提升到Root权限
为了实现完全系统妥协,我们针对init进程。init在do_epoll_wait中运行两个线程,使其成为可行的攻击向量:
|
|
其中一个线程是主线程,另一个是PropertyServiceThread,它从SecondStageMain中的StartPropertyService生成。通过针对这些线程中的任何一个,我们可以潜在地利用init进程获得进一步的控制。
|
|
PropertyServiceThread在property_set_fd监听套接字上注册一个epoll处理程序,然后进入一个循环,在其中重复调用具有无限超时的epoll_wait。这创建了一个长期存在的阻塞操作,如果识别出适当的触发器或攻击向量,可以利用它来劫持线程的执行流。
|
|
要唤醒此线程,我们可以使用任何有效的名称和值参数调用/system/bin/setprop。这将触发向套接字发送PROP_MSG_SETPROP2命令,导致epoll_wait返回并运行handle_property_set_fd。然后可以使用Mali写入来钩住handle_property_set_fd中调用的任何导入库函数,从而劫持init。
|
|
如果setprop缺乏足够的SELinux权限,CheckPermissions中的CheckMacPerms(由HandlePropertySet调用)将失败,触发LOG(ERROR),进而调用/system/lib64/libbase.so中的android::base::LogMessage::LogMessage。利用Mali写入原语,我们可以钩住这些导入函数之一来劫持执行并将权限提升到Root。
LogMessage内部调用/system/lib64/libc++.so中的std::ios_base::init,我们可以钩住它来劫持init。
然而,对于untrusted_app,直接调用/system/bin/setprop来触发劫持是行不通的。尽管setprop的执行是允许的,但由于setprop实际上指向/system/bin/toolbox(类型toolbox_exec),将不会有域转换,setprop将在untrusted_app的域中执行。在该域中,由于