• CVE-2015-3864漏洞利用分析(exploit_from_google)


    title: CVE-2015-3864漏洞利用分析(exploit_from_google)
    author: hac425
    tags:

    • CVE-2015-3864
    • 文件格式漏洞
      categories:
    • 安卓安全
      date: 2017-11-21 23:17:00

    前言

    接下来学习安卓的漏洞利用相关的知识,网上搜了搜,有大神推荐 stagefright 系列的漏洞。于是开干,本文分析的是 googleexploit. 本文介绍的漏洞是 CVE-2015-3864 , 在 google的博客上也有对该 exploit 的研究。

    我之前下载下来了:

    pdf版本 的链接:在这里

    exploit 的链接: https://www.exploit-db.com/exploits/38226/

    分析环境:

    Android 5.1 nexus4 
    

    正文

    这个漏洞是一个文件格式相关漏洞,是由 mediaserver 在处理 MPEG4 文件时所产生的漏洞,漏洞的代码位于 libstagefright.so 这个库里面。

    要理解并且利用 文件格式 类漏洞,我们就必须要非常清楚的了解目标文件的具体格式规范。

    Part 1 文件格式学习

    先来一张总体的格式图

    mp4 文件由 box 组成,图中那些 free, stsc等都是box, box 里也可以包含 box ,这种 box 就叫 containerbox .

    • 每个 box 前四个字节为 boxsize

    • 第二个四字节为 boxtypebox typeftyp,moov,trak 等等好多种,moovcontainerbox ,包含 mvhdtrakbox

    还有一些要注意的点。

    • box 中存储数据采用大端字节序存储
    • size 域为 0时,表示这是文件最后一个 box
    • size 为1 时,表示这是一个 large box ,在 type 域后面的 8 字节 作为该 box 的长度。

    下面来看两个实例。

    实例一

    • size 域为 00000014,所以该 box长度为 0x14 字节。
    • type 域为 66 74 79 70 所以 typefytp
    • 剩下的一些信息是一些与多媒体播放相关的一些信息。与漏洞利用无关,就不说了。

    实例二

    • size 域为1,表示从该 box 开头偏移8字节开始的8字节为 size 字段, 所以该 box 的大小为 0xFFFFFFFFFFFFFF88
    • typetx3g

    现在我们对该文件的格式已经有了一个大概的了解,这对于漏洞利用来说还不够,接下来我们要去看具体的解析该文件格式的代码是怎么实现的。

    解析文件的具体代码位于 MPEG4Extractor.cpp 中的 MPEG4Extractor::parseChunk 函数里面。
    该函数中的 chunk 对应的就是 box, 函数最开始先解析 typesize .

    	// 开始4字节为 box 大小, 后面紧跟的 4 字节为 box type
    	
        uint64_t chunk_size = ntohl(hdr[0]);
        uint32_t chunk_type = ntohl(hdr[1]); //大端序转换
        off64_t data_offset = *offset + 8; 	// 找到 box 数据区的偏移
    
    	// 如果size区为1, 那么后面8字节作为size
        if (chunk_size == 1) {
            if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
                return ERROR_IO;
            }
            chunk_size = ntoh64(chunk_size);
            data_offset += 8;
    
            if (chunk_size < 16) {
                // The smallest valid chunk is 16 bytes long in this case.
                return ERROR_MALFORMED;
            }
        } else if (chunk_size < 8) {
            // The smallest valid chunk is 8 bytes long.
            return ERROR_MALFORMED;
        }
    
    

    通过注释和代码,我们知道对于 size 的处理和前面所述是一致的。然后就会根据不同的 chunk_type ,进入不同的逻辑,

    如果 box 中还包含 子 box 就会递归调用该函数进行解析。

    Part 2 漏洞分析

    CVE-2015-3864 漏洞产生的原因是,在处理 tx3g box时,对于获取的 size 字段处理不当,导致分配内存时出现整数溢出,进而造成了堆溢出。

    size 为之前所解析的所有 tx3g box 的长度总和。chunk_size 为当前要处理的 tx3g box 的长度。然后 size + chunk_size 计算要分配的内存大小。 chunk_sizeuint64_t 类型的,chunk_size 我们在文件格式中我们所能控制的最大大小为 0xFFFFFFFFFFFFFFFF ( 看 part1 实例二 ) ,也是 64 位,但是我们还有一个 size 为可以控制,这样一相加,就会造成 整数溢出 , 导致分配小内存。而我们的 数据大小则远远大于分配的内存大小,进而造成堆溢出

    Part 3 漏洞利用

    概述

    现在我们已经拥有了堆溢出的能力,如果是在 ptmalloc 中,可以修改下一个堆块的元数据来触发 crash ,甚至可能完成漏洞利用。不过从 android 5开始,安卓已经开始使用 jemalloc 作为默认的堆分配器。

    jemalloc 中,小内存分配采用 regions 进行分配, region 之间是没有 元数据 的 (具体可以去网上搜 jemalloc 的分析的文章),所以 在 ctf 中常见的通过修改 堆块元数据 的漏洞利用方法在这里是没法用了。

    不过所有事情都有两面性。region 间是直接相邻的,那我就可以很方便的修改相邻内存块的数据。 如果我们在 tx3g 对应内存块的后面放置一个含有关键数据结构的内存块,比如一个对象,在 含有虚函数 的类的 对象开始4字节(32位下),会存放一个 虚表指针 .

    对象 调用 虚函数 时会从 虚表指针 指向的位置的 某个偏移(不同函数,偏移不同) 处取到相应的函数指针,然后跳过去执行。

    如果我们修改对象的虚表指针,我们就有可能在程序调用虚函数时,控制程序的流程。

    一些重要的 chunk_type(box type)

    tx3g box

    上一节提到,我们可以修改对象的虚表指针,以求能够控制程序的跳转。那我们就需要找到一个能够在解析 box 数据能时分配的对象。

    MPEG4DataSource 就是这样一个类。

    可以看到该对象继承自 DataSource, 同时还有几个虚函数。

    我们可以在ida中看看虚表的构成。

    可以看到 readAt 方法在虚表的第7项,也就是虚表偏移 0x1c 处。同时MPEG4DataSource在我这的大小为 0x20 .再看一下漏洞位置的代码。

    可以看到如果当前解析的 tx3g box 不是第一个tx3g box(即size>0),会先调用 memcpy , 把之前所有 tx3g box中的数据拷贝到刚刚分配的内存。

    如果我们先构造一个 tx3g ,其中包含的数据大于 0x20, 然后在构造一个 tx3g 构造大小使得 size+chunk_size = 0x20, 然后通过 memcpy 就可以覆盖 MPEG4DataSource 的虚表了。exploit 中就是这样干的。

    pssh box

    看看代码

    划线位置说明了 pssh 的结构。

        pssh 的结构
        开始8字节 表示 该 box 的性质
        
        00 00 00 40 70 73 73 68
        size: 0x40, 
        type: pssh :
        + 0xc 开始 16字节 为 pssh.uuid
        + 0x1c开始4字节为 pssh.datalen
        + 0x20 开始为 pssh.data
        可以查看 代码,搜索关键字: FOURCC('p', 's', 's', 'h')
    

    这里先分配 pssh.datalen 大小的内存,然后把 pssh.data 拷贝到刚刚分配的内存。完了之后会把 分配到的 PsshInfo 结构体增加到 类属性值 Vector<PsshInfo> mPssh 中, mPsshMPEG4Extractor::~MPEG4Extractor() 中才会被释放。

    所以在解析完 MPEG4格式前,通过 pssh 分配的内存会一直在内存中。

    avcC box 和 hvcC box
    这两个 box 的处理基本一致,以 avcC 为例进行介绍。解析代码如下

            case FOURCC('a', 'v', 'c', 'C'):
            {
            	// 这是一块临时分配, buffer 为智能指针,在 函数返回时相应内存会被释放。
                sp<ABuffer> buffer = new ABuffer(chunk_data_size);
                if (mDataSource->readAt(
                            data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
                    return ERROR_IO;
                }
    			// 在这里,会释放掉原来那个,新分配内存来容纳新的数据。
    			// 因此我们有了一个 分配,释放 内存能力
    			// setData 中会释放掉原来的buf, 新分配一个 chunk_data_size
    
                mLastTrack->meta->setData(
                        kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
    
                *offset += chunk_size;
                break;
            }
    

    首先根据 chunk_data_size 分配 ABufferbufferchunk_data_sizeboxsize 域指定,注意buffer是一个智能指针,在这里,它会在函数返回时释放。

    ABuffer 中是直接调用的 malloc 分配的内存。

    接下来读取数据到 buffer->data(), 最后调用 mLastTrack->meta->setData 保存数据到 meta, 在 setData 内部会先释放掉之前的内存,然后分配的内存,存放该数据,此时分配内存的大小还是chunk_data_size, 我们可控。

    hvcC 的处理方式基本一样。所以通过这两个 box 我们可以 分配指定大小的内存,并且可以随时释放前面分配的那个内存块 。我们需要使用这个来布局tx3g内存块 和 MPEG4DataSource 内存块。

    修改对象虚表指针

    下面结合exploit 和上一节的那几个关键 box ,分析通过布局内存,使得我们可以修改 MPEG4DataSource 的虚表指针。
    为了便于说明,取了 exploit 中的用于 修改对象虚表指针的相关代码进行解析 ( 我调试过程做了部分修改 )

    首先看到第7,8行,构造了第一个 tx3g box, 大小为 0x3a8, 后面在触发漏洞时,会先把这部分数据拷贝到分配到的小内存buffer中,然后会溢出到下一个 regionMPEG4DataSource 内存块。使用 cyclic 可以在程序 crash 时,计算 bufferMPEG4DataSource 之间的距离。

    13 行,调用了 memory_leak 函数, 该函数通过使用 pssh 来分配任意大小的内存,在这里分配的是 alloc_size ,即 0x20. 因为MPEG4DataSource 的大小为 0x20 ,就保证内存的分配会在同一个 run 中分配。这些这样这里分配了 40x20 的内存块,我认为是用来清理之前可能使用内存时,产生的内存碎片,确保后面内存分配按照我们的顺序进行分配。此时内存关系

    | pssh | - | pssh |
    

    1725 行,清理内存后,开始分配 avcChvcC, 大小也是 0x20, 然后在第 25 行又进行了内存碎片清理,原因在于我们在分配 avcChvcC时,会使用到 new ABuffer(chunk_data_size),这个临时的缓冲区,这个会在函数返回时被释放(请看智能指针相关知识)

    同时多分配了几个 pssh 确保可以把 avcChvcC包围在中间。所以现在的内存关系是

    | pssh | - | pssh | pssh | avcC | hvcC | pssh |
    

    然后是 第 29 行, 再次分配 hvcC ,不过这次的大小 为 alloc_size * 2, 触发 hvcC 的释放,而且确保不会占用 刚刚释放的 内存.(jemalloc中 相同大小的内存在同一个run中分配)

    | pssh | - | pssh | pssh | avcC | .... | pssh |
    
    

    接下来构造 stblMPEG4DataSource 占据刚刚空出来的 内存。

    | pssh | - | pssh | pssh | avcC | MPEG4DataSource | pssh |
    

    接下来, 第 38 行用同样的手法分配释放 avcC

    | pssh | - | pssh | pssh | .... | MPEG4DataSource | pssh |
    
    

    然后使用整数溢出,计算得到第二个 tx3g 的长度值,使得最后分配到的内存大小为0x20, 用来占据刚刚空闲的 avcC 的 内存块,于是现在的内存布局,就会变成这样。

    | pssh | - | pssh | pssh | tx3g | MPEG4DataSource | pssh |
    

    然后在

    就会溢出修改了 MPEG4DataSource 的虚表指针。然后在下面的 readAt 函数调用出会 crash.

    我测试时得好几次才能成功一次,估计和内存碎片相关。

    Thread 10 received signal SIGSEGV, Segmentation fault.
    0xb66b57cc in android::MPEG4Extractor::parseChunk (this=this@entry=0xb74e2138, offset=offset@entry=0xb550ca98, depth=depth@entry=0x2) at frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1905
    1905	            if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
    ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
    $r0   : 0xb74e27b8  →  0x61616169 ("iaaa"?)
    $r1   : 0xb74e2bb8  →  0x00000000
    $r2   : 0x61616169 ("iaaa"?)
    $r3   : 0x00000000
    $r4   : 0xb550c590  →  0x00000428
    $r5   : 0xfffffbf8
    $r6   : 0xb550c580  →  0xb74e5c98  →  0x28040000
    $r7   : 0xb550c570  →  0xfffffbf8
    $r8   : 0xb74e2138  →  0xb6749f18  →  0xb66b2841  →  <android::MPEG4Extractor::~MPEG4Extractor()+1> ldr r3,  [pc,  #188]	; (0xb66b2900 <android::MPEG4Extractor::~MPEG4Extractor()+192>)
    $r9   : 0x74783367 ("g3xt"?)
    $r10  : 0xb550ca98  →  0x01000a98
    $r11  : 0xb74e2790  →  0x28040000
    $r12  : 0x00000000
    $sp   : 0xb550c530  →  0xb74e2bb8  →  0x00000000
    $lr   : 0xb66b57bd  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r1,  [r4,  #0]
    $pc   : 0xb66b57cc  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r6,  [r2,  #28]
    $cpsr : [THUMB fast interrupt overflow carry ZERO negative]
    ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    $r0   : 0x00000000
    $r1   : 0xb74e2bb8  →  0x00000000
    $r2   : 0x61616169 ("iaaa"?)
    $r3   : 0x00000000
    $r4   : 0xb550c590  →  0x00000428
    $r5   : 0xfffffbf8
    $r6   : 0xb550c580  →  0xb74e5c98  →  0x28040000
    $r7   : 0xb550c570  →  0xfffffbf8
    $r8   : 0xb74e2138  →  0xb6749f18  →  0xb66b2841  →  <android::MPEG4Extractor::~MPEG4Extractor()+1> ldr r3,  [pc,  #188]	; (0xb66b2900 <android::MPEG4Extractor::~MPEG4Extractor()+192>)
    $r9   : 0x74783367 ("g3xt"?)
    $r10  : 0xb550ca98  →  0x01000a98
    $r11  : 0xb74e2790  →  0x28040000
    $r12  : 0x00000000
    $sp   : 0xb550c530  →  0xb74e2bb8  →  0x00000000
    $lr   : 0xb66b57bd  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r1,  [r4,  #0]
    $pc   : 0xb66b57cc  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r6,  [r2,  #28]
    $cpsr : [THUMB fast interrupt overflow carry ZERO negative]
    

    可以看到断在了<android::MPEG4Extractor::parseChunk(long+0> ldr r6, [r2, #28],去 ida 里面找到对应的位置。

    r2存放的就是虚表指针,可以确定成功修改了 虚函数表指针。

    偏移也符合预期。

    堆喷射

    上面我们已经成功修改了MPEG4DataSource 的虚表指针,并在虚函数调用时触发了 crash .

    我们现在能够修改对象的 虚表指针,并且能够触发虚函数调用。我们需要在一个可预测的内存地址精准的布置我们的数据,然后把虚表指针修改到这里,在 exploit 中使用了

    spray_size = 0x100000
    spray_count = 0x10
    
    sample_table(heap_spray(spray_size) * spray_count)
    

    来进行堆喷射

    heap_spray 函数 就是使用 pssh 来喷射的内存。每次分配 0x100 页,共分配了 0x10 次。 exploit 作者在 博客中写道,这样就可以在可预测的内存地址中定位到特定数据。在这里就是 用于 stack_pivotgadget.

    关于堆喷射

    在看雪上大佬们进行了讨论

    https://bbs.pediy.com/thread-222893-1.htm

    最后

    这个 exploit 写的确实强悍,提示我在进行漏洞利用时,要关注各种可能分配内存的地方,灵活的使用代码中的内存分配,来布局内存。 同时研究一个漏洞要把相关知识给补齐。对于这个漏洞就是 MPEG4 的文件格式和 相关的处理代码了。

    一些tips:

    • 使用 gef + gdb-multiarch 来调试 , pwndbg 我用着非常卡, gef 就不会
    • 调试过程尽量使用脚本减少重复工作量。

    使用的一些脚本。

    使用 gdbserver attach mediaserver 并转发端口的脚本

    adb root
    adb forward tcp:1234 tcp:1234
    a=`adb shell "ps | grep mediaserver" |  awk '{printf $2}'`
    echo $a
    adb shell "gdbserver --attach :1234 $a"
    
    

    gdb 的调试脚本

    set arch armv5
    gef-remote 127.0.0.1:1234
    set solib-search-path debug_so/
    directory android-5.1.0_r3/
    gef config context.layout "regs -source"
    set logging file log.txt
    set logging on
    break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1897 
    break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1630
    break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1647
    break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:884
    commands 1
    p chunk_size
    p buffer
    c
    end
    
    commands 2
    p buffer
    
    end
    
    commands 3
    p buffer
    c
    end
    
    commands 4
    hexdump dword  mDataSource 0x4
    c
    end
    
    
    

    参考:

    https://census-labs.com/media/shadow-infiltrate-2017.pdf

    https://googleprojectzero.blogspot.hk/

    http://blog.csdn.net/zhuweigangzwg/article/details/17222951

  • 相关阅读:
    Java线程--LockSupport使用
    Java线程--Callable使用
    Java线程--BlockingQueue使用
    Java线程--CopyOnWrite容器使用
    Java线程--ReentrantLock使用
    Java线程--Atomic原子类使用
    Java线程--ThreadPoolExecutor使用
    Java线程--ForkJoinPool使用
    自己动手搭建SSM
    为什么选择SSM+Redis框架开发模式?
  • 原文地址:https://www.cnblogs.com/hac425/p/9416939.html
Copyright © 2020-2023  润新知