Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用
作者:龔廣(@oldfresher)
閱讀本文之前,您最好理解Android中的Binder機制、用于圖形系統的BufferQueue原理、堆管理器je_malloc的基本原理。
此文介紹了如何利用libcutils庫中的堆破壞漏洞獲得system_server權限,此漏洞是研究Android圖形子系統時發現的,對應的CVE號為CVE-2015-1474和CVE-2015-1528。
1.漏洞代碼的位置
本文次涉及的漏洞位于創建native handle的函數,如下代碼片段所示[1],每一個GraphicBuffer對象都包含一個native handle 的指針,通過native handle,GraphicBuffer可以跨進程共享圖像系統所需的內存。
| 01 | native_handle_t* native_handle_create(int?numFds,?int?numInts) |
| 02 | { |
| 03 | ????native_handle_t* h =?malloc( |
| 04 | ????????????sizeof(native_handle_t) +?sizeof(int)*(numFds+numInts));//---------->整數溢出位置 |
| 05 | ? |
| 06 | ????h->version =?sizeof(native_handle_t); |
| 07 | ????h->numFds = numFds; |
| 08 | ????h->numInts = numInts; |
| 09 | ????return?h; |
| 10 | } |
當傳入精心構造的numFds和numInts(如numFds=0xffffffff,numInts=2)到native_handle_create 時,可以導致表達式”sizeof(native_handle_t) + sizeof(int)*(numFds+numInts)”整數溢出,接下來寫分配的緩沖區h則會導致堆破壞。有兩個函數會調用native_handle_create并寫分配的堆內存導致堆破壞。其中有一個影響Android的所有版本,另一個只影響Android Lollipop以上的版本。
影響所有Android版本的函數如下[2]
| 1 | status_t GraphicBuffer::unflatten( |
| 2 | ????????void?const*& buffer,?size_t& size,?int?const*& fds,?size_t& count) { |
| 3 | … |
| 4 | ????????native_handle* h = native_handle_create(numFds, numInts); |
| 5 | ????????memcpy(h->data,????????? fds,???? numFds*sizeof(int));???// ------------>這里會導致堆破壞 |
| 6 | ????????memcpy(h->data + numFds, &buf[10], numInts*sizeof(int)); |
| 7 | … |
| 8 | } |
當傳入的numFds和numInts被惡意構造后,h分配的緩沖區小于預期的大小,后續的memcpy會導致堆破壞。只影響Lollipop以上版本的函數如下,這個函數在所有的Android版本中都存在,不過只在Android 5.0以上才會被調用,其它版本不能觸發。
| 01 | native_handle* Parcel::readNativeHandle()?const |
| 02 | { |
| 03 | ... |
| 04 | ????native_handle* h = native_handle_create(numFds, numInts); |
| 05 | ????for?(int?i=0 ; err==NO_ERROR && i<numFds ; i++) { |
| 06 | ????????h->data[i] = dup(readFileDescriptor()); |
| 07 | ????????if?(h->data[i] < 0) err = BAD_VALUE; |
| 08 | ????} |
| 09 | ????err = read(h->data + numFds,?sizeof(int)*numInts);????? ----------------->堆破壞位置 |
| 10 | ... |
| 11 | ????return?h; |
| 12 | } |
read 函數類似與memcpy,后從Parcel中讀取sizeof(int)*numInts到h->data?+?numFds,因為分配給h的堆內存小于預期,所以read會導致堆破壞. 因為unflatten堆破壞時破壞的大小不好控制,我將使用這個函數來介紹此漏洞的利用。這個漏洞能被利用來提權的關鍵是numFds,numInts以及拷貝到native handle中的內容都可以被跨進程控制,從而使得低權限的進程可以注入代碼到高權限進程而達到提權。
2.利用方法
如果A進程可以獲得B進程的帶IGraphicProducer接口的binder代理對象,那么A進程可以通過跨進程的binder調用利用此漏洞可獲得B進程的權限。成功利用的關鍵函數是IGraphicProducer的setSidebandStream[4]函數。
| 01 | case?SET_SIDEBAND_STREAM: { |
| 02 | ????CHECK_INTERFACE(IGraphicBufferProducer, data, reply); |
| 03 | ????sp<NativeHandle> stream; |
| 04 | ????if?(data.readInt32()) { |
| 05 | ????????stream = NativeHandle::create(data.readNativeHandle(),?true);? |
| 06 | ????} |
| 07 | ????status_t result = setSidebandStream(stream); |
| 08 | ????reply->writeInt32(result); |
| 09 | ????return?NO_ERROR; |
| 10 | }?break; |
?
上述代碼描述了BnGraphicBufferProducer如何處理其它進程發來的SET_SIDEBAND_STREAM?事件,從代碼中可以看到,從其他進程傳來的parcel中的數據沒有做任何檢查,直接傳給了readNativeHandle,從而導致numFds,numInts和其它被拷貝到所分配的堆內存的數據可以被發起binder調用的進程任意構造,使得利用成為可能。
下圖是攻擊場景圖,低權限進程通過binder調用,利用此漏洞可獲得高權限進程的權限:
Figure1.攻擊場景
3.如何獲得system_server的權限
要獲得system_server的權限需要分三步走,順序不能亂:
第一步,從普通應用注入mediaserver。
第二步,從mediaserver注入surfaceflinger。
第三步,從surfaceflinger注入system_server.
必須按順序注入是因為只有前一步成功了才能獲得注入后續進程的權限,例如,只有拿下了mediaserver才能有權限訪問surfaceflinger中的一些特殊接口,才有機會注入surfaceflinger
Figure2. SELinux domains
如上圖所示,雖然surfaceflinger也是已系統用戶運行的,但是因為SElinux的存在surfaceflinger是運行在u:r:surfaceflinger:s0域中,而此域除了訪問圖形設備外,其它的權限很少,這是為什么已經獲得system用戶權限后還要繼續注入system_server的原因。system_server可以看在是Android的“內核“,可以訪問的資源很多,如果把root權限比作神的,能注入system_server應該算是“半神“。下圖描述了我們通過調用哪些接口,一步步的獲得system_server的權限。
Figure 3.?三步走獲得system_server權限
如上圖所示,一個普通應用程序通過調用IMediaRecord的querySurfaceMediaSource函數能夠獲得mediaserver進程導出的IGraphicProducer,從而普通應用程序可以注入代碼到mediaserver(詳情見后續章節),因為mediaserver有ACCESS_SURFACE_FLINGER的權限,所以注入到mediaserver中的代碼可以通過調用ISurfaceComposer的createSurface函數獲得surfaceflinger導出的IGraphicProducer接口,然后同過setSidebandStream可以拿下surfaceflinger。surfaceflinger 通過調用IWindowsManager的screenShotApplication 觸發system_server 調用 ISurfaceComposer captureScreen , 這個binder調用會將system_server 導出的一個 IGraphicProducer作為參數傳給surfaceflinger,從而surfaceflinger可以獲得system_server的IGraphicProducer接口,從而拿下system_server.
4.mediaserver注入詳解
我們需要經過三步才能獲得system_server的權限,但是由于NX,ASLR,SELinux,je_malloc和多個binder 服務線程相結合帶來的障礙,每一步都很復雜,本節將以mediaserver為例詳細介紹如何通過libutils的漏洞注入mediaserver. 簡單來說,需要經過5步才能成功。
1)控制binder服務線程
當使用je_malloc時,每一個線程都與一個特定的arena關聯,不同的線程從堆上分配內存時將使用不同的arena,每個arena又關聯一個或多個chunk,故分配給不同的線程的小塊堆內存將使用不同的chunk, Android中每個chunk的大小是1MB。圖4展示了je_malloc堆的離散性。
Figure 4.je_malloc中堆的分布
binder 服務線程處理binder代理發起的binder調用,服務線程的調用隨著并行的binder調用的增加而增多,一般都有一個最大值。就mediaserver而言,進程剛啟動時binder服務線程的數目是4,最多可以增加到17個,圖5顯示了mediaserver中binder服務線程最多時的狀況.
Figure 5.mediaserver的服務線程
當一個binder調用到達mediaserver時,系統會隨機的選擇一個服務線程來處理這個調用,而我們知道,不同的線程分配的堆內存位于不同的chunk中,所以,如果所有的線程都處于active狀態時,通過binder調用分配的內存可能位于不同的chunk中,從而使得通過binder調用進行堆風水基本不可能。解決方法是只讓一個binder服務線程處于active狀態,掛起其余的所有binder服務線程,從而多次binder調用所分配的堆內存可以位于同一個chunk中。
IGraphicProducer接口可以通過attachBuffer函數將一個GraphicBuffer加入到bufferqueue.而每一個bufferqueue只能存儲特定數量的GraphicBuffer, Lollipop中是64個,當bufferqueue中的GraphicBuffer已經達到64個后,如果還調用attachBuffer,處理attachBuffer的binder服務線程將會掛起,一直等到有bufferqueue中GraphicBuffer小于64為止,通過這種方法,可以掛起mediaserver中的16個binder線程,剩下的那個將服務我們發起的攻擊性binder調用。
2) 泄漏堆的內容
因為ASLR的存在,要想利用次漏洞,我們需要從mediaserver中泄漏地址信息,在je_malloc中,同一個線程中分配的相同大小的小塊內存將占據相鄰的region(je_malloc中有對region的定義),與dl_malloc不用的是,相鄰region之間沒有任何元數據。我們可以通過attachBuffer在mediaserver的堆上生成很多的native handle, native handle的結構如下,native handle 的大小由numFds和numInts決定,正常的native handle中numFds=2,numInts=12,分配的堆的大小是80。
(gdb) pt native_handle_t
type = struct native_handle {
int version;
int numFds;
int numInts;
int data[4294967296];
}
我們先通過attachBuffer分配多個正常的native handle,然后通過setSidebandStream構造一個畸形的native handle(numFds=-35 ,numInt=64),readNativeHandle會將相鄰的一個正常的native handle的numInts修改為較大的值, 當通過requestBuffer請求返回被修改的native handle 時,堆內容將會被泄漏。?
Figure 6.從mediaserver中泄漏堆內容
3)泄漏棧的基地址
因為NX的存在,我們需要使用ROP來繞過NX,而ROP需要能控制棧內容,所有我們需要知道棧的位置。知道了棧的位置,我們可以通過此漏洞重寫棧,從而可以將堆破壞漏洞轉化為棧重寫從而執行ROP。
我們已經知道如何泄漏堆的內容,所以我們可以在堆上搜索特定的數據結構來泄漏棧的基址。被搜索的關鍵數據結構為pthread_internal_t.
0xb652ec8c:?? 0xb424a080 0xb3b7c080?0x00000b58 0x00000a53
0xb652ec9c:?? 0xae8dcdb0 0x00000001?0xae7df000?0x000fe000
0xb652ecac:???0x00001000?0x00000000 0x00000000 0x00000000
0xb652ecbc:?? 0xb6e4700d 0xb3d48960 0x00000000 0xae7dd000
0xb652eccc:?? 0x00000001 0x00000000 0x00000000 0x00000000
0xb652ecdc:?? 0x00000000 0x00000000 0x00000000 0x00000000
上述地址范圍是一個pthread_internal_t的內容,從彩色高亮的區域我們可以得知這個結構描述的線程的一些屬性,tid 是 0xb58, pid 是 0xa53, 線程的棧的基址在0xae7df000. 因為這個結構中包含了線程的棧的基址,我們可以在泄漏的堆內存中搜索這樣的結構來得到棧的基址。搜索的特征為棧大小(0x000fe000) 和guard_size(0x00001000)。每一個通過pthread_create創建出來的binder服務線程都會分配一個pthread_internal_t的對象,一般來說,新創建出來的線程會被分配一個較大的tid. 因為mediaserver中除了binder服務線程外還有別的線程,別的線程的狀態不好控制,我們需要找到一個binder server線程對應的pthread_internal_t, 我們應該還記得binder server線程可以被動態觸發創建的,這樣就使得binder線程可以有一個較大的tid,我們可以搜索多個pthread_internal_t的對象,然后選擇其中tid最大的一個,則有很大的概率這個線程是處于掛起狀態的binder服務線程,處于掛起狀態的binder服務線程的調用棧是固定的,如圖7所示,我們可以通過重寫它的返回地址來觸發ROP的執行.
Figure 7.處于阻塞狀態的binder服務線程
4)泄漏共享庫(so)基址
我們需要模塊的基值來來構造ROP。為了有更多模塊來搜索ROP,我們可能需要泄漏多個模塊基址。泄漏libui.so的基址很容易。唯一需要做的是通過在泄漏的堆內存中搜索GraphicBuffer對象,GraphicBuffer對象包含一個 android_native_base_的子結構。這個結構中的incRef和decRef是指向libui.so中相應函數的函數指針。找到這個結構就能計算出libui.so的基址。GraphicBuffer具有特定特征,很容易搜索到。
(gdb) pt/m android_native_base_t
type = struct android_native_base_t {
int magic;
int version;
void *reserved[4];
void (*incRef)(android_native_base_t *);
void (*decRef)(android_native_base_t *);
}
為了比較方便的寫shellcode,最好是能找到libc.so的基址,這樣我們可以在shellcode中調用libc的函數,libui.so的GOT中有memcpy的地址,memcpy為libc.so中的函數,如果能泄漏libui.so的GOT表我們就能獲得libc.so的基地址。由于這個漏洞的局限性,要泄漏libui.so的GOT并不容易。從前面章節得知,我們可以通過修改numFds和numInts來泄漏堆內存,但這中方法只能泄漏連續的內存,因為泄漏內存的的邏輯是memcpy,如果泄漏的地址空間中有沒有被映射的內存,則memcpy直接會發生段錯誤。令一個限制是binder調用只能返回小于1MB的內存。堆內存和libui.so直接一般情況都存在沒有映射的頁面,而且兩者直接的距離也超過了1MB,所以不能通過簡單的修改numFds和numInts來泄漏libui.so的GOT。因為GraphicBuffer包含了native handle的指針,我們可以修改這個指針,使其指向一個偽造的native handle對象,則可以泄漏地址空間中緊跟這個native handle對象后的內存內容。泄漏的大小由偽造的numFds和numInts決定。通過這樣的方法,我們同樣可以泄漏libc.so的GOT,從而得到dlopen和dlsym的地址,有了這兩個函數,我們可以直接用高級語言寫shellcode.
5)控制je_malloc下次分配內存的位置
通過構造特殊的numFds和numInts,我們只能寫緊臨native handle的連續內存,因為je_malloc中棧和堆的地址空間通常不是相鄰的。所有我們需要找到一個重寫棧的方法,也就是將這個漏洞轉換為一個任意地址寫的漏洞。我們可以通過修改je_malloc中的tcache的指針表來實現. 當使用je_malloc并激活了tcache機制時,每一個線程都會為分配的小對象維護一個cache,這個cache存儲在一個叫tcache_t的結構中。對每一種特定大小區間的對象,tcache_t都為其維護了一個指針表。在Android的je_malloc實現中,共有31大小區間。見下示gdb輸出
(gdb) p je_small_bin2size_tab
$24 = {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072, 3584}
tcache的指針表存儲在堆上,與其它通過malloc分配的堆內存混合在一起,所以通過構造numInts 和 numFds可以重寫這些指針。下面所示的gdb輸出為,分配大于112,小于等于128的內存時所使用的tcache指針表。當通過je_malloc分配一個位于此區間的對象時,從avail開始索引為ncached-1的指針將是je_malloc返回的指針,通過將這個值修改為棧地址,那么,下一次分配特定大小對象,棧地址將被返回,寫被分配的對象內存是,棧將被重寫。通過這種方法,我們可以控制棧內容來執行ROP。
(gdb) p je_arenas[0].tcache_ql.qlh_first.tbins[11]
$9 = {tstats = {nrequests = 17}, low_water = 62, lg_fill_div = 1, ncached = 63, avail = 0xb6003f60}
(gdb) x/63xw je_arenas[0].tcache_ql.qlh_first.tbins[11].avail
0xb6003f60: 0xb6057f80? 0xb6057f00? 0xb6057e80? 0xb6057e00
0xb6003f70: 0xb6057d80? 0xb6057d00? 0xb6057c80? 0xb6057c00
0xb6003f80: 0xb6057b80? 0xb6057b00? 0xb6057a80? 0xb6057a00
0xb6003f90: 0xb6057980? 0xb6057900? 0xb6057880? 0xb6057800
0xb6003fa0: 0xb6057780? 0xb6057700? 0xb6057680? 0xb6057600
5.繞過SELinux加載so
因為SELinux的存在,普通應用創建的文件一般被標記為app_data_file 或 apk_data_file. mediaserver 沒有權限執行帶這些標簽的文件. 所以不能在shellcode里使用system或dlopen執行普通應用提供的執行文件。幸運的是,mediaserver 有execmem 權限。
allow mediaserver self:process execmem;???????????? ————>SELinux policy
我們可以通過mprotect修改匿名內存為可執行(這在surfaceflinger中是不行的),從而能執行shellcode. 然后在shellcode里實現從內存中加載so的機制(詳見PoC),從而帶 app_data_file 或 apk_data_file的文件可以以二進制流的形式通過漏洞傳給mediaserver,然后通過load so from memroy模塊加載執行。
Figure 8.?加載共享庫流程
因為mediaserver在Lollipop下沒有執行/system/bin/sh的權限,我通過注入busybox到mediaserver來執行shell命令,如果利用成功,可以得到一個從mediaserver反彈的shell(如圖9),細節請見PoC.
Figure 9.?帶mediaserver權限的shell
6.溢出surfaceflinger和system_server
溢出surfaceflinger 和 system_server與注入mediaserver相似,不夠有兩點需要注意:
1.surfaceflinger 沒有execmem權限,不能使用mprotect修改匿名內存為可執行,所有功能只能用RoP實現.
2.因為system_server和一般應用都是從Zygote fork出來的,模塊地址一樣,不需要泄漏system_server的模塊基地址.
PoC 見https://github.com/secmob/PoCForCVE-2015-1528
[引用]
[1]http://androidxref.com/5.0.0_r2/xref/system/core/libcutils/native_handle.c#29
[2]http://androidxref.com/5.0.0_r2/xref/frameworks/native/libs/ui/GraphicBuffer.cpp#303
[3]http://androidxref.com/5.0.0_r2/xref/frameworks/native/libs/binder/Parcel.cpp#1210
[4]http://androidxref.com/5.0.0_r2/xref/frameworks/native/libs/gui/IGraphicBufferProducer.cpp#403
[5]http://www.phrack.org/issues/68/10.html
總結
以上是生活随笔為你收集整理的Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WatchDog工作原理
- 下一篇: 自己动手调试Android源码(超简单)