15分钟看懂Android stagefright漏洞(以两枚MPEG4_covr漏洞为例)
更新日期:
扣吧力作,欢迎转载,转载请注明来自colbert337.github.io
StageFright漏洞有多犀利
什么是StageFright
StageFright是一个Android中的系统服务,会被mediaserver调用,可处理各种多媒体格式,由Natvie C++代码实现:
漏洞简介
Android Stagefright框架中发现了多个integer overflow/underflow等漏洞,可导致拒绝服务,甚至有任意代码执行等问题。
攻击者通过发送包含特制媒体文件的MMS或WEB页来触发该漏洞。由于stagefright不只是用来播放媒体文件的,还能自动产生缩略图,或者从视频或音频文件中抽取元数据,如长度、高度、宽度、帧频、频道和其他类似信息。因此接收到恶意彩信的用户只要查看缩略图就可触发该漏洞。
漏洞影响
“Stagefright”媒体播放引擎库在Android 2.2中引入,至5.1的所有版本上均存在此漏洞,预计会有95%的Android设备,约有九亿五千万的安卓设备受该漏洞影响.
使用Stagefright库的应用程序以Media权限运行,成功利用漏洞,允许攻击者浏览器媒体库相应的文件,但通过权限提升攻击,可完全控制设备。
该Stagefright漏洞所对应的CVE ID如下:
CVE-2015-1538
CVE-2015-1539
CVE-2015-3824
CVE-2015-3826
CVE-2015-3827
CVE-2015-3828
CVE-2015-3829
MPEG4文件格式基础
这里需要大家简单了解一下MPEG4文件格式。
MP4(MPEG-4 Part 14)是一种常见的多媒体容器格式,它是在“ISO/IEC 14496-14”标准文件中定义的。
MP4是一种描述较为全面的容器格式,被认为可以在其中嵌入任何形式的数据,各种编码的视频、音频等都不在话下。MP4格式的官方文件后缀名是“.mp4”,还有其他的以mp4为基础进行的扩展或者是缩水版本的格式,包括:M4V, 3GP, F4V等。
基础知识:
1、 MP4文件,由许多Box和FullBox组成。
2、 Box,每个Box由Header和Data组成,基本结构是:
3、 FullBox,是Box的扩展,Box结构的基础上在Header中增加8bits version和24bits flags。
4、 Header很重要,包含了整个Box的长度size和类型type。
当size==0时,代表这是文件中最后一个Box;
当size==1时,意味着Box长度需要更多bits来描述,超过了uint32的最大数值,会用往后偏移8位后的8位uint64来存放大小。(有点拗口,但很重要!)
当type是uuid时,代表Box中的数据是用户自定义扩展类型。
5、 Data,是Box的实际数据,可以是纯数据也可以是更多的子Boxes。
6、 当一个Box的Data中是一系列子Box时,这个Box又可成为Container Box。
如图所示:
两枚MPEG4_covr漏洞分析
Stagefright漏洞有7个,为了方便大家理解,笔者只举两个漏洞作为例子。
这两个漏洞都是出现在对MPEG4文件抽取解析covr box的地方,故笔者将其命名为【MPEG4_covr漏洞】
选取了Android 5.1.0_r1的代码来分析,链接如下:
1 | http://androidxref.com/5.1.0_r1/xref/frameworks/av/media/libstagefright/MPEG4Extractor.cpp |
来来来,上代码,这段代码里面有两个漏洞,你能看出来吗?
1 | case FOURCC('c', 'o', 'v', 'r'): |
第一个漏洞
integer overflow when processing covr MPEG4 atoms
1 | sp<ABuffer> buffer = new ABuffer(chunk_data_size + 1); |
由于未对chunk_data_size的长度进行限制,当chunk_data_size = SIZE_MAX = 0xFFFFFFFF,chunk_data_size + 1就会发生整数溢出,使得chunk_data_size + 1 = 0,造成了buffer申请内存的长度为0,然后readAt函数中再对buffer进行拷贝数据操作,导致堆越界。
看看readAt函数的实现,发现直接就用memcpy往*data里面写内容了(也就是buffer->data()),肯定会写穿。1
2
3
4
5
6
7
8
9
10
11ssize_t MPEG4DataSource::readAt(off64_t offset, void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
if (offset >= mCachedOffset
&& offset + size <= mCachedOffset + mCachedSize) {
memcpy(data, &mCache[offset - mCachedOffset], size);
return size;
}
return mSource->readAt(offset, data, size);
}
补丁链接:1
https://github.com/CyanogenMod/android_frameworks_av/commit/c50f5a29f50515c88f1430a1982aea5e19e19b0a
POC也很简单,只需要 chunk_data_size = 0xFFFFFFFF,那么如何做到呢?
仔细阅读MPEG4Extractor.cpp前面的代码:
1 | off64_t chunk_data_size = *offset + chunk_size - data_offset; |
1 | off64_t data_offset = *offset + 8; |
可以推导出1
*offset + chunk_size - (*offset + 8) = 0xFFFFFFFF
也就是1
chunk_size = 0xFFFFFFFF + 8
我们回顾一下BOX Header的数据结构:
1 | uint64_t chunk_size = ntohl(hdr[0]); |
从代码可以看出:
1) chunk_size其实就是MP4格式的BOX Header中的的Box长度size
2) 这里的chunk_type则是(int)’covr’
3) chunk_size的类型是uint64_t,是可以大于0xFFFFFFFF的:
继续看代码:1
2
3
4
5
6if (chunk_size == 1) {
if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
return ERROR_IO;
}
chunk_size = ntoh64(chunk_size);
data_offset += 8;
当chunk_size == 1时,就读取*offset + 8处的8个字节,作为chunk_size的值,同时data_offset会加8。(也就是MP4格式基础里面说的,当size==1时,会用往后偏移8位后的8位uint64来存放大小)
由于*offset就是BOX Header的开头,所以我们构造BOX Header的前8位为:1
00 00 00 01 ‘c’ ‘o’ ‘v’ ‘r’
此时,*offset+8的地方就是真正的chunk_size了,但由于chunk_size == 1时候,data_offset += 8,所以上面的公式已经不成立,需要重新计算:
1 | *offset + chunk_size - (*offset + 8 + 8) = 0xFFFFFFFF |
最后得出chunk_size = 0xFFFFFFFF + 16 = 0x10000000F
所以最后我们构造BOX Header的为:1
00 00 00 01 ‘c’ ‘o’ ‘v’ ‘r’ 00 00 00 01 00 00 00 0F
第二个漏洞
integer underflow in covr MPEG4 processing
问题出在这里:1
2
3
4const int kSkipBytesOfDataBox = 16;
mFileMetaData->setData(
kKeyAlbumArt, MetaData::TYPE_NONE,
buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
看setData的第三个参数,由于未检测chunk_data_size的长度,如果chunk_data_size – kSkipBytesOfDataBox为负数,会导致整数下溢,变成一个非常大的无符号整型。
setData的第三个参数很大会怎么样呢?再看看setData的实现:1
2
3
4
5
6
7
8void MetaData::typed_data::setData(
uint32_t type, const void *data, size_t size) {
clear();
mType = type;
allocateStorage(size);
memcpy(storage(), data, size);
}
setData的第三个参数的类型是size_t,当传入size=0xFFFFFFFF,allocateStorage(size)就会失败,返回null,而下一行代码memcpy没有做判断就往storage()写内容了,导致出错。
补丁链接:1
https://github.com/CyanogenMod/android_frameworks_av/commit/b1f29294f1a5831eb52a81d3ee082a9475f6e879
第二个漏洞的POC就简单多了,因为kSkipBytesOfDataBox = 16,所以推导过程如下:
chunk_data_size <= kSkipBytesOfDataBox
推到出 offset + chunk_size - offset - 8 <= 16
推到出 chunk_size - 8 <= 16
推到出 chunk_size <= 24(0x18)
所以当chunk_size的值<=24时会触发漏洞(chunk_size不能等于1哦)
我们构造BOX Header的一个样本(chunk_size=0x07):1
00 00 00 07 ‘c’ ‘o’ ‘v’ ‘r’
攻击路径
发送嵌入恶意视频文件的彩信
构建嵌入恶意视频的WEB页,诱使用户打开
在SD卡构建恶意视频文件,诱使用户打开
解决方案建议
尽快打补丁
禁用Stagefright服务
不要轻易打开陌生人发的含有MP4的彩信
留心恶意URL和恶意视频文件