CVE-2015-3864漏洞利用分析(exploit_from_google)
Posted hac425
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CVE-2015-3864漏洞利用分析(exploit_from_google)相关的知识,希望对你有一定的参考价值。
前言
接下来要学习安卓的漏洞利用相关的知识了,网上搜了搜,有大神推荐 stagefright
系列的漏洞。于是开干,本文分析的是 google
的 exploit
. 本文介绍的漏洞是 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
前四个字节为box
的size
第二个四字节为
box
的type
,box type
有ftyp,moov,trak
等等好多种,moov
是containerbox
,包含mvhd
、trak
等box
还有一些要注意的点。
box
中存储数据采用大端字节序存储
当size
域为 0时,表示这是文件最后一个box
- 当
size
为1 时,表示这是一个large box
,在type
域后面的8 字节
作为该box
的长度。
下面来看两个实例。
实例一
size
域为00000014
,所以该box
长度为0x14
字节。type
域为66 74 79 70
所以type
为fytp
- 剩下的一些信息是一些与多媒体播放相关的一些信息。与漏洞利用无关,就不说了。
实例二
size
域为1,表示从该box
开头偏移8字节开始的8字节为size
字段, 所以该box
的大小为0xFFFFFFFFFFFFFF88
type
为tx3g
现在我们对该文件的格式已经有了一个大概的了解,这对于漏洞利用来说还不够,接下来我们要去看具体的解析该文件格式的代码是怎么实现的。
解析文件的具体代码位于 MPEG4Extractor.cpp
中的 MPEG4Extractor::parseChunk
函数里面。
该函数中的 chunk
对应的就是 box
, 函数最开始先解析 type
和 size
.
// 开始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_size
是 uint64_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
中, mPssh
在 MPEG4Extractor::~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
分配 ABuffer
到 buffer
,chunk_data_size
在 box
的 size
域指定,注意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
中,然后会溢出到下一个 region
的 MPEG4DataSource
内存块。使用 cyclic
可以在程序 crash
时,计算 buffer
和 MPEG4DataSource
之间的距离。
第 13
行,调用了 memory_leak
函数, 该函数通过使用 pssh
来分配任意大小的内存,在这里分配的是 alloc_size
,即 0x20
. 因为MPEG4DataSource
的大小为 0x20
,就保证内存的分配会在同一个 run
中分配。这些这样这里分配了 4
个 0x20
的内存块,我认为是用来清理之前可能使用内存时,产生的内存碎片,确保后面内存分配按照我们的顺序进行分配。此时内存关系
| pssh | - | pssh |
第 17
到 25
行,清理内存后,开始分配 avcC
和 hvcC
, 大小也是 0x20
, 然后在第 25
行又进行了内存碎片清理,原因在于我们在分配 avcC
和 hvcC
时,会使用到 new ABuffer(chunk_data_size)
,这个临时的缓冲区,这个会在函数返回时被释放(请看智能指针相关知识)
同时多分配了几个 pssh
确保可以把 avcC
和 hvcC
包围在中间。所以现在的内存关系是
| pssh | - | pssh | pssh | avcC | hvcC | pssh |
然后是 第 29
行, 再次分配 hvcC
,不过这次的大小 为 alloc_size * 2
, 触发 hvcC
的释放,而且确保不会占用 刚刚释放的 内存.(jemalloc中 相同大小的内存在同一个run中分配)
| pssh | - | pssh | pssh | avcC | .... | pssh |
接下来构造 stbl
用 MPEG4DataSource
占据刚刚空出来的 内存。
| 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 ([email protected]=0xb74e2138, [email protected]=0xb550ca98, [email protected]=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_pivot
的 gadget
.
关于堆喷射
在看雪上大佬们进行了讨论
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
以上是关于CVE-2015-3864漏洞利用分析(exploit_from_google)的主要内容,如果未能解决你的问题,请参考以下文章