Out-of-bounds read information disclosure vulnerability in Microsoft Windows GDI+ EMR_SETPIXELV record
This article, the sixth in a seven-part series on vulnerabilities found via fuzzing the Graphics Device Interface (GDI) of Microsoft Windows, is about an information disclosure vulnerability in the EMR_SETPIXELV
enhanced metafile record. For other articles in the series click here.
TL;DR
An information disclosure vulnerability (CVE-2022-34728) exists when the Windows GDI+
component improperly discloses the contents of its memory.
This vulnerability allows remote attackers to disclose sensitive information on affected installations of Microsoft Windows. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.
The specific flaw exists within the processing of EMF
metafiles in gdiplus.dll
. A specially crafted EMR_SETPIXELV
record can result in a read past the end of an allocated buffer and disclose initialized or uninitialized heap memory. An attacker can leverage this in conjunction with other vulnerabilities to execute code in the context of the current process.
Description
The following analysis is based on Microsoft Windows 10 Professional (x86) using version 10.0.19041.1706 of gdiplus.dll
.
It seems processing a specially crafted EMF
metafile may lead to memory corruption in the ModifyRecordColor()
function and cause a subsequent call to memcpy()
to read memory out-of-bounds.
The below is the relevant excerpt of the crash analysis from WinDbg
when processing an EMF
metafile.
10:000> g
2(16d4.d34): Access violation - code c0000005 (first chance)
3First chance exceptions are reported before any exception handling.
4This exception may be expected and handled.
5eax=00000008 ebx=00000002 ecx=07ff2720 edx=00000008 esi=0809eff8 edi=07ff2720
6eip=711045e0 esp=009df3a4 ebp=009df3b8 iopl=0 nv up ei pl zr na pe nc
7cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
8gdiplus!EmfEnumState::ModifyRecordColor+0x22:
9711045e0 8b3430 mov esi,dword ptr [eax+esi] ds:002b:0809f000=????????
100:000> kb
11 # ChildEBP RetAddr Args to Child
1200 009df3b8 71165417 00000002 00000003 07ff2720 gdiplus!EmfEnumState::ModifyRecordColor+0x22
1301 009df3dc 71127c5d 0000000f 00000008 0809eff8 gdiplus!EmfEnumState::ProcessRecord+0x61157
1402 009df400 7114812c 0000000f 00000000 00000008 gdiplus!GdipPlayMetafileRecordCallback+0xdd
1503 009df42c 76373945 8c010a6b 080a0fe8 0809eff0 gdiplus!EnumEmfDownLevel+0x6c
1604 009df500 7636cb4c 711480c0 07fefcb0 009df574 gdi32full!bInternalPlayEMF+0x855
1705 009df514 76f7450b 8c010a6b eb460a5d 711480c0 gdi32full!EnumEnhMetaFile+0x2c
1806 009df534 711085a2 8c010a6b eb460a5d 711480c0 GDI32!EnumEnhMetaFileStub+0x2b
1907 009df588 71105aac 8c010a6b eb460a5d 009df62c gdiplus!MetafilePlayer::EnumerateEmfRecords+0xc8
2008 009df640 7110a07c 07fefcb0 eb460a5d 009df788 gdiplus!GpGraphics::EnumEmf+0x464
2109 009df7b0 71117461 009df7f4 009df7f4 00000002 gdiplus!GpMetafile::EnumerateForPlayback+0x651
220a 009df908 7112867f 07fb9f28 009df938 009df948 gdiplus!GpGraphics::DrawImage+0x541
230b 009df970 711976a8 07fb9f28 009df998 009df9a8 gdiplus!GpGraphics::DrawImage+0x61
240c 009df9d0 711988bf 00000064 00000064 00000064 gdiplus!GpMetafile::GetBitmap+0x1c0
250d 009df9e4 7117c8a5 00000064 00000064 07fbbff0 gdiplus!GpMetafile::GetThumbnail+0x2f
260e 009dfa0c 005d128b 07fb9f28 00000064 00000064 gdiplus!GdipGetImageThumbnail+0x65
27...
Crash analysis
The following analysis is based on Microsoft Windows 10 Professional (x86) using version 10.0.19041.1706 of gdiplus.dll
.
When processing EMF
records, the execution flow reaches the EmfEnumState::ProcessRecord()
function within gdiplus.dll
to handle each record. For EMR_SETPIXELV
records execution continues with the EmfEnumState::ModifyRecordColor()
function.
1int EmfEnumState::ProcessRecord(int Type, uint Size, ...)
2{
3 ...
4 if ( *(this + 9) ) {
5 ...
6 } else {
7 nSize = Size; // 0x10 = 0x8
8 }
9 switch ( Type ) { // 0xF
10 ...
11 case 0xF:
12 EmfEnumState::ModifyRecordColor(this, ColorAdjustTypeBrush, ColorAdjustTypePen);
13 ...
14 }
15}
It seems ModifyRecordColor()
has the same purpose as the SetPixelV()
function which sets the pixel at the specified coordinates to the closest approximation of the specified color.
1void EmfEnumState::ModifyRecordColor(int this, int ColorAdjustTypeBrush, int ColorAdjustTypePen)
2{
3 pixel = *(this + 0x5C);
4 if ( pixel ) {
5 if ( *(this + 0x60) >= (4 * ColorAdjustTypeBrush) ) { // 0x10 >= 0x8
6 oldPalEntry = *(4 * ColorAdjustTypeBrush + pixel); // Crash here!
7 newPalEntry = MfEnumState::ModifyColor(this, oldPalEntry, ColorAdjustTypePen);
8 if ( newPalEntry != oldPalEntry ) {
9 if ( EmfEnumState::CreateCopyOfCurrentRecord(this, Size) )
10 *(*(v3 + 0x98) + 4 * ColorAdjustTypeBrush + 8) = newPalEntry;
11 }
12 }
13 }
14}
The following is the human readable form of the affected EMRSETPIXELV
1 structure which defines the color of the pixel at the specified logical coordinates and triggers the read access violation:
1typedef struct tagEMRSETPIXELV {
2 DWORD iType = 0x0000000f; // 0x04 bytes
3 DWORD nSize; = 0x00000010; // 0x04 bytes
4 POINTL ptlPixel {
5 DWORD x = 0x00000001; // 0x04 bytes
6 DWORD y = 0x0000000e; // 0x04 bytes
7 };
8 COLORREF crColor = 0x00000010; // 0x04 bytes
9} EMRSETPIXELV; // 0x14 bytes
Root cause analysis
It seems ModifyRecordColor()
assumes that the minimum size of the data within the EMR_SETPIXELV
2 record is only 0x8
bytes, while the ptlPixel
member is an 0x8
bytes POINTL
structure and the crColor
member is another 0x4
bytes COLORREF
3 structure, which all together require 0xC
bytes.
Due to the fact that the nBytes
field of the EMR_HEADER
record is set to 0x100
, the GetEmf()
function will only allocate 256
bytes for the metafile and cut off the crColor
field of the EMR_SETPIXELV
record, which still seems to be valid as the value of the nSize
field is 0x4
bytes smaller anyway.
As the below pseudocode shows, the EnumEmfDownLevel()
function checks only that the value of the nSize
member is larger than 0x8
bytes, which would indicate that there are additional fields in the record.
1int EnumEmfDownLevel(...) {
2 if ( !emr || mr->nSize < 8 || ... || IsEmfPlusRecord(emr) )
3 goto LABEL_13;
4 Size = nSize - 8;
5 Type = emr->iType;
6 Header = emr->dParm;
7 if ( nSize - 8 <= 0 ) {
8 Size = 0;
9 Header = 0;
10 }
11 ...
12 if ( !GdipPlayMetafileRecordCallback(Type, 0, Size, Header, ...) ) {
13 ...
14 result = 0;
15 } else {
16LABEL_13:
17 result = 1;
18 }
19 return result;
20}
Proof of concept
An attacker who successfully exploited the vulnerability could leak small parts of initialized or uninitialized heap memory of the process, due to the fact that the ModifyColor()
function will pick up the next 0x4
bytes followed by the ptlPixel
field of the record. The attacker can only leak 0x3
bytes, because the high-order byte will be set to 0x0
during the operation.
As the below excerpt of the memory shows, the attacker could leak the bytes 0x77
, 0x66
and 0x3A
as F2 will be set to 0x0
.
10113AF88 0F 00 00 00 10 00 00 00 01 00 00 00 0E 00 00 00 ................
20113AF98 77 66 3A F2 AF 7C 00 08 14 0C 46 29 00 00 00 00 wf:=ยป|....F)....
The ModifyRecordColor()
function will call EmfEnumState::CreateCopyOfCurrentRecord()
to create a copy of the EMR_SETPIXELV
record at 0x02F20788
and then set the crColor
value of the new record located at 0x02F20798
to 0x003A6677
.
102F20788 0F 00 00 00 10 00 00 00 01 00 00 00 0E 00 00 00 ................
202F20798 77 66 3A 00 00 00 00 00 00 00 00 00 00 00 00 00 wf:.............
The following is the human readable form of the above EMRSETPIXELV
structure which contains the leaked bytes in the crColor
member:
1typedef struct tagEMRSETPIXELV {
2 DWORD iType = 0x0000000f;
3 DWORD nSize; = 0x00000010;
4 POINTL ptlPixel {
5 DWORD x = 0x00000001;
6 DWORD y = 0x0000000e;
7 };
8 COLORREF crColor = 0x003A6677;
9} EMRSETPIXELV;
The bug has been reproduced on a fully patched Windows 10 64-bit with a 32-bit PoC program, but the 64-bit build of GdiPlus.dll
might be also affected. Note that PageHeap
is required to reproduce the crash.
Patch analysis
Timeline
⬅️ 2022-05-20: Reported issue to MSRC.
➡️ 2022-05-23: MSRC opened case 72035.
➡️ 2022-06-02: MSRC confirmed the vulnerability.
➡️ 2022-09-13: Coordinated public release of advisory.