2016-03-13

CVE-2015-1538 libstagefright漏洞分析

漏洞分析

POC直接使用https://github.com/jduck/cve-2015-1538-1生成的mp4文件,作者说明了这个exp并不通用,我的测试机为nexus-5-android-4.4,只能造成崩溃。 崩溃信息:

    03-13 13:19:33.130 14267 14267 I DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    03-13 13:19:33.130 14267 14267 I DEBUG   : Build fingerprint: 'google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys'
    03-13 13:19:33.130 14267 14267 I DEBUG   : Revision: '11'
    03-13 13:19:33.130 14267 14267 I DEBUG   : pid: 17874, tid: 17928, name: Binder_1  >>> /system/bin/mediaserver <<<
    03-13 13:19:33.130 14267 14267 I DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 41d00010
    03-13 13:19:33.140 15745 15836 W NativeCrashListener: Couldn't find ProcessRecord for pid 17874
    03-13 13:19:33.180 14267 14267 I DEBUG   :     r0 41d00010  r1 00d00000  r2 001ed3c9  r3 00000000
    03-13 13:19:33.180 14267 14267 I DEBUG   : AM write failure (32 / Broken pipe)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     r4 b5b40030  r5 000000da  r6 2a0480a0  r7 00000a38
    03-13 13:19:33.180 14267 14267 I DEBUG   :     r8 0000000c  r9 b5b40034  sl 00000008  fp 2a0480a4
    03-13 13:19:33.180 14267 14267 I DEBUG   :     ip 41d00000  sp b5b40010  lr b697edaf  pc b697ed3a  cpsr 000f0030
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d0  0000001000000079  d1  0000000000000061
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d2  7472616b6d6f6f64  d3  6d696e666d6469a9
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d4  4242424242424242  d5  4242424242424242
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d6  4242424242424242  d7  4242424242424242
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d8  0000000000000000  d9  0000000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d10 0000000000000000  d11 0000000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d12 0000000000000000  d13 0000000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d14 0000000000000000  d15 0000000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d16 3f96de83a904ab51  d17 3f50624dd2f1a9fc
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d18 41cc356335800000  d19 3fc35fe27b800000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d20 3fc5533fc2a45f6a  d21 bf66be3f0c22dc9a
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d22 3fc2e2cedd55ea5c  d23 3fe0000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d24 3f88b0d283c30939  d25 bf88b0d28a518506
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d26 0000000000000000  d27 4000000000000000
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d28 3ffda3a6245542b4  d29 bfc4eaefa4251850
    03-13 13:19:33.180 14267 14267 I DEBUG   :     d30 3ff0000000000000  d31 3fe29d5df484a30a
    03-13 13:19:33.180 14267 14267 I DEBUG   :     scr 60000010
    03-13 13:19:33.180 14267 14267 I DEBUG   : 
    03-13 13:19:33.180 14267 14267 I DEBUG   : backtrace:
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #00  pc 0007dd3a  /system/lib/libstagefright.so (android::SampleTable::setSampleToChunkParams(long long, unsigned int)+137)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #01  pc 00062f8d  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+2816)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #02  pc 0006271f  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+658)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #03  pc 0006271f  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+658)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #04  pc 0006271f  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+658)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #05  pc 0006271f  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+658)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #06  pc 0006271f  /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+658)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #07  pc 00063aaf  /system/lib/libstagefright.so (android::MPEG4Extractor::readMetaData()+46)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #08  pc 00063d6d  /system/lib/libstagefright.so (android::MPEG4Extractor::getMetaData()+8)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #09  pc 0007f695  /system/lib/libstagefright.so (android::StagefrightMetadataRetriever::getFrameAtTime(long long, int)+28)
    03-13 13:19:33.180 14267 14267 I DEBUG   :     #10  pc 00035945  /system/lib/libmediaplayerservice.so (android::MetadataRetrieverClient::getFrameAtTime(long long, int)+64)
    ...

查看堆栈调用,最后崩溃在:android::SampleTable::setSampleToChunkParams(long long, unsigned int)+137,非法地址41d00010,该值与R0寄存器值相等,猜想对该寄存器操作导致。 ida反汇编libstagefright.so定位到该行代码

.text:0007DD2C loc_7DD2C                               ; CODE XREF: android::SampleTable::setSampleToChunkParams(long long,uint)+BC j
.text:0007DD2C                 LDR             R0, [R6,#8]
.text:0007DD2E                 MOV.W           R10, #8
.text:0007DD32                 MUL.W           R7, R8, R5
.text:0007DD36                 LDRD.W          R2, R3, [R6,#0x20]
.text:0007DD3A                 LDR             R1, [R0]     // crash!!!
.text:0007DD3C                 MOV.W           R11, #0
.text:0007DD40                 ADDS.W          R10, R10, R2
.text:0007DD44                 STMEA.W         SP, {R4,R8}
.text:0007DD48                 ADC.W           R11, R11, R3
.text:0007DD4C                 ADDS.W          R2, R10, R7
.text:0007DD50                 ADC.W           R3, R11, #0
.text:0007DD54                 LDR             R1, [R1,#0x1C]
.text:0007DD56                 BLX             R1    // 跳转可控!!!
.text:0007DD58                 CMP             R0, #0xC
.text:0007DD5A                 BEQ             loc_7DD5E
.text:0007DD5C                 B               loc_7DD72

0007DD3A处执行了LDR R1, [R0],获取R0寄存器的值,验证了该想法。注意下边BLX R1将跳转到R1执行代码,R1 = [[R0]+0x1C],所以如果R0可控,则可控制代码执行流程。 R0=41d00010,刚好是EXP中对喷的值sp_addr,所以构造好数据即可执行到shellcode中。

对照源码(附带注释):

status_t SampleTable::setSampleToChunkParams(
        off_t data_offset, size_t data_size) {
    if (mSampleToChunkOffset >= 0) {
        return ERROR_MALFORMED;
    }
  
  // 数据块偏移 
    mSampleToChunkOffset = data_offset;
  
  // 小于8说明无数据(头部)
    if (data_size < 8) {
        return ERROR_MALFORMED;
    }
  
  // 获取size大小,前8个字节
    uint8_t header[8];
    if (mDataSource->readAt(
                data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
        return ERROR_IO;
    }
  
  // 高4位是否为0(version,flags)
    if (U32_AT(header) != 0) {
        // Expected version = 0, flags = 0.
        return ERROR_MALFORMED;
    }
  
  // 获取低4位(Number Of Entries)
    mNumSampleToChunkOffsets = U32_AT(&header[4]);
  
  // 检测数据大小是否合法
    if (data_size < 8 + mNumSampleToChunkOffsets * 12) {
        return ERROR_MALFORMED;
    }
  
  // 创建SampleToChunkEntry对象 size=12
    mSampleToChunkEntries =
        new SampleToChunkEntry[mNumSampleToChunkOffsets];
  
  // 循环解析出每个SampleToChunkEntry数据
    for (uint32_t i = 0; i < mNumSampleToChunkOffsets; ++i) {
        uint8_t buffer[12];
        if (mDataSource->readAt(
                    mSampleToChunkOffset + 8 + i * 12, buffer, sizeof(buffer))
                != (ssize_t)sizeof(buffer)) {
            return ERROR_IO;
        }
 
        CHECK(U32_AT(buffer) >= 1);  // chunk index is 1 based in the spec.
 
        // We want the chunk index to be 0-based.
        mSampleToChunkEntries[i].startChunk = U32_AT(buffer) - 1;
        mSampleToChunkEntries[i].samplesPerChunk = U32_AT(&buffer[4]);
        mSampleToChunkEntries[i].chunkDesc = U32_AT(&buffer[8]);
    }
 
    return OK;
}

最后blx r1对应的代码为mDataSource->readAt(),mDataSource类型为sp<DataSource>,readAt函数在MPEG4DataSource中实现,位于虚表指针0x1C处,如下所示:

所以可以得出崩溃时R0为mDataSource指针,即堆喷时覆盖了mDataSource指针。

再看setSampleToChunkParams函数调用来源:

status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
 ...
 case FOURCC('s', 't', 's', 'c'):
        {
            status_t err =
                mLastTrack->sampleTable->setSampleToChunkParams(
                        data_offset, chunk_data_size);

            if (err != OK) {
                return err;
            }

            *offset += chunk_size;
            break;
        }
 ...
}

在解析stsc类型的数据中调用了该函数,查看sampleTable类,在其+8处刚好为setSampleToChunkParams函数,对照汇编最开始的LDR R0, [R6,#8],可知此时R6为sampleTable指针。

查看exp中stsc数据块内容

再结合前边源码setSampleToChunkParams解析过程:

1.检查数据块大小是否合法,即大于头部8个字节。

2.读取的mNumSampleToChunkOffsets为0xc0000003,经过mul计算后0xc0000003*0c=0x900000024,由于32位乘法指令导致上溢位0x24。 data_size为数据类型stsc前四个字节再减去8个字节的头部,即为0x34-0x8=0x2c,即通过了data_size < 8 + mNumSampleToChunkOffsets * 12的判断。

3.创建0xc0000003个SampleToChunkEntry对象,每个大小为12字节。

4.循环解析每个SampleToChunkEntry数据,由于EXP中stsc数据大小为0x1200字节,去掉前8个字节,则实际循环次数为(0x1200 / 0xc) - 1 = 0x17F次,如果在循环期间能够覆盖sampleTable或者mDataSource则必然崩溃。溢出的数据会不断填充高地址,所以要保证new出来的SampleToChunkEntry地址比他们低。

ida挂载调试,直接下断到 mSampleToChunkEntries = new SampleToChunkEntry[mNumSampleToChunkOffsets]. 最后可以看到三个地址如下:

加上在上级调用看到的mLastTrack地址,四个地址如下所示:

    mSampleToChunkEntries       = 0xb7fb5358
    mLastTrack                  = 0xb7fb59d0
    sampleTable                 = 0xb7fb5a98
    mDataSource                 = 0xb7fb50a8

0xb7fb5a98 - 0xb7fb5358 = 0x740,所以只要溢出超过0x740字节就必定能够覆盖sampleTable地址上的内容,因此LDR R0, [R6,#8]后R0获取的并不是mDataSource而是stsc中的数据。

但是如果只是覆盖了sampleTable地址,执行不到下边的blx r1就会发生崩溃,所以这种情况并不能控制代码执行流程。 在EXP中,通过覆盖Track对象的sampleTable指针,然后程序执行到Track对象的析构函数来控制流程。

在调试中可以知道几个重要结构的地址分布:mSampleToChunkEntries < Track < sampleTable,看下Track结构:

   struct Track {
        Track *next;
        sp<MetaData> meta;
        uint32_t timescale;
        sp<SampleTable> sampleTable;
        bool includes_expensive_metadata;
        bool skipTrack;
    };

其中sampleTable指针位于Track+0xC处,假如溢出覆盖到的地址为fakeaddr,如果Track+0xC < fakeaddr < sampleTable,那么就能覆盖掉Track对象的sampleTable指针并不引起崩溃。 当所有数据块数据解析完后,会调用MPEG4Extractor中的析构函数,遍历track链表执行delete操作。

delete函数会调用android::RefBase::decStrong

此时r0为sampleTable指针,利用方式和cve-2014-7911相同。

可利用条件

利用条件需要多次调试来确定,调试过程几个重要的地方:

    // sony xperia S android 4.04
    // 关闭aslr
    echo 0 > proc/sys/kernel/randomize_va_space 

    // 断点
    trak:
        /systembbstagefright.so+0x6CB68 | _ZN7android14MPEG4Extractor10parseChunkEPxi+0x208

    stsc:
        _ZN7android11SampleTable22setSampleToChunkParamsExj
        // (mSampleToChunkEntries = new SampleToChunkEntry[mNumSampleToChunkOffsets];)
        _ZN7android11SampleTable22setSampleToChunkParamsExj+0x66 

    MPEG4Extractor::~MPEG4Extractor
        _ZN7android14MPEG4ExtractorD1Ev

    decStrong:
        _ZNK7android7RefBase9decStrongEPKv

漏洞利用条件限制要几个重要地址: track1track2sampleTable2SampleToChunkEntry2。 MPEG4Extractor在析构是通过track链表遍历逐个删除,那么覆盖掉track1或者track2的sampleTable指针都可以控制程序流程。 为保证程序不崩,溢出的地址必须位于track2和sampleTable2中间,所以可利用条件为: SampleToChunkEntry2 + overflowSize < (track1 | track2) + 0xC < sampleTable2 上边条件需要多次调试来调整overflowSize大小。

比如某次调试中发现: track1 = 0x2D3D8 track2 = 0x2E458 SampleToChunkEntry2 = 0x2DB00 sampleTable2 = 0x2F010

sampleTable2 - SampleToChunkEntry2 = 0x1510 track2 - SampleToChunkEntry2 = 0x958

满足利用条件,即SampleToChunkEntry2溢出以后能够成功覆盖track2并且程序不崩溃。

另外exp中的堆喷地址spaddr需要调试去确定一个概率比较高的地址,libc.so:restorecore_regs地址也需要修改。

exp在实际测试中成功率很低,原因都是由于溢出数据覆盖掉了sampleTable地址的数据,导致调用mDataSorce时崩溃,或者覆盖的地址高于track和sampleTable几个对象以外的其他数据,导致应用解析失败或者crash。

另一篇分析文章一步一步调通stagefright exploit说明的tx3g位置问题我在调试时也验证了,SampleToChunkEntry2地址位于track1和track2之间的几率相对来说比较大,但是还是有几率落在track1之前,溢出大小我的机器上调试过程中,在能成功率用的内存布局情况下,track2和SampleToChunkEntry2的距离基本是在0x900~0x1200之间,所以原作者的溢出值并没有问题,需要针对不同机器调试确认,下边是我实际调试中选取的10组数据。

index track1 track2 SampleToChunkEntry2 sampleTable2 overflow_size is_exploit
0 0x2d2d8 0x2d298 0x2e100 0x2e9f0 x no
1 0x2d698 0x15e00 0x2db70 0x2ede0 x no
2 0x40cb0 0x407f0 0x3ef98 0x44528 0x1858 yes
3 0x2d288 0x2d360 0x2d760 0x2ec70 x no
4 0x3d500 0x400a0 0x42e48 0x3fa00 x no
5 0x40a38 0x3ccf0 0x428a8 0x3e530 x no
6 0x2d3d8 0x2e458 0x2db00 0x2f010 0x958 yes
7 0x418e0 0x3feb8 0x43f38 0x3fb90 x no
8 0x3d548 0x410f0 0x3f890 0x3fa88 x no
9 0x3cf78 0x3f690 0x3e5e0 0x42a40 0x10b0 yes

漏洞利用

析构函数执行过后会跳到ROP中,rop布局好数据后先是调用mprotect函数将spaddr地址长度为0x1000改为可执行权限,然后跳入shellreverse_tcp代码中执行,参考CVE-2015-1538漏洞利用中的Shellcode分析

这个过程中所有函数的调用通过SVC指令完成,类似于Intel CPU中的int 0x80中断,SVC指令会根据相应的调用号去执行相应的函数,这些编号定义在include\arm\unistd.h中。

#define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)
#define __NR_exit (__NR_SYSCALL_BASE+ 1)
#define __NR_fork (__NR_SYSCALL_BASE+ 2)
#define __NR_read (__NR_SYSCALL_BASE+ 3)
#define __NR_write (__NR_SYSCALL_BASE+ 4)
#define __NR_open (__NR_SYSCALL_BASE+ 5)
#define __NR_close (__NR_SYSCALL_BASE+ 6)
#define __NR_creat (__NR_SYSCALL_BASE+ 8)
#define __NR_link (__NR_SYSCALL_BASE+ 9)
#define __NR_unlink (__NR_SYSCALL_BASE+ 10)
#define __NR_execve (__NR_SYSCALL_BASE+ 11)
#define __NR_chdir (__NR_SYSCALL_BASE+ 12)

影响

/system/core/rootdir/init.rc定义了mediaserver用户组权限

service media /system/bin/mediaserver 511    
class main    
user media  
group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm 
ioprio rt 4

system/core/include/private/androidfilesystemconfig.h 所以可以访问audio camera bluetooth。。。

总结

  1. EXP利用MP4格式标准的其它Box申请了一些零碎的内存块创造内存间隙,在程序执行过程中这些零碎内存块很有可能被free,后面申请SampleToChunkEntry的内存就有可能出现在低地址,成功溢出到sampleTable指针并不造成崩溃。
  2. Heap Spray,通过tx3g Box堆喷2M数据,使其某一page出现在预测地址上。
  3. ROP和shellreversetcp中的SVC调用,mprotect函数绕过dep保护。
  4. mp4文件格式解析流程。
  5. 几个重要的内存地址的不确定性导致漏洞无法100%成功,并且rop中重要跳转指令直接硬编码,无法绕过aslr,但是溢出数据足够大能100%造成崩溃。
  6. 或者有其他细节我没理解到,又或者有更好的方法去布局内存分布,值得思考。

cve-2015-3864

cve-2015-3864是同系列中的另外一个漏洞,详见cve-2015-3864

没有评论:

发表评论

Android Root Zap Framework

‎ 1. Warning 请遵守GPL开源协议, 请遵守法律法规, 本项目仅供学习和交流, 请勿用于非法用途! 道路千万条, 安全第一条, 行车不规范, 亲人两行泪. 2. Android Root Zap Frame...