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)

एक प्रिंटिंग प्रेस की कल्पना कीजिए जिसके ऑर्डर फ़ॉर्म में बैनर की लंबाई लिखने के लिए सिर्फ़ चार खाने हैं। रिकॉर्ड में पहले से 9,998 मीटर दर्ज हैं और आप 5 मीटर और माँगते हैं। क्लर्क कुल जोड़ चार खानों में लिखता है, संख्या पलटकर 0003 बन जाती है, और प्रेस सिर्फ़ 3 मीटर की शीट काटती है। फिर मशीन पूरे 10,003 मीटर का पाठ उसी शीट पर छापती है और स्याही मेज़ पर रखे दूसरे ग्राहकों के ऑर्डर पर फैल जाती है। Stagefright ने मेमोरी के साथ यही किया: आकार का जोड़ पलट गया, बहुत छोटा बफ़र बना और वीडियो का सारा डेटा उसमें लिख दिया गया।

इस पेज के मुख्य शब्द

Stagefright (libstagefright)
Android की नेटिव मीडिया लाइब्रेरी, जो विशेषाधिकार वाली mediaserver प्रक्रिया के भीतर ऑडियो और वीडियो फ़ाइलें पार्स करती है।
MPEG-4 एटम
MP4 फ़ाइल का स्वयं-वर्णित खंड: घोषित आकार, चार अक्षरों का प्रकार और डेटा। पार्सर घोषित आकार पर भरोसा करके तय करता है कि कितना पढ़ना है।
tx3g
3GPP का टाइम्ड-टेक्स्ट एटम, जिसमें टेक्स्ट ट्रैक के सबटाइटल का फ़ॉर्मैट रहता है।
इंटीजर ओवरफ़्लो
जब गणना का परिणाम इंटीजर प्रकार की सीमा से बड़ा होकर पलटकर छोटा मान बन जाता है, जैसे SIZE_MAX + 2 का 1 बन जाना।

मूल कारण विश्लेषण (Root Cause)

MPEG4Extractor::parseChunk() पहले से सहेजे गए फ़ॉर्मैट डेटा का आकार हमलावर-नियंत्रित tx3g एटम के chunk_size में जोड़ता था, पर ओवरफ़्लो की जाँच नहीं करता था। पलटा हुआ जोड़ हीप आवंटन का आकार तय करता था, और फिर readAt() पूरे chunk_size बाइट उसमें कॉपी कर देता था।

हमले का चरण-दर-चरण प्रवाह

Step 1

गढ़ा हुआ वीडियो भेजा जाता है

हमलावर MMS या मैसेजिंग ऐप से ऐसी 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;
}

इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट

स्रोत