您的位置:首页 > 其它

浅析CVE-2015-3636

2015-08-09 19:27 645 查看

//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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: