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

● CVE-2015-3824 · CVSS v2 10.0 · Alta
Pesquisa · FlawOpen

CVE-2015-3824: estouro de inteiro no tx3g do Stagefright (libstagefright do Android)

Como uma soma de tamanhos sem verificação, ao analisar o átomo de legendas tx3g do MPEG-4, deu a volta, alocou um buffer minúsculo no heap e permitiu que um vídeo malicioso corrompesse a memória do mediaserver sem nenhuma interação do usuário.

💡 Explicação em Linguagem Simples (ELI5)

Imagine uma gráfica cujo formulário de pedido tem só quatro casas para o comprimento de uma faixa. Já há 9.998 metros registrados e você pede mais 5. O atendente escreve o total nas quatro casas, o número vira 0003, e a gráfica corta uma folha de 3 metros. Depois a impressora despeja os 10.003 metros de texto nessa folha, e a tinta escorre sobre os pedidos dos outros clientes na mesa. O Stagefright fez o mesmo com a memória: o total deu a volta, um buffer minúsculo foi reservado e todos os dados do vídeo foram gravados nele.

Conceitos Centrais e Termos

Stagefright (libstagefright)
Biblioteca nativa de mídia do Android que analisa arquivos de áudio e vídeo dentro do processo privilegiado mediaserver.
Átomo MPEG-4
Bloco autodescritivo de um arquivo MP4: um tamanho declarado, um tipo de quatro caracteres e o conteúdo. O analisador confia no tamanho declarado para decidir quanto ler.
tx3g
Átomo de texto temporizado do 3GPP que traz a formatação das legendas de uma faixa de texto.
Estouro de inteiro
Quando o resultado de uma conta excede o que o tipo inteiro comporta e volta para um valor pequeno, como SIZE_MAX + 2 virando 1.

Análise de Causa Raiz

MPEG4Extractor::parseChunk() somava o tamanho dos dados de formatação já guardados ao chunk_size do átomo tx3g, controlado pelo atacante, sem verificar estouro. A soma que deu a volta definia o tamanho da alocação no heap, e readAt() copiava em seguida todos os chunk_size bytes para dentro dela.

Fluxo de Ataque Passo a Paso

Step 1

Um vídeo malicioso é entregue

O atacante envia um arquivo MP4 cujo átomo tx3g declara um chunk_size próximo do valor máximo, por exemplo via MMS ou aplicativo de mensagens.

Step 2

A análise começa sozinha

No Android 5.1 e anteriores, o serviço de mídia analisava a mídia recebida para gerar prévias e miniaturas, sem precisar de nenhum toque.

Step 3

O cálculo de tamanho dá a volta

size + chunk_size estoura e o new[] aloca um buffer de poucos bytes.

Step 4

O heap é sobrescrito

readAt() copia chunk_size bytes para o buffer pequeno, corrompendo o heap do mediaserver e dando ao atacante execução de código com os privilégios desse processo.

Código-Fonte: Vulnerável vs. Seguro

IMPLEMENTAÇÃO VULNERÁVEL
// 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;
}
PATCH SEGURO E ROBUSTO
// 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;
}

Lista de Verificação de Segurança para Engenharia

Fontes