
إثبات مفهوم وأداة إعادة إنتاج مزوّدة بأدوات قياس لـ CVE-2026-28609، وهو كتابة خارج الحدود في MatroskaExtractor الخاص بنظام Android يمكن الوصول إليها عبر ملف WebM مصمم خصيصًا يحتوي على مسار صوتي PCM بترتيب البايتات الكبير.
إثبات مفهوم، وأداة إعادة إنتاج مُجهّزة، ودراسة تقنية لـ CVE-2026-28609، وهي كتابة خارج الحدود في MatroskaExtractor الخاص بنظام Android يمكن الوصول إليها عبر ملف WebM مُصمَّم بعناية يحتوي على مسار صوت PCM بترتيب البايتات الكبير (big-endian).
frameworks/av/media/module/extractors/mkv/MatroskaExtractor.cppتحتوي الدالة MatroskaSource::read() في MatroskaExtractor الخاص بنظام
Android على حلقة لتبديل بايتات PCM بترتيب البايتات الكبير تقوم بتحويل
مؤشر بيانات الإطار إلى uint16_t * قبل إضافة frame->range_offset()،
وهو إزاحة بالبايتات. ولأن حساب المؤشرات في لغة C على uint16_t *
يضاعف الإزاحة بمقدار 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]);
}
يُجري الإصلاح في المصدر الأعلى تحويل المؤشر إلى uint8_t * قبل تطبيق
الإزاحة:
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، تم التحقق منه تحت AddressSanitizer على جهاز Android 14 حقيقي. يتضمن المستودع:
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 |
| ترتيب البايتات الكبير (Big-endian) | AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN == 1 |
| عينات 16 بت | AMEDIAFORMAT_KEY_BITS_PER_SAMPLE == 16 |
الإطار يمتلك range_offset غير صفري | يُضبط بواسطة set_range(offset, ...) |
تتحقق الشروط الثلاثة الأولى عبر تعريف المسار بمعرّف الترميز
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 (التشفير) و ContentEncKeyID.0x1) مساوية لـ 0، مما يشير إلى أن الإطار
غير مشفر لكنه مشفر المحتوى. يأخذ هذا الفرع else في
setWebmBlockCryptoInfo، الذي يستدعي set_range(1, len - 1).بعد التجريد، يصبح range_offset = 1 لكل إطار. ثم يحسب الفرع
المعرض للخطر (uint16_t *)data + 1، مما يقدم بمقدار بايتين،
وتقرأ/تكتب الحلقة البايتات [2, 2 + range_length) — بايت واحد
بعد نهاية التخصيص البالغ 65 بايتًا (إطار 64 بايتًا + إشارة
بايت واحد).
+---------+------------------------------------+
| 0x00 | 64 bytes of frame data (0xAA...) |
+---------+------------------------------------+
signal PCM payload
تُزال بايت الإشارة 0x00 بواسطة set_range(1, 64)، مما يترك إطارًا
بحجم 64 بايت داخل تخصيص بحجم 65 بايت. ثم تكتب حسابات المؤشر
المعرّضة للخطر عند إزاحة البايت من 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_HOMEadb على PATH (Linux، أو adb.exe من WSL)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.
mkdir -p deps/aosp-includes/libs
for lib in libstagefright_foundation.so libstagefright_flacdec.so \
libmedia.so libutils.so libbinder.so libcutils.so \
libbase.so libmediandk.so; do
adb pull "/system/lib64/$lib" deps/aosp-includes/libs/
done
cp scripts/include_dirs.conf.sample scripts/include_dirs.conf
cp scripts/build.conf.sample scripts/build.conf # if provided separately
$EDITOR scripts/build.conf
عيّن ANDROID_API ليطابق جهازك الهدف (مثل 34 لنظام
Android 14).