浅析CVE-2015-3636

来源:互联网 发布:网络电影播放器排行榜 编辑:程序博客网 时间:2024/05/21 09:55

//weibo: @少仲


0x0 漏洞信息

https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-3636


0x1 漏洞描述

CVE-2015-3636漏洞是Linux kernel的ping套接字上存在的一个Use-After-Free漏洞.


0x2 代码分析

在调用connect连接用socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP)创建的套接字对象前,代码如下:

int inet_dgram_connect(struct socket *sock, struct sockaddr * uaddr,       int addr_len, int flags){struct sock *sk = sock->sk;if (addr_len < sizeof(uaddr->sa_family))return -EINVAL;if (uaddr->sa_family == AF_UNSPEC)return sk->sk_prot->disconnect(sk, flags);if (!inet_sk(sk)->inet_num && inet_autobind(sk))return -EAGAIN;return sk->sk_prot->connect(sk, (struct sockaddr *)uaddr, addr_len);}

当sa_family == AF_UNSPEC的情况下,调用disconnect函数取决于protocol的类型.比如ICMP套接字.

int udp_disconnect(struct sock *sk, int flags){struct inet_sock *inet = inet_sk(sk);sk->sk_state = TCP_CLOSE;inet->inet_daddr = 0;inet->inet_dport = 0;sock_rps_reset_rxhash(sk);sk->sk_bound_dev_if = 0;if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK))inet_reset_saddr(sk);if (!(sk->sk_userlocks & SOCK_BINDPORT_LOCK)) {sk->sk_prot->unhash(sk);inet->inet_sport = 0;}sk_dst_reset(sk);return 0;}

可以看到调用了sk_prot->unhash(sk)函数.


static void ping_v4_unhash(struct sock *sk){struct inet_sock *isk = inet_sk(sk);pr_debug("ping_v4_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num);if (sk_hashed(sk)) {write_lock_bh(&ping_table.lock);hlist_nulls_del(&sk->sk_nulls_node);sock_put(sk);isk->inet_num = 0;isk->inet_sport = 0;sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);write_unlock_bh(&ping_table.lock);}}

通过这段代码,可以看出.如果sk_hashed,它会删除sk_nulls_node在hlist当中的存储.那么我们看下都删除的代码


static inline void __hlist_nulls_del(struct hlist_nulls_node *n){struct hlist_nulls_node *next = n->next;struct hlist_nulls_node **pprev = n->pprev;*pprev = next;if (!is_a_nulls(next))next->pprev = pprev;}static inline void hlist_nulls_del(struct hlist_nulls_node *n){__hlist_nulls_del(n);n->pprev = LIST_POISON2;}

在hlist_nulls_del被调用后,会调用sock_put函数.

static inline void sock_put(struct sock *sk){if (atomic_dec_and_test(&sk->sk_refcnt))sk_free(sk);}

我们可以看到,当sk->sk_nulls_node开始删除的时候,n->pprev的值变成了LIST_POISON2.而这个值在(32位 and 64位)系统中被定义成了0x200200.这段地址可以被攻击者mmap到用户空间.当connect函数被调用第二次的时候,socket对象从hlist当中删除,但是它仍然是hashed类型,因为他取决于第一次connect时的sk->sk_node.因此会再次进入if条件下,再次删除sk->sk_nulls_node.

当执行到*pprev=next时,由于之前的值为0x200200,如果它没有被map到用户空间,则会产生一个页面错误,因此导致内核的panic.(如果要避免crash的情况出现,就需要在第二次连接ICMP socket之前,把它mmap到用户空间.)

每次进入if条件下,都要判断sock的对象的引用计数是否为0.如果为0的话就会被free掉.也就是说比如一个套接字对象被第二次连接时,它的引用计数变成0,因此内核会释放掉它.但是当文件描述符的句柄在用户程序中,并且和内核中的socket对象相关,则容易出现UAF的问题.


0x3 如何利用

当调用close函数释放socket对象时,会触发inet_release函数

int inet_release(struct socket *sock){struct sock *sk = sock->sk;if (sk) {long timeout;sock_rps_reset_flow(sk);ip_mc_drop_socket(sk);timeout = 0;if (sock_flag(sk, SOCK_LINGER) &&!(current->flags & PF_EXITING))timeout = sk->sk_lingertime;sock->sk = NULL;sk->sk_prot->close(sk, timeout);}return 0;}

当内核调用inet_release函数来释放socket对象时,会调用sk_prot->close函数.而sk_proto是sk_common结构中的一个成员变量.它的类型又包含了TCP,UDP,PING等.当0x200200这段地址没有被map的情况下,会造成内核的crash.如果映射一块内存出来就不会crash掉.所以利用的方法就是想办法覆盖掉sk的内存空间,然后使它指向自己的函数,然后提权.


0x4 伪Poc

void exp_func(){struct sockaddr_in addr = {0};int ret = 0;void* map_addr = NULL;map_addr = mmap(,,,,,);int sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP);addr.sin_family = AF_INET;ret = connect(sockfd,&addr,sizof(sockaddr_in));addr.sin_family = AF_UNSPEC;ret = connect(sockfd,&addr,sizof(sockaddr_in));addr.sin_family = AF_UNSPEC;ret = connect(sockfd,&addr,sizof(sockaddr_in));......}

工作原因还是不放出完整的代码了,但是根据大体思路和逆向出来的代码,已经可以确定是这样的.第一次连接时,必须要把sa_family设置成AF_INET类型来保证sk是hashed类型,否则无法进入if条件下.另外要校验一下/proc/sys/net/ipv4/ping_group_range的权限才能使用.


0x5 漏洞修复


static __inline__ void sk_nulls_node_init(struct hlist_nulls_node *node){node->pprev = NULL;}



2 0