日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用

發(fā)布時間:2025/3/15 Android 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者:龔廣(@oldfresher)

閱讀本文之前,您最好理解Android中的Binder機制、用于圖形系統(tǒng)的BufferQueue原理、堆管理器je_malloc的基本原理。
此文介紹了如何利用libcutils庫中的堆破壞漏洞獲得system_server權(quán)限,此漏洞是研究Android圖形子系統(tǒng)時發(fā)現(xiàn)的,對應的CVE號為CVE-2015-1474和CVE-2015-1528。

1.漏洞代碼的位置

本文次涉及的漏洞位于創(chuàng)建native handle的函數(shù),如下代碼片段所示[1],每一個GraphicBuffer對象都包含一個native handle 的指針,通過native handle,GraphicBuffer可以跨進程共享圖像系統(tǒng)所需的內(nèi)存。

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));//---------->整數(shù)溢出位置
05 ?
06 ????h->version =?sizeof(native_handle_t);
07 ????h->numFds = numFds;
08 ????h->numInts = numInts;
09 ????return?h;
10 }

當傳入精心構(gòu)造的numFds和numInts(如numFds=0xffffffff,numInts=2)到native_handle_create 時,可以導致表達式”sizeof(native_handle_t) + sizeof(int)*(numFds+numInts)”整數(shù)溢出,接下來寫分配的緩沖區(qū)h則會導致堆破壞。有兩個函數(shù)會調(diào)用native_handle_create并寫分配的堆內(nèi)存導致堆破壞。其中有一個影響Android的所有版本,另一個只影響Android Lollipop以上的版本。
影響所有Android版本的函數(shù)如下[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被惡意構(gòu)造后,h分配的緩沖區(qū)小于預期的大小,后續(xù)的memcpy會導致堆破壞。只影響Lollipop以上版本的函數(shù)如下,這個函數(shù)在所有的Android版本中都存在,不過只在Android 5.0以上才會被調(diào)用,其它版本不能觸發(fā)。

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 函數(shù)類似與memcpy,后從Parcel中讀取sizeof(int)*numInts到h->data?+?numFds,因為分配給h的堆內(nèi)存小于預期,所以read會導致堆破壞. 因為unflatten堆破壞時破壞的大小不好控制,我將使用這個函數(shù)來介紹此漏洞的利用。這個漏洞能被利用來提權(quán)的關(guān)鍵是numFds,numInts以及拷貝到native handle中的內(nèi)容都可以被跨進程控制,從而使得低權(quán)限的進程可以注入代碼到高權(quán)限進程而達到提權(quán)。

2.利用方法

如果A進程可以獲得B進程的帶IGraphicProducer接口的binder代理對象,那么A進程可以通過跨進程的binder調(diào)用利用此漏洞可獲得B進程的權(quán)限。成功利用的關(guān)鍵函數(shù)是IGraphicProducer的setSidebandStream[4]函數(shù)。

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如何處理其它進程發(fā)來的SET_SIDEBAND_STREAM?事件,從代碼中可以看到,從其他進程傳來的parcel中的數(shù)據(jù)沒有做任何檢查,直接傳給了readNativeHandle,從而導致numFds,numInts和其它被拷貝到所分配的堆內(nèi)存的數(shù)據(jù)可以被發(fā)起binder調(diào)用的進程任意構(gòu)造,使得利用成為可能。

下圖是攻擊場景圖,低權(quán)限進程通過binder調(diào)用,利用此漏洞可獲得高權(quán)限進程的權(quán)限:

Figure1.攻擊場景

3.如何獲得system_server的權(quán)限

要獲得system_server的權(quán)限需要分三步走,順序不能亂:
第一步,從普通應用注入mediaserver。
第二步,從mediaserver注入surfaceflinger。
第三步,從surfaceflinger注入system_server.
必須按順序注入是因為只有前一步成功了才能獲得注入后續(xù)進程的權(quán)限,例如,只有拿下了mediaserver才能有權(quán)限訪問surfaceflinger中的一些特殊接口,才有機會注入surfaceflinger

Figure2. SELinux domains

如上圖所示,雖然surfaceflinger也是已系統(tǒng)用戶運行的,但是因為SElinux的存在surfaceflinger是運行在u:r:surfaceflinger:s0域中,而此域除了訪問圖形設(shè)備外,其它的權(quán)限很少,這是為什么已經(jīng)獲得system用戶權(quán)限后還要繼續(xù)注入system_server的原因。system_server可以看在是Android的“內(nèi)核“,可以訪問的資源很多,如果把root權(quán)限比作神的,能注入system_server應該算是“半神“。下圖描述了我們通過調(diào)用哪些接口,一步步的獲得system_server的權(quán)限。

Figure 3.?三步走獲得system_server權(quán)限

如上圖所示,一個普通應用程序通過調(diào)用IMediaRecord的querySurfaceMediaSource函數(shù)能夠獲得mediaserver進程導出的IGraphicProducer,從而普通應用程序可以注入代碼到mediaserver(詳情見后續(xù)章節(jié)),因為mediaserver有ACCESS_SURFACE_FLINGER的權(quán)限,所以注入到mediaserver中的代碼可以通過調(diào)用ISurfaceComposer的createSurface函數(shù)獲得surfaceflinger導出的IGraphicProducer接口,然后同過setSidebandStream可以拿下surfaceflinger。surfaceflinger 通過調(diào)用IWindowsManager的screenShotApplication 觸發(fā)system_server 調(diào)用 ISurfaceComposer captureScreen , 這個binder調(diào)用會將system_server 導出的一個 IGraphicProducer作為參數(shù)傳給surfaceflinger,從而surfaceflinger可以獲得system_server的IGraphicProducer接口,從而拿下system_server.

4.mediaserver注入詳解

我們需要經(jīng)過三步才能獲得system_server的權(quán)限,但是由于NX,ASLR,SELinux,je_malloc和多個binder 服務線程相結(jié)合帶來的障礙,每一步都很復雜,本節(jié)將以mediaserver為例詳細介紹如何通過libutils的漏洞注入mediaserver. 簡單來說,需要經(jīng)過5步才能成功。

1)控制binder服務線程

當使用je_malloc時,每一個線程都與一個特定的arena關(guān)聯(lián),不同的線程從堆上分配內(nèi)存時將使用不同的arena,每個arena又關(guān)聯(lián)一個或多個chunk,故分配給不同的線程的小塊堆內(nèi)存將使用不同的chunk, Android中每個chunk的大小是1MB。圖4展示了je_malloc堆的離散性。

Figure 4.je_malloc中堆的分布

binder 服務線程處理binder代理發(fā)起的binder調(diào)用,服務線程的調(diào)用隨著并行的binder調(diào)用的增加而增多,一般都有一個最大值。就mediaserver而言,進程剛啟動時binder服務線程的數(shù)目是4,最多可以增加到17個,圖5顯示了mediaserver中binder服務線程最多時的狀況.

Figure 5.mediaserver的服務線程

當一個binder調(diào)用到達mediaserver時,系統(tǒng)會隨機的選擇一個服務線程來處理這個調(diào)用,而我們知道,不同的線程分配的堆內(nèi)存位于不同的chunk中,所以,如果所有的線程都處于active狀態(tài)時,通過binder調(diào)用分配的內(nèi)存可能位于不同的chunk中,從而使得通過binder調(diào)用進行堆風水基本不可能。解決方法是只讓一個binder服務線程處于active狀態(tài),掛起其余的所有binder服務線程,從而多次binder調(diào)用所分配的堆內(nèi)存可以位于同一個chunk中。

IGraphicProducer接口可以通過attachBuffer函數(shù)將一個GraphicBuffer加入到bufferqueue.而每一個bufferqueue只能存儲特定數(shù)量的GraphicBuffer, Lollipop中是64個,當bufferqueue中的GraphicBuffer已經(jīng)達到64個后,如果還調(diào)用attachBuffer,處理attachBuffer的binder服務線程將會掛起,一直等到有bufferqueue中GraphicBuffer小于64為止,通過這種方法,可以掛起mediaserver中的16個binder線程,剩下的那個將服務我們發(fā)起的攻擊性binder調(diào)用。

2) 泄漏堆的內(nèi)容

因為ASLR的存在,要想利用次漏洞,我們需要從mediaserver中泄漏地址信息,在je_malloc中,同一個線程中分配的相同大小的小塊內(nèi)存將占據(jù)相鄰的region(je_malloc中有對region的定義),與dl_malloc不用的是,相鄰region之間沒有任何元數(shù)據(jù)。我們可以通過attachBuffer在mediaserver的堆上生成很多的native handle, native handle的結(jié)構(gòu)如下,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構(gòu)造一個畸形的native handle(numFds=-35 ,numInt=64),readNativeHandle會將相鄰的一個正常的native handle的numInts修改為較大的值, 當通過requestBuffer請求返回被修改的native handle 時,堆內(nèi)容將會被泄漏。?

Figure 6.從mediaserver中泄漏堆內(nèi)容

3)泄漏棧的基地址

因為NX的存在,我們需要使用ROP來繞過NX,而ROP需要能控制棧內(nèi)容,所有我們需要知道棧的位置。知道了棧的位置,我們可以通過此漏洞重寫棧,從而可以將堆破壞漏洞轉(zhuǎn)化為棧重寫從而執(zhí)行ROP。

我們已經(jīng)知道如何泄漏堆的內(nèi)容,所以我們可以在堆上搜索特定的數(shù)據(jù)結(jié)構(gòu)來泄漏棧的基址。被搜索的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)為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的內(nèi)容,從彩色高亮的區(qū)域我們可以得知這個結(jié)構(gòu)描述的線程的一些屬性,tid 是 0xb58, pid 是 0xa53, 線程的棧的基址在0xae7df000. 因為這個結(jié)構(gòu)中包含了線程的棧的基址,我們可以在泄漏的堆內(nèi)存中搜索這樣的結(jié)構(gòu)來得到棧的基址。搜索的特征為棧大小(0x000fe000) 和guard_size(0x00001000)。每一個通過pthread_create創(chuàng)建出來的binder服務線程都會分配一個pthread_internal_t的對象,一般來說,新創(chuàng)建出來的線程會被分配一個較大的tid. 因為mediaserver中除了binder服務線程外還有別的線程,別的線程的狀態(tài)不好控制,我們需要找到一個binder server線程對應的pthread_internal_t, 我們應該還記得binder server線程可以被動態(tài)觸發(fā)創(chuàng)建的,這樣就使得binder線程可以有一個較大的tid,我們可以搜索多個pthread_internal_t的對象,然后選擇其中tid最大的一個,則有很大的概率這個線程是處于掛起狀態(tài)的binder服務線程,處于掛起狀態(tài)的binder服務線程的調(diào)用棧是固定的,如圖7所示,我們可以通過重寫它的返回地址來觸發(fā)ROP的執(zhí)行.

Figure 7.處于阻塞狀態(tài)的binder服務線程

4)泄漏共享庫(so)基址

我們需要模塊的基值來來構(gòu)造ROP。為了有更多模塊來搜索ROP,我們可能需要泄漏多個模塊基址。泄漏libui.so的基址很容易。唯一需要做的是通過在泄漏的堆內(nèi)存中搜索GraphicBuffer對象,GraphicBuffer對象包含一個 android_native_base_的子結(jié)構(gòu)。這個結(jié)構(gòu)中的incRef和decRef是指向libui.so中相應函數(shù)的函數(shù)指針。找到這個結(jié)構(gòu)就能計算出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中調(diào)用libc的函數(shù),libui.so的GOT中有memcpy的地址,memcpy為libc.so中的函數(shù),如果能泄漏libui.so的GOT表我們就能獲得libc.so的基地址。由于這個漏洞的局限性,要泄漏libui.so的GOT并不容易。從前面章節(jié)得知,我們可以通過修改numFds和numInts來泄漏堆內(nèi)存,但這中方法只能泄漏連續(xù)的內(nèi)存,因為泄漏內(nèi)存的的邏輯是memcpy,如果泄漏的地址空間中有沒有被映射的內(nèi)存,則memcpy直接會發(fā)生段錯誤。令一個限制是binder調(diào)用只能返回小于1MB的內(nèi)存。堆內(nèi)存和libui.so直接一般情況都存在沒有映射的頁面,而且兩者直接的距離也超過了1MB,所以不能通過簡單的修改numFds和numInts來泄漏libui.so的GOT。因為GraphicBuffer包含了native handle的指針,我們可以修改這個指針,使其指向一個偽造的native handle對象,則可以泄漏地址空間中緊跟這個native handle對象后的內(nèi)存內(nèi)容。泄漏的大小由偽造的numFds和numInts決定。通過這樣的方法,我們同樣可以泄漏libc.so的GOT,從而得到dlopen和dlsym的地址,有了這兩個函數(shù),我們可以直接用高級語言寫shellcode.

5)控制je_malloc下次分配內(nèi)存的位置

通過構(gòu)造特殊的numFds和numInts,我們只能寫緊臨native handle的連續(xù)內(nèi)存,因為je_malloc中棧和堆的地址空間通常不是相鄰的。所有我們需要找到一個重寫棧的方法,也就是將這個漏洞轉(zhuǎn)換為一個任意地址寫的漏洞。我們可以通過修改je_malloc中的tcache的指針表來實現(xiàn). 當使用je_malloc并激活了tcache機制時,每一個線程都會為分配的小對象維護一個cache,這個cache存儲在一個叫tcache_t的結(jié)構(gòu)中。對每一種特定大小區(qū)間的對象,tcache_t都為其維護了一個指針表。在Android的je_malloc實現(xiàn)中,共有31大小區(qū)間。見下示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分配的堆內(nèi)存混合在一起,所以通過構(gòu)造numInts 和 numFds可以重寫這些指針。下面所示的gdb輸出為,分配大于112,小于等于128的內(nèi)存時所使用的tcache指針表。當通過je_malloc分配一個位于此區(qū)間的對象時,從avail開始索引為ncached-1的指針將是je_malloc返回的指針,通過將這個值修改為棧地址,那么,下一次分配特定大小對象,棧地址將被返回,寫被分配的對象內(nèi)存是,棧將被重寫。通過這種方法,我們可以控制棧內(nèi)容來執(zhí)行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的存在,普通應用創(chuàng)建的文件一般被標記為app_data_file 或 apk_data_file. mediaserver 沒有權(quán)限執(zhí)行帶這些標簽的文件. 所以不能在shellcode里使用system或dlopen執(zhí)行普通應用提供的執(zhí)行文件。幸運的是,mediaserver 有execmem 權(quán)限。

allow mediaserver self:process execmem;???????????? ————>SELinux policy

我們可以通過mprotect修改匿名內(nèi)存為可執(zhí)行(這在surfaceflinger中是不行的),從而能執(zhí)行shellcode. 然后在shellcode里實現(xiàn)從內(nèi)存中加載so的機制(詳見PoC),從而帶 app_data_file 或 apk_data_file的文件可以以二進制流的形式通過漏洞傳給mediaserver,然后通過load so from memroy模塊加載執(zhí)行。

Figure 8.?加載共享庫流程

因為mediaserver在Lollipop下沒有執(zhí)行/system/bin/sh的權(quán)限,我通過注入busybox到mediaserver來執(zhí)行shell命令,如果利用成功,可以得到一個從mediaserver反彈的shell(如圖9),細節(jié)請見PoC.

Figure 9.?帶mediaserver權(quán)限的shell

6.溢出surfaceflinger和system_server

溢出surfaceflinger 和 system_server與注入mediaserver相似,不夠有兩點需要注意:
1.surfaceflinger 沒有execmem權(quán)限,不能使用mprotect修改匿名內(nèi)存為可執(zhí)行,所有功能只能用RoP實現(xiàn).
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

總結(jié)

以上是生活随笔為你收集整理的Android libcutils库中整数溢出导致的堆破坏漏洞的发现与利用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。