一个漏洞为何能影响数千万服务器以及66%安卓手机?

安全研究团队Perception Point发现Linux系统内核中存在一个高危级别的本地权限提升0day漏洞,编号为CVE-2016-0728。目前有超过66%的安卓手机和1000万Linux PC和服务器都受到这项内存泄露漏洞的影响。

漏洞介绍

Perception Point研究团队发现了一个Linux内核的本地提权漏洞。虽然这个漏洞自2012年便已经存在,但Perception Point团队声称近期才发现这个漏洞,目前已经提交至内核安全团队,后续还会发布PoC利用代码。

这个漏洞会影响到数以千万计的Linux 个人计算机和服务器,以及大约66%的安卓设备(包括手机和平板)。尽管Perception Point团队以及内核安全团队目前尚未发现针对这个漏洞的利用,我们还是建议安全团队尽快检测可能受此影响的设备,并打补丁。

本文我们将对漏洞的技术细节进行介绍,以及如何通过这个漏洞实现内核代码执行。最终,PoC成功实现从本地用户提权限权至root权限。

漏洞

CVE-2016-0728 这个漏洞本身存在于Linux内核密钥管理和保存功能keyrings中。在我们详细介绍之前,先来据了解一些关于这个漏洞的背景知识。

直接引自其帮助页面,keyrings主要功能是为驱动程序在内核中保留或者缓存安全数据、身份认证秘钥、加密秘钥以及其他数据。由于提供了系统调用接口——即keyctl系统调用(此外还有两个处理密钥的系统调用:add_key和request_key。不过,keyctl是本文绝对的关键),因此用户空间的程序可以以管理这些对象,并利用该工具实现各自的目的。

每个进程都会使用keyctl(KEYCTL_JOIN_SESSION_KEYRING,名称)为当前的会话创建一个密钥环(keyring),然后可以为密钥环(keyring)指定名称,也可以通过传递NULL不予指定。密钥环(keyring)对象能够通过引用相同keyring名称在不同进程中进行共享。如果进程已经拥有一个会话密钥环(keyring),keyctl系统调用便会使用新的密钥环(keyring)取代原来的。

如果一个对象被多个进程共享,位于usage字段的对象内部引用计数便会递增。当进程替换当前的密钥环(keyring)时,有可能发生泄漏。正如摘自内核3.18版本的如下代码片段,代码跳转至error2标签,忽略了key_put调用,同时泄露由find_keyring_by_name增加的引用计数。

从用户空间触发这个漏洞是非常简单的,正如下面代码片段所示,这里导致了100个keyring泄露引用:

下面的输出显示leaked-keyring已经有100个引用。

漏洞利用

尽管漏洞本身可以直接导致了内存泄露,但它可以引发更为严重的后果。经过对相关数据流的快速审查,我们发现存储对象引用计数的数据字段是atomic_t数据类型的,即实际上是int数据类型,意味着在32和64位体系架构上都是32位大小。虽然每个整数在理论上都是可以溢出的,这种观察方法使得利用这个漏洞溢出引用计数的方法看似可行。

如果某个进程导致内核对同一对象解引用0x100000000次,它会让内核认为该对象已没有被引用,因此会释放该对象。如果同一个进程还拥有对象的另一个合法引用,并在内核释放之后进行利用,便会造成内核引用一个已释放或者已重新分配的内存区域。

通过这种方法,我们可以构造一个内存释放后再使用的漏洞(use-after-free)。已经有许多关于内核中内存释放后再使用的漏洞示例,接下来的步骤对于有经验的漏洞研究人员可谓轻车熟路了。这段可执行的利用代码大体步骤如下所示:

1、保留一个密钥对象的(合法)引用; 2、溢出相同密钥环(keyring)对象的usage字段; 3、获取已释放的密钥环(keyring)对象 4、从用户空间中,使用用户可控的内容在已释放密钥环(keyring)对象所占用的内存空间上分配一个新的内核对象。 5、使用旧的密钥对象的引用,并触发代码执行。

第一步完全来源于帮助页面,第二步已经在前面进行过解释。下面继续对后面几步的技术细节进行解释。

溢出引用计数

这一步实际上是这个漏洞的延伸。usage字段是int类型的,这也就意味着它在32和64位体系结构中的最大值是2^32。为了让usage字段溢出,我们必须让片段循环2^32次以上,才能让usage达到0。

释放keyring对象

有几种方法来释放keyring对象。一种是使用一个进程来是keyring usage字段溢出到0,通过在密钥环子系统的垃圾回收算法,将对象释放,因为一旦密钥环子系统释放对象之后,usage字段的计数就会归零。

温馨提示,如果我们看到join_session_keyring函数(join_session_keyring函数是将一个会话 keyring 替换为新的会话keyring)prepare_creds同样增加了当前会话keyring,分别是abort_creds或者commit_credsdecrements。

问题在于abort_creds并不同步降低keyring的usage字段,但是稍后它会通过RCU工作机制进行调用(在修改数据的时候,首先需要读取数据,然后生成一个副本,对副本进行修改,修改完成之后再将老数据update成新的数据,此所谓RCU),这意味着我们可能在不知情的状况下发生溢出。想要解决这个问题,我们可以尝试在每次调用join_session_keyring之后使用sleep(1)。

当然sleep(2^32)秒的时间是不可行的。可行的方法是使用divide-and-conquer算法的一个变量,在第2^31-1次调用之后sleep……这样我们永远不会发生无意的溢出,因为refcount最大值在没有调用的时候可以加倍。

分配和控制内核对象

我们的进程指向一个keyring释放对象,现在我们需要分配一个内核对象来覆写keyring对象。由于SLAB内存的机制,在释放keyring对象之后分配多个对象的keyring数据长度将变得更为容易。我们选择使用Linux IPC子系统发送大小为0xb8 – 0x30的信息,这时keyring对象的数据长度为0xb8,消息header的数据长度为0x30。

这样我们便将keyring对象的数据长度控制在0X88字节之下。

获取内核代码执行

由于keyring对象内部的key_type结构包含许多函数指针,因此获取这一步将变得相当容易。Revoke函数是一个有趣的函数指针,能够通过调用keyctl(KEY_REVOKE,key_name)函数来进行使用。下面便是Linux内核调用revoke函数的代码片段:

Keyring对象会通过以下方式填补:

通过利用keyring的uid和标志值,不断尝试对keyring对象进行加载,并通过对该过程的检测,来获取到从key->type->revoke的执行过程。这个类型区域会指向一个带有函数指针的用户空间结构,造成以root权限执行revoke函数指针。这是一段代码片段演示:

Commit_creds和prepare_kernel_cred函数的地址是静态的,因此可以确定每个受影响的Linux内核版本以及android设备。

现在迎来最后一步:

这里有一个漏洞在64位3.18内核系统上的完全利用,下面显示的则是EXP在一台英特尔i7-5500 CPU上面大约运行了30分钟,得到了完全利用(通常提权漏洞并不存在时间问题):

缓解措施及结论

该漏洞影响Linux内核3.8以及更高版本。SMEP(监督模式执行保护)&SMAP、SELinux会对这个漏洞在安卓设备上面的利用制造一定困难。或许我们后面可以讨论一下如何绕过这些缓解措施,不过当下最重要的还是请尽快打补丁!

查看完整代码请点击最下方阅读原文

*原文地址:perception-point,PoC来源:Pastebin SamSmith编译,感谢Rabbit_Run和Troy修订,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2016-01-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏海说

6、Java包的命名与划分

包的命名与划分 (一)使用Java包的目的 在了解做一件事之前,需要了解做这件事的目的。而使用Java包的目的大概如下: 1    对类进行归类,便于开发查找。...

33900
来自专栏Golang语言社区

深入Go语言网络库的基础实现

Go语言的出现,让我见到了一门语言把网络编程这件事情给做“正确”了,当然,除了Go语言以外,还有很多语言也把这件事情做”正确”了。我一直坚持着这样的理念——要做...

29870
来自专栏Java3y

COW奶牛!Copy On Write机制了解一下

触及到知识的盲区了,于是就去搜了一下copy-on-write写时复制这个技术究竟是怎么样的。发现涉及的东西蛮多的,也挺难读懂的。于是就写下这篇笔记来记录一下我...

25630
来自专栏熊二哥

快速入门系列--WCF--02消息、会话与服务寄宿

经过WCF基础的ABC学习,已经可以构建简单的WCF的服务,使用不同的服务地址和绑定类型,根据业务提供所需的服务契约。但不禁想问,服务所使用的消息报文是什么样的...

22550
来自专栏小勇DW3

redis cluster是如何做到集两家之长的

站在读写分离的层次看redis的时候,redis和master和slave存在明显的主从关系,也就是说master处于管理状态,salve跟着大哥混,maste...

27920
来自专栏CRPER折腾记

Vue折腾记 - (2)写一个不大靠谱的面包屑组件

我把页面标题和面包屑封装到一起..就不用涉及到组件的通讯了, 不然又要去监听路由或者依赖状态去获取

17520
来自专栏ytkah

dedecms自增标签[field:global.autoindex/]的运用

  用bootstrap建站时用到幻灯片切换模块,里面有个active(下面代码中的data-slide-to="0"),其余的按顺序递增(1,2),如果用de...

36840
来自专栏张戈的专栏

事实证明Linux永远是NO.1

嗨,我正在写一个简单的程序,我要让这个程序打印出一个整数。 #include <stdio.h> int main() {    int linux = ...

36870
来自专栏飞雪无情的博客

Go语言实战笔记(一)| Go包管理

这本是In Action系列的书籍,这个系列做研发的都知道,在研发届评价很多,很多新的技术、语言等都会有一本实战的书籍。既然是实战,那么这本书假设了他的读者有了...

14330
来自专栏专业duilib使用+业余界面开发

mysql5.7 安装,服务启动失败无data目录处理

25530

扫码关注云+社区

领取腾讯云代金券