漏洞分析
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
漏洞利用条件限制要几个重要地址:
track1
,track2
,sampleTable2
,SampleToChunkEntry2
。
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。。。
总结
- EXP利用MP4格式标准的其它Box申请了一些零碎的内存块创造内存间隙,在程序执行过程中这些零碎内存块很有可能被free,后面申请SampleToChunkEntry的内存就有可能出现在低地址,成功溢出到sampleTable指针并不造成崩溃。
- Heap Spray,通过tx3g Box堆喷2M数据,使其某一page出现在预测地址上。
- ROP和shellreversetcp中的SVC调用,mprotect函数绕过dep保护。
- mp4文件格式解析流程。
- 几个重要的内存地址的不确定性导致漏洞无法100%成功,并且rop中重要跳转指令直接硬编码,无法绕过aslr,但是溢出数据足够大能100%造成崩溃。
- 或者有其他细节我没理解到,又或者有更好的方法去布局内存分布,值得思考。
cve-2015-3864
cve-2015-3864是同系列中的另外一个漏洞,详见cve-2015-3864。
没有评论:
发表评论