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

● CVE-2015-3824 · CVSS v2 10.0 · Alta
Investigación · FlawOpen

CVE-2015-3824: desbordamiento de enteros en tx3g de Stagefright (libstagefright de Android)

Cómo una suma de tamaños sin comprobar, al analizar el átomo de subtítulos tx3g de MPEG-4, dio la vuelta, reservó un búfer diminuto en el heap y permitió que un vídeo manipulado corrompiera la memoria de mediaserver sin ninguna interacción del usuario.

💡 Explicación en Lenguaje Sencillo (ELI5)

Imagina una imprenta cuyo formulario de pedido solo tiene cuatro casillas para la longitud de una pancarta. Ya hay 9.998 metros registrados y pides 5 más. El empleado escribe el total en las cuatro casillas, el número da la vuelta a 0003 y la imprenta corta una hoja de 3 metros. Después la impresora vuelca los 10.003 metros de texto sobre esa hoja y la tinta se derrama sobre los pedidos de los demás clientes. Stagefright hizo lo mismo con la memoria: el total dio la vuelta, se reservó un búfer diminuto y se escribieron en él todos los datos del vídeo.

Conceptos Clave y Términos

Stagefright (libstagefright)
Biblioteca multimedia nativa de Android que analiza archivos de audio y vídeo dentro del proceso privilegiado mediaserver.
Átomo MPEG-4
Bloque autodescriptivo de un archivo MP4: un tamaño declarado, un tipo de cuatro caracteres y el contenido. El analizador confía en el tamaño declarado para decidir cuánto leer.
tx3g
Átomo de texto temporizado de 3GPP que contiene el formato de los subtítulos de una pista de texto.
Desbordamiento de enteros
Cuando el resultado de una operación supera lo que cabe en el tipo entero y vuelve a un valor pequeño, como SIZE_MAX + 2 convertido en 1.

Análisis de Causa Raíz

MPEG4Extractor::parseChunk() sumaba el tamaño de los datos de formato ya guardados al chunk_size del átomo tx3g, controlado por el atacante, sin comprobar el desbordamiento. La suma que dio la vuelta fijaba el tamaño de la reserva en el heap, y readAt() copiaba después los chunk_size bytes completos en ella.

Flujo de Ataque Paso a Paso

Step 1

Se entrega un vídeo manipulado

El atacante envía un archivo MP4 cuyo átomo tx3g declara un chunk_size cercano al valor máximo, por ejemplo por MMS o una aplicación de mensajería.

Step 2

El análisis empieza solo

En Android 5.1 y anteriores, el servicio multimedia analizaba los archivos recibidos para crear vistas previas y miniaturas, sin necesidad de tocar nada.

Step 3

El cálculo del tamaño da la vuelta

size + chunk_size se desborda y new[] reserva un búfer de solo unos pocos bytes.

Step 4

Se sobrescribe el heap

readAt() copia chunk_size bytes en el búfer pequeño, corrompe el heap de mediaserver y da al atacante ejecución de código con los privilegios de ese proceso.

Código Fuente: Vulnerable vs. Seguro

IMPLEMENTACIÓN VULNERABLE
// 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;
}
PARCHE SEGURO Y 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 Verificación de Seguridad para Ingeniería

Fuentes