再探CVE-2016-0728

//weibo: @少仲


0x0  漏洞信息

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


0x1  漏洞描述

cve-2016-0728是一个linux平台上的UAF漏洞.漏洞主要的原因是由于keyrings组件当中的引用计数问题导致的.它使用一个32位的无符号整数做引用计数,但是在计数器出现溢出的时候没有进行合理的处理.当对象的引用计数达到最大时会变成0,因此释放对象的内存空间.而此时程序还保留对引用对象的引用,所以形成了UAF漏洞.

 

0x2  代码分析

long join_session_keyring(const char *name)
{
	const struct cred *old;
	struct cred *new;
	struct key *keyring;
	long ret, serial;

	new = prepare_creds();
	if (!new)
		return -ENOMEM;
	old = current_cred();

	/* if no name is provided, install an anonymous keyring */
	if (!name) {
		ret = install_session_keyring_to_cred(new, NULL);
		if (ret < 0)
			goto error;

		serial = new->session_keyring->serial;
		ret = commit_creds(new);
		if (ret == 0)
			ret = serial;
		goto okay;
	}

	/* allow the user to join or create a named keyring */
	mutex_lock(&key_session_mutex);

	/* look for an existing keyring of this name */
	keyring = find_keyring_by_name(name, false);  //key->usage + 1
	if (PTR_ERR(keyring) == -ENOKEY) {
		/* not found - try and create a new one */
		keyring = keyring_alloc(name, old->uid, old->gid, old,
			KEY_ALLOC_IN_QUOTA, NULL);
		if (IS_ERR(keyring)) {
			ret = PTR_ERR(keyring);
			goto error2;
		}
	} else if (IS_ERR(keyring)) {
		ret = PTR_ERR(keyring);
		goto error2;
	} else if (keyring == new->session_keyring) {  //keyname == 当前cred中的key name
		ret = 0;                //直接返回,绕过key_put
		goto error2;
	}

	/* we've got a keyring - now to install it */
	ret = install_session_keyring_to_cred(new, keyring);
	if (ret < 0)
		goto error2;

	commit_creds(new);
	mutex_unlock(&key_session_mutex);

	ret = keyring->serial;
	key_put(keyring);
okay:
	return ret;

error2:
	mutex_unlock(&key_session_mutex);
error:
	abort_creds(new);
	return ret;
}

通过以上代码可以得出只要进程使用当前正在使用的keyring名,程序就会跳过kref_put(keyring),造成引用计数的只增不减.由于引用计数是无符号整形,可以通过循环调用,整数溢出的方法来将它置0.


0x3 如何利用

1. 造成引用计数溢出,int所能保存最大值为232 - 1.所以只要进行4294967295次循环,就可以将它清零.

2. 释放引用计数为0keyring对象

3. 使用slab机制分配内核对象来覆盖之前已经释放的keyring对象

4. 获得内核代码的执行权限.使用keyctl(KEY_REVOKE,key_name)这个API来调用revoke().当我们覆盖keyring对象的时候,我们可以控制指向revoke()的函数指针,让其指向我们准备好的提权代码.


0x4  POC问题主要分析

1.   漏洞代码所在的内核版本应该是3.8以后.但是大部分安卓5.0的设备都运行在3.4的内核版本之中.

2.   网上的poc没有成功的原因是因为没有获得commit_creds和prepare_kernel_cred的地址,攻击者需要得到root权限,将kptrstrict标志置为0后,才能通过/proc/kallsyms来查看符号的地址.

3.   在用户空间的提权代码则需要commit_creds(prepare_kernel_cred(0));函数将cred结构置为0,从而获取root权限.

4.   使用ret2usr来在用户态调用提权代码需要考虑pxn的问题.在没有pxn防护的手机中可以在内核空间执行用户态代码.然而在有pxn防护的情况下,则需要考虑使用ROP来寻找内核gadget来绕过保护.

5.   需要调用4294967295次,时间过长有可能导致shell反弹超时.所以很难利用.

6.   出现漏洞的代码存在于linux内核中的keyring服务.但是该服务必须在内核编译时启用CONFIG_KEYS 选项.在AOSP内核的config文件中,并没有发现CONFIG_KEYS被启用.所以说android设备并没有包含漏洞相关的代码,因此就是说android设备没有被该漏洞所影响.

7.   在安卓4.4版本以后,强制开启了SELinux的策略.SELinux减少了Linux内核的攻击面.SELinux的策略也限制了ASOP在设备上通过非授信app调用exp的权限.比如当一个app尝试去执行keyctl系统调用去创建一个keyring的对象时,系统调用会被拒绝. 开源的poc代码使用了SysV IPC (msgget) 来分配内存传递漏洞利用代码.安卓5.0的SELinux策略限制了SysV IPC因此阻止了包含利用的代码.


0x5  实测

1. 首先去掉get_symbol的函数,root掉手机以后,直接将kptrstrict置为0,读取kallsyms的全部信息.获取相关符号地址


2. arm版的通过使用syscall来取代keyctl调用.

3. 通过添加一个计算来显示循环的百分比

再探CVE-2016-0728_第1张图片

在我的设备上测试的时间为12:00 – 16:46 ,进度显示为0.1%.

再探CVE-2016-0728_第2张图片

所以大约要执行166天左右才能跑完…

 

 

参考文章:

http://bobao.360.cn/learning/detail/2576.html

https://www.mulliner.org/blog/blosxom.cgi/security/CVE-2016-0728_vs_android.html

https://github.com/nardholio/cve-2016-0728

 




你可能感兴趣的:(再探CVE-2016-0728)