
CVE-2026-28609 के लिए प्रूफ-ऑफ-कॉन्सेप्ट और इंस्ट्रुमेंटेड रिप्रोडक्शन हार्नेस, जो Android के MatroskaExtractor में एक आउट-ऑफ-बाउंड्स राइट है, जिसे बिग-एंडियन PCM ऑडियो ट्रैक वाली एक तैयार की गई WebM फ़ाइल के माध्यम से पहुँचा जा सकता है।
CVE-2026-28609 के लिए प्रूफ-ऑफ-कॉन्सेप्ट, इंस्ट्रुमेंटेड रिप्रोडक्शन हार्नेस, और तकनीकी राइट-अप, जो Android के MatroskaExtractor में एक आउट-ऑफ-बाउंड्स राइट है, जिसे बिग-एंडियन PCM ऑडियो ट्रैक वाली एक तैयार की गई WebM फ़ाइल के माध्यम से पहुँचा जा सकता है।
frameworks/av/media/module/extractors/mkv/MatroskaExtractor.cppAndroid के MatroskaExtractor में MatroskaSource::read() में एक
PCM बिग-एंडियन बाइट-स्वैप लूप है जो एक फ़्रेम डेटा पॉइंटर को
uint16_t * में कास्ट करता है इससे पहले कि frame->range_offset() जोड़ा जाए, जो एक बाइट ऑफ़सेट है।
चूँकि uint16_t * पर C पॉइंटर अंकगणित ऑफ़सेट को
sizeof(uint16_t) = 2 से स्केल करता है, परिणामी पॉइंटर बफ़र की शुरुआत से
2 * range_offset बाइट्स आगे होता है, न कि range_offset बाइट्स।
जब range_offset > 0 होता है, तो लूप फ़्रेम के MediaBuffer के अंत से
एक या अधिक बाइट्स आगे पढ़ता और लिखता है। ASan के बिना किसी डिवाइस पर
ओवर-रीड चुपचाप सफल हो जाता है और ओवर-राइट बफ़र के ठीक बाद वाले बाइट को
भ्रष्ट कर देता है।
कमज़ोर पंक्ति:
// MatroskaExtractor.cpp:1105 (pre-fix)
uint16_t *dstData = (uint16_t *)frame->data() + frame->range_offset();
uint16_t *srcData = (uint16_t *)frame->data() + frame->range_offset();
for (size_t i = 0; i < frame->range_length() / 2; i++) {
dstData[i] = ntohs(srcData[i]);
}
The upstream fix casts the pointer to uint8_t * before applying
the offset:
uint16_t *data = (uint16_t *)((uint8_t *)frame->data() + frame->range_offset());
for (size_t i = 0; i < frame->range_length() / 2; i++) {
data[i] = ntohs(data[i]);
}
CVE-2026-28609 के लिए एक कार्यशील, पुनरुत्पादनीय ट्रिगर, जिसे एक वास्तविक Android 14 डिवाइस पर AddressSanitizer के अंतर्गत सत्यापित किया गया है। रिपॉज़िटरी में शामिल हैं:
range_offset के साथ संवेदनशील शाखा में ले जाता है।dlopen के माध्यम से एक्सट्रैक्टर प्लगइन लोड करता है, GETEXTRACTORDEF को आमंत्रित करता है, और फ़ाइल से फ़्रेम पढ़ता है।परिणाम, एक संवेदनशील डिवाइस पर:
==14927==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 2 at 0x003c61ab3960 thread T0
#0 ... MatroskaSource::read(...) MatroskaExtractor.cpp:1113
0x003c61ab3961 is located 0 bytes after 65-byte region
कमज़ोर शाखा के लिए निम्नलिखित सभी शर्तों का सत्य होना आवश्यक है:
| शर्त | स्रोत |
|---|---|
| ट्रैक PCM है | mType == PCM |
| बिग-एंडियन | AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN == 1 |
| 16-बिट सैंपल | AMEDIAFORMAT_KEY_BITS_PER_SAMPLE == 16 |
फ़्रेम में गैर-शून्य range_offset है | set_range(offset, ...) द्वारा सेट किया गया |
पहली तीन शर्तें ट्रैक को कोडेक ID A_PCM/INT/BIG और बिट गहराई 16 के साथ घोषित करके पूरी की जाती हैं। चौथी शर्त दिलचस्प है।
range_offset को केवल MatroskaSource::setWebmBlockCryptoInfo() में गैर-शून्य मान पर सेट किया जाता है, जिसे readBlock() से निम्नलिखित शर्त के अंतर्गत कॉल किया जाता है:
if (err == OK && mExtractor->mIsWebm && trackInfo->mEncrypted) {
err = setWebmBlockCryptoInfo(mbuf);
}
इसलिए ट्रिगर फ़ाइल को यह संतुष्ट करना होगा:
mIsWebm — EBML DocType तत्व "webm" होना चाहिए, "matroska" नहीं।mEncrypted — ट्रैक को ContentEncodings के साथ ContentEncodingType = 1 (Encryption) और एक ContentEncKeyID घोषित करना चाहिए।0x1) 0 हो, जो दर्शाता है कि फ़्रेम एन्क्रिप्टेड नहीं है बल्कि content-encoded है। यह setWebmBlockCryptoInfo में else शाखा लेता है, जो set_range(1, len - 1) को कॉल करता है।स्ट्रिप के बाद, प्रत्येक फ़्रेम के लिए range_offset = 1 होता है। कमज़ोर शाखा फिर (uint16_t *)data + 1 की गणना करती है, जो 2 बाइट्स आगे बढ़ता है, और लूप बाइट्स [2, 2 + range_length) को पढ़ता/लिखता है — जो 65-बाइट आवंटन (64-बाइट फ़्रेम + 1-बाइट सिग्नल) के अंत से एक बाइट आगे है।
+---------+------------------------------------+
| 0x00 | 64 bytes of frame data (0xAA...) |
+---------+------------------------------------+
signal PCM payload
सिग्नल बाइट 0x00 को set_range(1, 64) द्वारा हटा दिया जाता है, जिससे 65-बाइट आवंटन के भीतर एक 64-बाइट फ्रेम बचता है। कमजोर पॉइंटर अंकगणित फिर बाइट ऑफसेट 2 से 65 तक लिखता है।
.
├── README.md
├── LICENSE
├── .gitignore
│
├── exploit/
│ └── generator.py WebM generator + verifier
│
├── harness/
│ └── harness_c_abi.cpp dlopen-based trigger harness
│
└── scripts/
├── build.conf API level, sanitizer, RTTI flags
├── include_dirs.conf.sample Include roots template
├── build_foundation.sh Build the foundation archive
├── build_plugin.sh Build the extractor plugin
├── build_harness.sh Build the harness
└── run.sh End-to-end build + push + run
उपस्ट्रीम निर्भरताएँ, जिन्हें उपयोगकर्ता द्वारा बिल्ड से पहले क्लोन किया जाता है:
av/ frameworks/av (AOSP)
libwebm/ external/libwebm (mkvparser)
flac/ external/flac
aosp-includes/ system/core, system/logging, system/libbase,
frameworks/native — header trees only
aosp-includes/libs/ libstagefright_foundation.so, libmedia.so,
libutils.so, libbinder.so, libcutils.so,
libbase.so, libmediandk.so, libstagefright_flacdec.so
— pulled from the target device
bash, python3, make हों$ANDROID_NDK_HOME के माध्यम से सेट करेंPATH पर adb (Linux, या WSL से adb.exe)mkdir -p deps && cd deps
# AOSP frameworks/av (contains the vulnerable extractor)
git clone --depth 1 -b android-14.0.0_r1 \
https://android.googlesource.com/platform/frameworks/av av
# libwebm (mkvparser)
git clone --depth 1 \
https://android.googlesource.com/platform/external/libwebm libwebm
# libFLAC
git clone --depth 1 \
https://android.googlesource.com/platform/external/flac flac
# AOSP header trees (no full checkout required)
mkdir -p aosp-includes
cd aosp-includes
for m in core libbase logging native; do
git clone --depth 1 \
"https://android.googlesource.com/platform/system/$m" "$m" 2>/dev/null || true
done
cd ../..
यदि आपका AOSP ट्री मॉड्यूलर एक्सट्रैक्टर लेआउट
(av/media/module/extractors/mkv/) का उपयोग करता है, तो किसी और समायोजन की आवश्यकता नहीं है।
यदि यह पुराने लेआउट (av/media/libstagefright/matroska/) का उपयोग करता है,
तो MKV वेरिएबल के लिए बिल्ड स्क्रिप्ट देखें।