浅析CVE-2015-3636
2015-08-09 19:27
645 查看
//weibo: @少仲
0x0 漏洞信息
https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-36360x1 漏洞描述
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; }
相关文章推荐
- hbase通过row key 的前缀查询记录
- 1078. Hashing (25)
- 安装后维修指南再次双系统xp、win7、ubuntu
- 【Cocos2d入门教程三】HelloWorld之一目了然
- 递归调用2
- 常见错误及解决方式
- 串口使用
- 智渔课堂官方免费教程三:Java基础之注释 关键字 标识符
- 一笔画问题 NYOJ
- Swift调用C代码的方法
- 103. Binary Tree Zigzag Level Order Traversal
- I学霸官方免费教程三:Java基础之注释 关键字 标识符
- 2015年7月份来的知识回顾
- 单元测试junit
- 说一说Android的工程目录结构
- VSTS负载测试——如何:使用 SQL 创建结果存储区
- Scala 深入浅出实战经典 第52讲:Scala中路径依赖代码实战详解
- c语言中 指针和一维数组 简要总结
- 栈 链表的实现
- 第一周工作总结及计划表