CVE-2020-17057 Unitialized pointer privilege upgrade vulnerability

CVE-2020-17057 Microsoft Windows DirectComposition Uninitialized Pointer Privilege Escalation


Microsoft patched a I discovered two years ago. The official introduction can be found in MSRC Acknowledgements. This is a very good loophole. In this blog post, I will post details of this vulnerability and how to use this vulnerability to obtain a palette that can read and write memory.


The DirectComposition component is a Windows kernel mode graphics component with many syscalls and sub-functions. A major system call is NtDCompositionProcessChannelBatchBuffer. This system call can perform many operations, including creating resources, releasing resources, and setting resource properties. To use this system call, you need a channel created from the NtDCompositionCreateChannel system call.

NtDCompositionProcessChannelBatchBuffer supports many sub-functions.

enum class DComProcessCommandId : unsigned int{    nCmdProcessCommandBufferIterator,    nCmdCreateResource,    nCmdOpenSharedResource,    nCmdReleaseResource,    nCmdGetAnimationTime,    nCmdCapturePointer,    nCmdOpenSharedResourceHandle,    nCmdSetResourceCallbackId,    nCmdSetResourceIntegerProperty,    nCmdSetResourceFloatProperty,    nCmdSetResourceHandleProperty,    nCmdSetResourceHandleArrayProperty,    nCmdSetResourceBufferProperty,    nCmdSetResourceReferenceProperty,    nCmdSetResourceReferenceArrayProperty,    nCmdSetResourceAnimationProperty,    nCmdSetResourceDeletedNotificationTag,    nCmdAddVisualChild,    nCmdRedirectMouseToHwnd,    nCmdSetVisualInputSink,    nCmdRemoveVisualChild};

Different sub-functions have different structures. Some structures are shown here.

struct CREATE_RESOURCE{    DComProcessCommandId Command;    ULONG hResource;    ULONG ResourceType;    ULONG bShare;};struct SET_BUFFER_PROPERTY{    DComProcessCommandId Command;    ULONG hResource;    ULONG flag;    ULONG BufferSize;};

CVE-2020-17057 analysis

Crash details

FAULTING_IP:win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+1cfffffa16`2c238900 ff4a14          dec     dword ptr [rdx+14h]CONTEXT:  ffff9e0af5bc3ea0 -- (.cxr 0xffff9e0af5bc3ea0)rax=0000000000000001 rbx=2929292929292929 rcx=fffffa40082f0ce0rdx=2929292929292929 rsi=0000000000000000 rdi=fffffa40082f0ce0rip=fffffa162c238900 rsp=ffff9e0af5bc48a0 rbp=fffffa40082f0ce0 r8=00000000000000cd  r9=ffff800000000000 r10=ffff9e0af5bc4b00r11=ffff9e0af5bc4780 r12=0000000000000002 r13=ffff9e0af5bc49b1r14=fffffa400c3f0010 r15=fffffa40082f0ce0iopl=0         nv up ei pl nz na pe nccs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050202win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+0x1c:fffffa16`2c238900 ff4a14          dec     dword ptr [rdx+14h] ds:002b:29292929`2929293d=???????? # Child-SP          RetAddr           Call Site00 ffff9e0a`f5bc2de8 fffff802`5812c9a2 nt!DbgBreakPointWithStatus01 ffff9e0a`f5bc2df0 fffff802`5812bf86 nt!KiBugCheckDebugBreak+0x1202 ffff9e0a`f5bc2e50 fffff802`5800f6a7 nt!KeBugCheck2+0x94603 ffff9e0a`f5bc3560 fffff802`58021569 nt!KeBugCheckEx+0x10704 ffff9e0a`f5bc35a0 fffff802`580209bc nt!KiBugCheckDispatch+0x6905 ffff9e0a`f5bc36e0 fffff802`5801845f nt!KiSystemServiceHandler+0x7c06 ffff9e0a`f5bc3720 fffff802`57e6dd97 nt!RtlpExecuteHandlerForException+0xf07 ffff9e0a`f5bc3750 fffff802`57e6c9a6 nt!RtlDispatchException+0x29708 ffff9e0a`f5bc3e70 fffff802`580216ac nt!KiDispatchException+0x18609 ffff9e0a`f5bc4530 fffff802`5801d3e0 nt!KiExceptionDispatch+0x12c0a ffff9e0a`f5bc4710 fffffa16`2c238900 nt!KiGeneralProtectionFault+0x3200b ffff9e0a`f5bc48a0 fffffa16`2c3f77e0 win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+0x1c0c ffff9e0a`f5bc48d0 fffffa16`2c3f7bfc win32kbase!DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences+0x5c0d ffff9e0a`f5bc4900 fffffa16`2c237148 win32kbase!DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty+0x36c0e ffff9e0a`f5bc4960 fffffa16`2c236be1 win32kbase!DirectComposition::CApplicationChannel::ProcessCommandBufferIterator+0x4a80f ffff9e0a`f5bc4a20 fffffa16`2d17edfe win32kbase!NtDCompositionProcessChannelBatchBuffer+0x1a110 ffff9e0a`f5bc4ac0 fffff802`58020fb5 win32k!NtDCompositionProcessChannelBatchBuffer+0x1611 ffff9e0a`f5bc4b00 00007fff`d7753724 nt!KiSystemServiceCopyEnd+0x2512 00000026`ad8ffb98 00007ff6`e53c1365 win32u!NtDCompositionProcessChannelBatchBuffer+0x14


The type of this is an uninitialized pool memory reference.

The allocation of uninitialized memory is in the DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty function, and the third parameter is 0x15 (this value is different in different OS versions). The following code shows the allocation, which is a resource object pointer table.

v10 = this;  Size = a5; // buffer size  v11 = a3 - 0x15; // the third argument  if ( !v11 )  {    if ( !a4 && *((_DWORD *)this + 0x5A) > 0u ) // a4 is buffer property    {      DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences(this, a2);      *a6 = 1;      *((_DWORD *)v10 + 4) &= 0xFFFFF7FF;      return (unsigned int)v7;    }    DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences(this, a2);    v24 = Size >> 3;    if ( (unsigned int)(Size >> 3) )    {      v25 = Win32AllocPoolWithQuota(16i64 * (unsigned int)v24, 0x72694344i64); // memory allocation, not zeroed      v26 = 0;      *((_QWORD *)v10 + 0x2C) = v25;      if ( !v25 )        v26 = 0xC0000017;

Then, it processes a pair of resource objects in a loop. If the type of the second resource object is not 0x57, the loop will be interrupted and the memory of the second resource object in the pair will not be written.

v8 = a4;      do // process two resources in one loop      {        if ( v27 >= (unsigned int)v24 )          break;        v28 = v8[2 * v27]; // get first resource handle        v29 = (unsigned int)(v28 - 1);        if ( v28 && v29 < *((_QWORD *)v9 + 0xA) )        {          _mm_lfence();          v30 = *(_QWORD *)(v29 * *((_QWORD *)v9 + 11) + *((_QWORD *)v9 + 7)); // get resource from resource table according to resource handle        }        else        {          v30 = 0i64;        }        if ( v30          && (*(unsigned __int8 (__fastcall **)(__int64, signed __int64))(*(_QWORD *)v30 + 0x60i64))(v30, 0x67i64) ) // check the resource is of type 0x67 or not        {          *(_QWORD *)(*((_QWORD *)v10 + 0x2C) + 16i64 * v27) = v30; // if true, write resource object pointer to the memory previously allocated.          DirectComposition::CResourceMarshaler::AddRef(*(DirectComposition::CResourceMarshaler **)(*((_QWORD *)v10 + 44)                                                                                                  + 16i64 * v27));          ++*((_DWORD *)v10 + 0x5A); // resource pair number        }        else        {          v7 = 0xC000000D;        }        if ( v7 >= 0 )        {          v31 = v8[2 * v27 + 1]; // get second resource handle          if ( v31 )          {            v32 = (unsigned int)(v31 - 1);            if ( v32 >= *((_QWORD *)v9 + 0xA) )            {              v33 = 0i64;            }            else            {              _mm_lfence();              v33 = *(_QWORD *)(v32 * *((_QWORD *)v9 + 0xB) + *((_QWORD *)v9 + 7)); // get resource from resource table according to resource handle            }            if ( v33              && (*(unsigned __int8 (__fastcall **)(__int64, signed __int64))(*(_QWORD *)v33 + 0x60i64))(v33, 0x57i64) ) // check the resource is of type 0x57 or not            {              *(_QWORD *)(*((_QWORD *)v10 + 0x2C) + 16i64 * v27 + 8) = v33;              DirectComposition::CResourceMarshaler::AddRef(*(DirectComposition::CResourceMarshaler **)(*((_QWORD *)v10 + 44) + 16i64 * v27 + 8));            }            else            {              v7 = 0xC000000D; // if false, it will not set the memory to zero            }          }          else          {            *(_QWORD *)(*((_QWORD *)v10 + 44) + 16i64 * v27 + 8) = 0i64;          }        }        ++v27;      }      while ( v7 >= 0 );      v6 = a6;      if ( v7 < 0 )        goto LABEL_55; // goto release function      ....................LABEL_55:  if ( *((_QWORD *)v10 + 44) )    DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences(v10, v9); // go in here  return (unsigned int)v7;

Finally, it will enter the DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences function, which will release a pair of resource objects in a loop without any checks. The uninitialized object pointer memory will be referenced in the DirectComposition::CApplicationChannel::ResourceResource function. Then the system will perform BSoD.

void __fastcall DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences(DirectComposition::CInteractionTrackerMarshaler *this, struct DirectComposition::CApplicationChannel *a2){  DirectComposition::CInteractionTrackerMarshaler *v2; // rdi  DirectComposition::CApplicationChannel *v3; // rbp  __int64 v4; // rcx  unsigned int v5; // esi  v2 = this;  v3 = a2;  v4 = *((_QWORD *)this + 44);  if ( v4 )  {    v5 = 0;    if ( *((_DWORD *)v2 + 90) )    {      do      {        DirectComposition::CApplicationChannel::ReleaseResource(          v3,          *(struct DirectComposition::CResourceMarshaler **)(*((_QWORD *)v2 + 44) + 16i64 * v5));        DirectComposition::CApplicationChannel::ReleaseResource(          v3,          *(struct DirectComposition::CResourceMarshaler **)(*((_QWORD *)v2 + 44) + 16i64 * v5++ + 8)); // this memory is uninitialized, and crash in this function      }      while ( v5 < *((_DWORD *)v2 + 90) ); // *((_DWORD *)v2+90) is resource pair number      v4 = *((_QWORD *)v2 + 44);    }    Win32FreePool(v4);    *((_QWORD *)v2 + 44) = 0i64;    *((_DWORD *)v2 + 90) = 0;    *((_DWORD *)v2 + 91) = 0;  }}

How do I subtract one from any address into any resource object version

The crash dump shows me that the is an arbitrary address minus a vulnerability. But the functional logic is to release the resource object in the resource object pointer table. What if we can make a resource object pointer table?

Let us dive into the structure of the CApplicationChannel object. The CApplicationChannel object manages the lifetime of its resources. Two CLinearObjectTableBase objects are stored in CApplicationChannel. One is here + 0x38, and the other is here + 0x70.

if ( a4 )    v8 = DirectComposition::CApplicationChannel::CreateInternalSharedResource(this, a3, &v12);  else    v8 = DirectComposition::CApplicationChannel::CreateInternalResource(this, a3, &v12);  v9 = v8;  if ( v8 >= 0 )  {    v9 = DirectComposition::CLinearObjectTableBase::InsertObject(           (DirectComposition::CApplicationChannel *)((char *)v6 + 56),           (void *)v12,           v5);
v27 = DirectComposition::CLinearObjectTableBase::InsertObject(          (DirectComposition::CApplicationChannel *)((char *)v4 + 112),          (void *)v11,          (unsigned int *)(v11 + 24));

CLinearObjectTableBase saves all resources created by the channel in the resource object pointer table. The InsertObject function will write resource pointers at certain indexes in the table. This is exactly what we want.

  1. We spray 0x400 resources, which will result in two 0x2000 byte object pointer tables in the CLinearObjectTableBase object.
  2. If we create another resource, two 0x2000-byte object pointer tables will be released, and two new 0x4000-byte object pointer tables will be allocated. Now, we have two 0x2000 bytes of memory, which are full of resource object pointers.
  3. We trigger a vulnerability with a buffer size of 0x1000. The newly released 0x2000 byte resource object pointer table will be allocated. The resource object at a specific position in the resource object pointer table will be released.

Currently, we have achieved the freedom of arbitrary resource objects.

How to get a palette with dangling data pointers

Look at the CInteractionTrackerMarshaler::SetBufferProperty function with the flag 0x41. There is also a CDCompDynmicArrayBase object in CInteractionTrackerMarshaler. We can use it to spray out a large amount of memory and then release it.

if ( Size == 0xC )  {    v17 = (_QWORD *)((char *)this + 368);    v18 = *((_DWORD *)this + 98);    v19 = *((_DWORD *)a4 + 2);    Src = *(_QWORD *)a4;    v36 = v19;    v7 = DirectComposition::CDCompDynamicArrayBase::Grow(           (DirectComposition::CInteractionTrackerMarshaler *)((char *)this + 368),           1i64,           0x72694344i64);    if ( v7 >= 0 )    {      memmove((void *)(*v17 + v17[4] * v18), &Src, v17[4]);      *a6 = 1;    }
  1. We use the palette to do pool feng shui to create a 0xc000 byte vulnerability in memory.
  2. We call the CInteractionTrackerMarshaler::SetBufferProperty function with the flag 0x41 to create a 0xc000 memory allocation, which is located in the wind pool.
  3. We trigger an error to release this CInteractionTrackerMarshaler resource object.
  4. Spray some palettes to allocate the memory we just released.
  5. We call the CInteractionTrackerMarshaler::SetBufferProperty function with the flag 0x41 again to release the palette data. Then we have a palette with hanging data pointers. We can manipulate the released memory by calling SetPaletteEntries and GetPaletteEntries.