淺析CVE-2015-3636

//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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章