臟牛(DirtyCow)Linux本地提權漏洞復現(CVE-2016-5195)


漏洞名稱:臟牛漏洞(Dirty COW)

漏洞編號:CVE-2016-5195

漏洞概述:

  Linux內核的內存子系統的get_user_page內核函數在處理Copy-on-Write(寫時拷貝,以下使用COW表示)的過程中,存在條件競爭漏洞,導致可以破壞私有只讀內存映射。一個低權限的本地用戶能夠利用此漏洞獲取其他只讀內存映射的寫權限,有可能進一步導致提權漏洞(修改su或者passwd程序就可以達到root的目的)

影響范圍:Linux kernel >= 2.6.22(2007年發行,直到2016年10月18日才修復)

提權為root權限的EXP一:  https://github.com/FireFart/dirtycow

測試環境(centos6.5)

1、查看系統內核版本:Linux kernel >2.6.22

2、查看用戶信息:普通用戶

 3、將exp一下載到本地,使用gcc -pthread dirty.c -o dirty -lcrypt命令對dirty.c進行編譯,生成一個dirty的可執行文件

 4、執行./dirty 密碼命令,即可進行提權。

5、漏洞原理及分析

5.1 臟牛漏洞回顧
在分析大臟牛漏洞前,我們需要對原始的臟牛漏洞利用方式進行完整的分析理解: 之前的漏洞是在get_user_pages函數中,這個函數能夠獲取用戶進程調用的虛擬地址之后的物理地址,調用者需要聲明它想要執行的具體操作(例如寫/鎖等操作),所以內存管理可以准備相對應的內存頁。具體來說,也就是當進行寫入私有映射的內存頁時,會經過一個COW(寫時拷貝)的過程,即復制只讀頁生成一個帶有寫權限的新頁,原始頁可能是私有保護不可寫的,但它可以被其他進程映射使用。用戶也可以在COW后的新頁中修改內容之后重新寫入到磁盤中。         

現在我們來具體看下get_user_pages函數的相關代碼:

5.2、現在我們來具體看下get_user_pages函數的相關代碼:

整個while循環的目的是獲取請求頁隊列中的每個頁,反復操作直到滿足構建所有內存映射的需求,這也是retry標簽的作用;

follow_page_mask讀取頁表來獲取指定地址的物理頁(同時通過PTE允許)或獲取不滿足需求的請求內容;

在follow_page_mask操作中會獲取PTE的spinlock,用來保護試圖獲取內容的物理頁不會被釋放掉;

faultin_page函數申請內存管理的權限(同樣有PTE的spinlock保護)來處理目標地址中的錯誤信息;

在成功調用faultin_page后,鎖會自動釋放,從而保證follow_page_mask能夠成功進行下一次嘗試,以下是涉及到的代碼。         

原始的漏洞代碼在faultin_page底部:

上面這個判斷語句想要表示的是,如果當前VMA中的標志顯示當前頁不可寫,但是用戶又執行了頁的寫操作,那么內核會執行COW操作,並且在處理中會有VM_FAULT_WRITE標志。換句話說在執行了COW操作后,上面的if判斷為真,這時就移除了FOLL_WRITE標志。         

一般情況下在COW操作后移除FOLL_WRITE標志是沒有問題的,因為這時VMA指向的頁是剛經過寫時拷貝復制的新頁,我們是有寫權限的,后續不進行寫權限檢查並不會有問題。 但是,考慮這樣一種情況,如果在這個時候用戶通過madvise(MADV_DONTNEED)將剛剛申請的新頁丟棄掉,那這時本來在faultin_page后應該成功的follow_page_mask會再次失敗,又會進入faultin_page的邏輯,但是這個時候已經沒有FOLL_WRITE的權限檢查了,只會檢查可讀性。這時內核就會將只讀頁面直接映射到我們的進程空間里,這時VMA指向的頁不再是通過COW獲得的頁,而是文件的原始頁,這就獲得了任意寫文件的能力。   

基本來看,上述的過程流也就是臟牛漏洞的利用過程。   

在faultin_page中有對應的修復補丁:

 

同時也加入了另一個新的follow_page_mask函數:

 與減少權限請求數不同,get_user_pages現在記住了經過COW循環的過程。之后只需要有FOLL_FORCE和FOLL_COW標志聲明過且PTE標記為污染,就可以獲取只讀頁的寫入操作。

5.2大臟牛漏洞分析

THP通過PMD(Pages Medium目錄,PTE文件下一級)的_PAGE_PSE設置來打開,PMD指向一個2MB的內存頁而非PTEs目錄。PMDs在每一次掃描到頁表時都會通過pmd_trans_huge函數進行檢查,所以我們可以通過觀察PMD指向pfn還是PTEs目錄來判斷是否可以聚合。在一些結構中,大PUDs(上一級目錄)同樣存在,這會導致產生1GB的頁。 仔細查看臟牛補丁中關於THP的部分,我們可以發現大PMDs中用了和can_follow_write_pte同樣的邏輯,其添加的對應函數can_follow_write_pmd:

 然而在大PMD中,一個頁可以通過touch_pmd函數,無需COW循環就標記為dirty:

這個函數在每次get_user_pages調用follow_page_mask試圖訪問大頁面時被調用,很明顯這個注釋有問題,而現在dirty bit並非無意義的,尤其是在使用get_user_pages來讀取大頁時,這個頁會無需經過COW循環而標記為dirty,使得can_follow_write_pmd的邏輯發生錯誤。

在此時, 如何利用該漏洞就很明顯了,我們可以使用類似臟牛的方法。這次在我們丟棄復制的內存頁后,必須觸發兩次page fault,第一次創建它,第二次寫入dirty bit。

調用鏈如下:

經過這個過程可以獲得一個標記為臟的頁面,並且是未COW的,剩下的就是要獲取FOLL_FORCE和FOLL_COW標志了。這個過程可以采取類似dirtyCOW的利用方式。

 

總結這個漏洞利用的思路如下:   

①首先經過COW循環,獲取到FOLL_COW標志
②用madvise干掉臟頁
③再次獲取頁將直接標記為臟
④寫入

“臟牛”漏洞是Linux內核之父Linus親自修復的,他提交的補丁單獨針對“臟牛”而言並沒有問題。從我們的分析過程中發現,內核的開發者希望將“臟牛”的修復方法引用到PMD的邏輯中,但是由於PMD的邏輯和PTE並不完全一致才最終導致了“大臟牛”漏洞。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM