最近在忙开题的事情,期间和同学聊了一下这个漏洞。这是一篇概览介绍,更多细节请参考之前发的文章。这个漏洞是我调试的第一个内核漏洞,2018上半年花了很多时间在这个漏洞的调试和利用上面,从三月份开始断断续续调试到八月初才结束,希望可以和对Kernel感兴趣的同学一起交流。原文如下。

CVE-2017-8890是启明星辰ADLab于2017年5月发现的linux内核中隐藏了11年的double free漏洞,影响了当时几乎所有linux内核版本。在2018年初,国内安全媒体freebuf披露了该漏洞的利用细节,可以用于root 2017年5月之前几乎所有的linux服务器,并且可以在几乎所有的Android手机上完成内核提权操作。由于漏洞类型是double free漏洞,因此仅有POC就可以完成内核的DoS,造成的危害和影响非常大。
在下面从漏洞原理、漏洞利用、漏洞防御的角度来对该漏洞进行说明。

一、漏洞原理

linux 内核中的对该漏洞的补丁位于net/ipv4/inet_connection_sock.c中,使用inet_sk(newsk)->mc_list = NULL;来对mc_list成员初始化置null。newsk是一个sock的结构体指针(struct sock *newsk)。通过查看源码,可知mc_list是ip_mc_socklist的结构体指针。通过对漏洞补丁函数inet_csk_clone_lock的调用链分析,可知该函数的功能是在建立三次握手时复制socket,但是由于没有对socket中的ip_mc_socklist置null,导致了socket复制后在释放时导致double free漏洞,从而内核发生崩溃。

由于是double free的漏洞,触发漏洞时,必然要对mc_list进行创建和二次释放。通过查看内核源码中对mc_list所有引用,可知创建的调用链如下所示:
image001

图1-1 mc_list创建过程

同理,mc_list释放的过程如图1-2所示。


图1-2 mc_list释放过程

明确了创建和释放过程后,在POC中首先创建一个socket并加入组播模式(MCAST_JOIN_GROUP),这时socket中存在mc_list结构体。POC中监听该socket并用accept等待连接。建立连接时,内核会创建子sokcet,其中存在父socket的mc_list结构体。关闭子socket,这时等待mc_list真正释放后关闭父socket,可以同样的位置触发第二次free。这样就可以触发double free漏洞。POC的伪代码如表1-1所示。

sockfd = socket(AF_INET, …);
setsockopt(sockfd, SOL_IP, MCAST_JOIN_GROUP, …);
bind(sockfd,…);
listen(sockfd,…)
newsockfd = accept(sockfd,…);
close(newsockfd);  // first free
close(sockfd);     // double free

表1-1 触发double free的POC

二、漏洞利用

漏洞利用的过程可以简单分为这两个过程:通过double free实现控制EIP,通过控制EIP来代码执行提升权限。

触发内核double free后,由于是double free的漏洞,两次free的时机都可控。因此控制EIP的思路很简单,在第一次释放后,通过heap spray来占位被释放的obj,从而在第二次free的时候,尝试利用占位的数据去劫持EIP。

劫持EIP的主要难点有以下两点:

  1. 堆喷射占位obj
  2. 利用obj中的指针来劫持EIP

通过对ip_mc_socklist这个obj的源码分析可知,堆喷射的obj有如下要求:前八字节可控、obj大于32byte小于等于64byte。

通过对于linux内核源码中sock_kmalloc这个堆喷射实现函数分析,可以得到类似如图2-1的函数调用关系。

图2-1 sock_kmalloc函数调用关系

对源码分析和测试后,找到了满足条件的ip_mc_source这个函数,可以用于堆喷射。堆喷射控制EIP后,根据漏洞利用的环境不同,有对应不同的策略。

如在ubuntu上利用时,根据内核的保护措施不同,可以采取不同的利用思路。

  1. 没有任何保护措施时,控制EIP后,直接跳转到用户态的shellcode即可完成root提权。
  2. 有SMEP时,内核态不能执行用户态shellcode,可以通过内核rop结合shellcode来提权。
  3. 有SMAP时,内核态不能直接访问用户态数据,可以通过ret2dir把提权代码布置在内核态完成root提权。

利用效果如图2-2所示。

图2-2 ubuntu root提权

如在Android手机上利用时,由于Android中采取的linux内核版本相对较低,通常为3.10,但大多开启了PXN保护措施,内核态不能执行用户态shellcode。因此通过控制PC指针后修改addr_limit,用户态可以任意读写内核态。把double free转化成内核态任意地址的读写后,修改当前进程的cred结构体提权到root。之后patch selinux这个内核保护措施,可以完成提权操作到init权限(init权限比root权限更高)。

利用效果如图2-3所示。

图2-3 nexus6p Android手机提权

通过以上分析我们可以发现, POC的实现难度较低,造成的影响通常为DoS,会导致服务器重启、手机重启。而提权root的难度较大,利用的实现过程比POC实现起来复杂很多,造成的危害也更大,可以导致服务器和手机上数据被普通程序或APP窃取。

三、漏洞防范

为了在linux服务器和Android手机上避免被非法程序或APP提权,开发者通常的防御措施是更新内核版本。在各大linux发行版的更新文档中会指出当前更新会修复哪些漏洞,或者通过apt-get changelog的方式来查看当前linux内核版本修复了哪些漏洞。如果熟悉linux内核源码,手工patch补丁后自己重新编译内核也可以有效完成漏洞防御。

由于该漏洞从产生到披露已经隐藏了11年之久,linux内核被众多服务器、IoT设备使用,因此海量的设备都遭受影响。对于国内的安全研究人员而言,提升对于linux内核的漏洞挖掘能力非常重要。但由于linux内核的源代码达到了百万的级别,采用人工审计的效率非常低下,因此采用一种高效的漏洞挖掘方法就非常必要。

通常漏洞挖掘中都会采用fuzz的方式来进行,通过分析fuzz出的crash来回溯分析crash的原因从而判断是否可利用。该漏洞CVE-2017-8890的发现也是通过google的syzkaller fuzz框架发现的。因此,改进fuzz策略、改变fuzz的思路对于提升漏洞挖掘的效率会有非常大的帮助。漏洞挖掘后,判断一个crash是否可利用,也会耗费大量的时间和精力,京东安全研究员在blackhat USA 2018中介绍了他们的linux 内核自动化漏洞利用框架FUZE,自动化的实现了部分linux内核的漏洞利用,大大缩短了漏洞利用的时间,帮助安全研究人员判断漏洞是否可利用。

四、总结

该漏洞CVE-2017-8890从披露时就给行业内造成了巨大影响,几乎所有基于linux内核的服务器和Android手机都受影响。本文分析了该漏洞的原理,简要介绍了ubuntu、Android手机上的利用过程和实现难度,最后对于linux内核漏洞的防范做了简单的总结。

五、参考

  1. http://www.freebuf.com/articles/terminal/160041.html
  2. https://xz.aliyun.com/t/2383
  3. https://xz.aliyun.com/t/2385