CVE-2015-3636
CVE-2015-3636,由Keen Team发现的Linux内核UAF漏洞(Keen Team的paper:Own your Android! Yet Another Universal Root),可以用来提权,但是在x86_64上只能导致内核panic。影响范围为4.0.3以下的Linux内核,可用于Android 6.0以下的系统Root。
漏洞分析
具体的漏洞成因paper中已经讲得很清楚了,原因就是net/ipv4/ping.c中的ping_unhash函数对于hlist node的错误处理,没有将pprev置NULL导致sokcet中的sk可被free进而导致UAF。这里主要再记录一些poc触发流程的分析。
在socket相关的代码中,有几个结构体对于理解比较关键,首先是struct socket中的ops指针指向的struct proto_ops和struct sock中的sk_prot指向的struct proto,前者代表当前socke所暴露出的对socket的操作接口,后者是针对某一协议的具体操作实现,ops一般都是简单包装后调用对应的sk_prot中的函数,两者通过inetsw_array数组来完成组合,像这样:
|
|
对于ping协议来说,涉及的就是ping_prot与inet_dgram_ops。还有就是struct net_proto_family,这个结构体只有三个成员,其中就包含了对应协议族的socket所用的create函数指针,在其中会完成ops和sk_prot指针的初始化。
漏洞的主要目的在于将sk对象的refcnt减为零从而触发free,同时还必须要先把这个sk hash一次,这样才能进入if(sk_hashed(sk))后面的逻辑中去。而这个hash过程,跟进去具体看一下可以知道,主要就是把目标socket和端口号建立个联系(所以之后对next的pprev指针的操作是不用担心稳定性问题的,一开始还有些疑惑orz)。
关于refcnt的操作,在一开始创建socket的时候,会从sys_socketcall一路调用到inet_create来创建AF_INET对应的socket,并在inet_create中调用sock_init_data,其中使用:
|
|
初始化refcnt为1。
关于hash的操作,对于ping协议来说,不是在相对应的ping_hash中做的,ping_hash中什么也没有,而是在ops中的inet_dgram_connect中完成的。具体地是由调用inet_autobind再调用sk_prot中的get_port,在其中完成refcnt++以及hash操作。
所以这样看下来,最快的触发UAF的方式就是先create socket,此时refcnt=1,然后正常connect,此时refcnt=2并且完成hash,然后就可以两次触发disconnect使refcnt=0触发free了。
Exploit
具体的漏洞利用思路Keen Team也已经在paper中很好地秀给我们了,在这里还是记录一些实践中的坑点或是收获。
- 关于结构体偏移,可以使用gdb来看:
|
|
- 关于mc_list,在利用中除了直接覆写sk_prot中的函数指针外,还有一个检查点要过,在inet_release中调用的ip_mc_drop_socket,如果mc_list成员为NULL可以直接从这里出去:
|
|
- 关于JOP链,一开始找的时候执着于找到paper中的core gadget,想着能够复用gadget可以省事,结果还不如直接找更快些,真的是被恶心到了…
- 关于确认task_struct位置,可以利用task_struct中的三个cpu_timers的next和prev指针相同以及real_cred与cred相同的特性来避免硬编码offset,提高exp的通用性。
- 关于绕过SELinux,Keen Team的另一个slide:How to Root 10 Million Phones with One Exploit也详细讲述了利用思路,真是让人陶醉。
- 可实际上即便拿到了u:r:init:s0的context,却依然是受到限制的,这里还需要继续研究
- 除了使用JOP去改写addr_limit以外,还可以劫持PC到内核中调用了set_fs(KERNEL_DS)的代码后面去,这样就可以让内核为我们修改addr_limit,当然要保证跳过去之后的控制流依然在我们的掌控中,比如像kernel_setsockopt这样的函数就可以:
|
|
最后分享一句在看Android的sepolicy文件时看到的注释,看得我害怕地躲在角落瑟瑟发抖:
|
|