flawopen.com/Teardowns/cve-2015-3824-android-stagefright-tx3g-integer-overflow

● CVE-2015-3824 · CVSS v2 10.0 · 高危
安全研究 · FlawOpen

CVE-2015-3824:Android libstagefright 中 Stagefright tx3g 整数溢出

在解析 MPEG-4 字幕原子 tx3g 时,一次未经检查的大小相加发生回绕,只分配了极小的堆缓冲区,使精心构造的视频无需任何用户操作即可破坏 mediaserver 的内存。

💡 通俗易懂的原理解析 (ELI5)

想象一家印刷店,订单表上只有四个格子用来填写横幅长度。档案里已经记了 9998 米,你又要求再加 5 米。店员把总数写进四个格子,数字回绕成了 0003,于是店里只裁了一张 3 米长的纸。接着印刷机把整整 10003 米的文字印到这张纸上,油墨溢出来弄脏了桌上其他顾客的订单。Stagefright 对内存做的就是同样的事:大小之和发生回绕,只分配了一个极小的缓冲区,却把全部视频数据写了进去。

核心概念与专有名词

Stagefright(libstagefright)
Android 原生多媒体库,在特权进程 mediaserver 中解析音频和视频文件。
MPEG-4 原子(atom)
MP4 文件中的自描述数据块:声明的大小、四个字符的类型以及数据。解析器依据声明的大小决定读取多少字节。
tx3g
3GPP 定时文本原子,保存文本轨道中字幕的格式信息。
整数溢出
运算结果超出整数类型的表示范围后回绕成一个很小的值,例如 SIZE_MAX + 2 变成 1。

根本原因剖析 (Root Cause)

MPEG4Extractor::parseChunk() 将已保存的文本格式数据大小与攻击者可控的 tx3g 原子 chunk_size 相加,却没有检查溢出。回绕后的和决定了堆分配的大小,随后 readAt() 把完整的 chunk_size 字节复制进去。

攻击执行流程分解

Step 1

投递构造好的视频

攻击者通过彩信或即时通讯应用发送一个 MP4 文件,其 tx3g 原子声明的 chunk_size 接近最大值。

Step 2

解析自动开始

在 Android 5.1 及更早版本中,媒体服务会自动解析收到的媒体以生成预览和缩略图,无需用户点击。

Step 3

大小计算发生回绕

size + chunk_size 溢出,new[] 只分配了几个字节的缓冲区。

Step 4

堆内存被覆盖

readAt() 将 chunk_size 字节复制进这个小缓冲区,破坏 mediaserver 的堆,使攻击者以该进程的权限执行代码。

源代码对比:漏洞与安全实现

存在漏洞的实现
// Simplified from MPEG4Extractor::parseChunk() in libstagefright, before the fix
case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }

    // chunk_size is read from the file. size + chunk_size can wrap around,
    // so a tiny buffer is allocated for a huge copy.
    uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size];
    if (buffer == NULL) {
        return ERROR_MALFORMED;
    }
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    // Copies the full chunk_size bytes into the undersized buffer.
    if ((size_t)mDataSource->readAt(*offset, buffer + size, chunk_size) < chunk_size) {
        delete[] buffer;
        return ERROR_IO;
    }
    mLastTrack->meta->setData(kKeyTextFormatData, 0, buffer, size + chunk_size);
    delete[] buffer;
    *offset += chunk_size;
    break;
}
加固后的安全修复
// Simplified from MPEG4Extractor::parseChunk() in libstagefright, after the fix
case FOURCC('t', 'x', '3', 'g'):
{
    uint32_t type;
    const void *data;
    size_t size = 0;
    if (!mLastTrack->meta->findData(kKeyTextFormatData, &type, &data, &size)) {
        size = 0;
    }

    // Reject any length whose sum would overflow before allocating.
    if (chunk_size < 0 || (uint64_t)chunk_size > SIZE_MAX - size) {
        return ERROR_MALFORMED;
    }

    uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size];
    if (buffer == NULL) {
        return ERROR_MALFORMED;
    }
    if (size > 0) {
        memcpy(buffer, data, size);
    }
    if ((size_t)mDataSource->readAt(*offset, buffer + size, chunk_size) < chunk_size) {
        delete[] buffer;
        return ERROR_IO;
    }
    mLastTrack->meta->setData(kKeyTextFormatData, 0, buffer, size + chunk_size);
    delete[] buffer;
    *offset += chunk_size;
    break;
}

工程与系统安全加固清单

参考来源