12月17, 2020

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

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

Author: B1aN of 360 Vulcan Team

Abstract

Microsoft has patched a vulnerability I found almost two years ago. The official introduction can be found in MSRC Acknowledgements. It’s a very excellent vulnerability. In this blog post, I will publish the detail of this vulnerability and how I use this vulnerability to get a palette which can read write memory.

Background

DirectComposition component is a Windows kernel-mode graphics component with many syscalls and sub-functions. A main syscall is NtDCompositionProcessChannelBatchBuffer. This syscall can do a lot of operation which include creating resource, releasing resource, setting resource property and so on. To use that syscall, you need a channel which is created from NtDCompositionCreateChannel syscall.

NtDCompositionProcessChannelBatchBuffer support 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-function have different structure. Some structures are showed 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 detail

FAULTING_IP:
win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+1c
fffffa16`2c238900 ff4a14          dec     dword ptr [rdx+14h]

CONTEXT:  ffff9e0af5bc3ea0 -- (.cxr 0xffff9e0af5bc3ea0)
rax=0000000000000001 rbx=2929292929292929 rcx=fffffa40082f0ce0
rdx=2929292929292929 rsi=0000000000000000 rdi=fffffa40082f0ce0
rip=fffffa162c238900 rsp=ffff9e0af5bc48a0 rbp=fffffa40082f0ce0
 r8=00000000000000cd  r9=ffff800000000000 r10=ffff9e0af5bc4b00
r11=ffff9e0af5bc4780 r12=0000000000000002 r13=ffff9e0af5bc49b1
r14=fffffa400c3f0010 r15=fffffa40082f0ce0
iopl=0         nv up ei pl nz na pe nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050202
win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+0x1c:
fffffa16`2c238900 ff4a14          dec     dword ptr [rdx+14h] ds:002b:29292929`2929293d=????????

 # Child-SP          RetAddr           Call Site
00 ffff9e0a`f5bc2de8 fffff802`5812c9a2 nt!DbgBreakPointWithStatus
01 ffff9e0a`f5bc2df0 fffff802`5812bf86 nt!KiBugCheckDebugBreak+0x12
02 ffff9e0a`f5bc2e50 fffff802`5800f6a7 nt!KeBugCheck2+0x946
03 ffff9e0a`f5bc3560 fffff802`58021569 nt!KeBugCheckEx+0x107
04 ffff9e0a`f5bc35a0 fffff802`580209bc nt!KiBugCheckDispatch+0x69
05 ffff9e0a`f5bc36e0 fffff802`5801845f nt!KiSystemServiceHandler+0x7c
06 ffff9e0a`f5bc3720 fffff802`57e6dd97 nt!RtlpExecuteHandlerForException+0xf
07 ffff9e0a`f5bc3750 fffff802`57e6c9a6 nt!RtlDispatchException+0x297
08 ffff9e0a`f5bc3e70 fffff802`580216ac nt!KiDispatchException+0x186
09 ffff9e0a`f5bc4530 fffff802`5801d3e0 nt!KiExceptionDispatch+0x12c
0a ffff9e0a`f5bc4710 fffffa16`2c238900 nt!KiGeneralProtectionFault+0x320
0b ffff9e0a`f5bc48a0 fffffa16`2c3f77e0 win32kbase!DirectComposition::CApplicationChannel::ReleaseResource+0x1c
0c ffff9e0a`f5bc48d0 fffffa16`2c3f7bfc win32kbase!DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences+0x5c
0d ffff9e0a`f5bc4900 fffffa16`2c237148 win32kbase!DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty+0x36c
0e ffff9e0a`f5bc4960 fffffa16`2c236be1 win32kbase!DirectComposition::CApplicationChannel::ProcessCommandBufferIterator+0x4a8
0f ffff9e0a`f5bc4a20 fffffa16`2d17edfe win32kbase!NtDCompositionProcessChannelBatchBuffer+0x1a1
10 ffff9e0a`f5bc4ac0 fffff802`58020fb5 win32k!NtDCompositionProcessChannelBatchBuffer+0x16
11 ffff9e0a`f5bc4b00 00007fff`d7753724 nt!KiSystemServiceCopyEnd+0x25
12 00000026`ad8ffb98 00007ff6`e53c1365 win32u!NtDCompositionProcessChannelBatchBuffer+0x14

Vulnerability detail

The type of this vulnerability is uninitialized pool memory reference.

The allocation of the uninitialized memory is in DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty function with the third argument is 0x15(This value is different in different OS version). The code below shows the allocation which is a resource object pointers 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 process a pair of resource object in one loop. The memory of the second resource object in the pair will not be writen if it breaks out of the loop with the second resource object is not type of 0x57.

      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 go in DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences function which will release a pair of resource object in one loop without any checks. The uninitialized object pointers memory will be referenced in DirectComposition::CApplicationChannel::ReleaseResource function. Then the system will 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;
  }
}

Exploit

How I convert arbitrary address minus one to arbitrary resource object release

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

Let's dive into the structure of the CApplicationChannel object. CApplicationChannel object management the lifetime of its resource. Two CLinearObjectTableBase objects stored in CApplicationChannel. One is at this+0x38 and another is at this+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 hold all the resources which are created by the channel in a resource object pointer table. The InsertObject function will write resource pointer at certain index in the table. This is exactly what we want.

  1. We spray 0x400 resources which will lead two 0x2000 bytes object pointer tables in CLinearObjectTableBase object.
  2. If we create one more resource, two 0x2000 bytes object pointer tables will be freed and two new 0x4000 bytes object pointer tables will be allocated. Now we have two 0x2000 bytes memory which is full of resource object pointer.
  3. We trigger the vulnerability with buffer size is 0x1000. The 0x2000 bytes resource object pointer table just freed will be allocated. And a resource object at certain postion in resource object pointer table will be freed.

For now we achieve arbitrary resource object free.

How I get a palette with dangling data pointer

Look into CInteractionTrackerMarshaler::SetBufferProperty function with flag 0x41, there is a CDCompDynmicArrayBase object in CInteractionTrackerMarshaler also. We can use it to spray a large size memory and then free 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 palette to do pool fengshui to make 0xc000 bytes hole in memory.
  2. We call CInteractionTrackerMarshaler::SetBufferProperty function with flag 0x41 to create a 0xc000 memory allocation which will locate in pool fengshui hole.
  3. We trigger bug to free this CInteractionTrackerMarshaler resource object.
  4. Spray some palette to allocate the memory we just freed.
  5. We call CInteractionTrackerMarshaler::SetBufferProperty function with flag 0x41 one more time to free the palette data. Then we have a palette with a dangling data pointer. We can manipulate that freed memory by calling SetPaletteEntries and GetPaletteEntries.

Conclusion

To exploit this vulnerability takes a lot of days and nights. Hope this post is useful to you.

本文链接:https://blogs.360.net/post/cve-2020-17057 detail and exploit.html

-- EOF --

Comments